Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.30% |
26 / 27 |
|
80.00% |
4 / 5 |
CRAP | |
0.00% |
0 / 1 |
AbstractPbkdf2Password | |
96.30% |
26 / 27 |
|
80.00% |
4 / 5 |
15 | |
0.00% |
0 / 1 |
newInstance | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
4.13 | |||
canUseOpenSSL | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getDefaultParams | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getDelimiter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
crypt | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
7 | |||
getDigestAlgo | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
pbkdf2 | n/a |
0 / 0 |
n/a |
0 / 0 |
0 |
1 | <?php |
2 | /** |
3 | * Implements the AbstractPbkdf2Password class for the MediaWiki software. |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | declare( strict_types = 1 ); |
24 | |
25 | /** |
26 | * A PBKDF2-hashed password |
27 | * |
28 | * This is a computationally complex password hash for use in modern applications. |
29 | * The number of rounds can be configured by $wgPasswordConfig['pbkdf2']['cost']. |
30 | * |
31 | * To support different native implementations of PBKDF2 and the underlying |
32 | * hash algorithms, the following subclasses are available: |
33 | * |
34 | * - Pbkdf2PasswordUsingOpenSSL is the preferred, more efficient option |
35 | * and is used by default when possible. |
36 | * - Pbkdf2PasswordUsingHashExtension provides compatibility with PBKDF2 |
37 | * password hashes computed using legacy algorithms, as well as PHP |
38 | * installations lacking OpenSSL support. |
39 | * |
40 | * @since 1.40 |
41 | */ |
42 | abstract class AbstractPbkdf2Password extends ParameterizedPassword { |
43 | /** |
44 | * Create a new AbstractPbkdf2Password object. |
45 | * |
46 | * In the default configuration, this is used as a factory function |
47 | * in order to select a PBKDF2 implementation automatically. |
48 | * |
49 | * @internal |
50 | * @see Password::__construct |
51 | * @param PasswordFactory $factory Factory object that created the password |
52 | * @param array $config Array of engine configuration options for hashing |
53 | * @param string|null $hash The raw hash, including the type |
54 | * @return AbstractPbkdf2Password The created object |
55 | */ |
56 | public static function newInstance( |
57 | PasswordFactory $factory, |
58 | array $config, |
59 | string $hash = null |
60 | ): self { |
61 | if ( isset( $config['class'] ) && is_subclass_of( $config['class'], self::class ) ) { |
62 | // Use the configured subclass |
63 | return new $config['class']( $factory, $config, $hash ); |
64 | } elseif ( self::canUseOpenSSL() ) { |
65 | return new Pbkdf2PasswordUsingOpenSSL( $factory, $config, $hash ); |
66 | } else { |
67 | return new Pbkdf2PasswordUsingHashExtension( $factory, $config, $hash ); |
68 | } |
69 | } |
70 | |
71 | /** |
72 | * Check if OpenSSL can be used for computing PBKDF2 password hashes. |
73 | * |
74 | * @return bool |
75 | */ |
76 | protected static function canUseOpenSSL(): bool { |
77 | // OpenSSL 1.0.1f (released 2014-01-06) is the earliest version supported by |
78 | // PHP 7.1 through 8.0 that hashes the HMAC key blocks only once rather than |
79 | // on each iteration. Once support for these PHP versions is dropped, the |
80 | // version check can be removed. (PHP 8.1 requires OpenSSL >= 1.0.2) |
81 | return function_exists( 'openssl_pbkdf2' ) && OPENSSL_VERSION_NUMBER >= 0x1000106f; |
82 | } |
83 | |
84 | protected function getDefaultParams(): array { |
85 | return [ |
86 | 'algo' => $this->config['algo'], |
87 | 'rounds' => $this->config['cost'], |
88 | 'length' => $this->config['length'] |
89 | ]; |
90 | } |
91 | |
92 | protected function getDelimiter(): string { |
93 | return ':'; |
94 | } |
95 | |
96 | public function crypt( string $password ): void { |
97 | if ( count( $this->args ) == 0 ) { |
98 | $this->args[] = base64_encode( random_bytes( 16 ) ); |
99 | } |
100 | |
101 | $algo = $this->params['algo']; |
102 | $salt = base64_decode( $this->args[0] ); |
103 | $rounds = (int)$this->params['rounds']; |
104 | $length = (int)$this->params['length']; |
105 | |
106 | $digestAlgo = $this->getDigestAlgo( $algo ); |
107 | if ( $digestAlgo === null ) { |
108 | throw new PasswordError( "Unknown or unsupported algo: $algo" ); |
109 | } |
110 | if ( $rounds <= 0 || $rounds >= 0x7fffffff ) { |
111 | throw new PasswordError( 'Invalid number of rounds.' ); |
112 | } |
113 | if ( $length <= 0 || $length >= 0x7fffffff ) { |
114 | throw new PasswordError( 'Invalid length.' ); |
115 | } |
116 | |
117 | $hash = $this->pbkdf2( $digestAlgo, $password, $salt, $rounds, $length ); |
118 | $this->hash = base64_encode( $hash ); |
119 | } |
120 | |
121 | /** |
122 | * Get the implementation specific name for a hash algorithm. |
123 | * |
124 | * @param string $algo Algorithm specified in the password hash string |
125 | * @return string|null $algo Implementation specific name, or null if unsupported |
126 | */ |
127 | abstract protected function getDigestAlgo( string $algo ): ?string; |
128 | |
129 | /** |
130 | * Call the PBKDF2 implementation, which hashes the password. |
131 | * |
132 | * @param string $digestAlgo Implementation specific hash algorithm name |
133 | * @param string $password Password to hash |
134 | * @param string $salt Salt as a binary string |
135 | * @param int $rounds Number of iterations |
136 | * @param int $length Length of the hash value in bytes |
137 | * @return string Hash value as a binary string |
138 | * @throws PasswordError If an internal error occurs in hashing |
139 | */ |
140 | abstract protected function pbkdf2( |
141 | string $digestAlgo, |
142 | string $password, |
143 | string $salt, |
144 | int $rounds, |
145 | int $length |
146 | ): string; |
147 | } |