Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.30% covered (success)
96.30%
26 / 27
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractPbkdf2Password
96.30% covered (success)
96.30%
26 / 27
80.00% covered (warning)
80.00%
4 / 5
15
0.00% covered (danger)
0.00%
0 / 1
 newInstance
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 canUseOpenSSL
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getDefaultParams
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getDelimiter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 crypt
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
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
23declare( 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 */
42abstract 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}