wiki.techinc.nl/includes/password/AbstractPbkdf2Password.php
Daimona Eaytoy 1d6776fdbc Replace deprecated MWException
Also remove some unchecked exception from doc comments.

Bug: T328220
Bug: T240672
Change-Id: I88b1e948ce5da77d9c4862a2b98793d6ba00cf8b
2024-01-19 21:58:42 +00:00

147 lines
5 KiB
PHP

<?php
/**
* Implements the AbstractPbkdf2Password class for the MediaWiki software.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
declare( strict_types = 1 );
/**
* A PBKDF2-hashed password
*
* This is a computationally complex password hash for use in modern applications.
* The number of rounds can be configured by $wgPasswordConfig['pbkdf2']['cost'].
*
* To support different native implementations of PBKDF2 and the underlying
* hash algorithms, the following subclasses are available:
*
* - Pbkdf2PasswordUsingOpenSSL is the preferred, more efficient option
* and is used by default when possible.
* - Pbkdf2PasswordUsingHashExtension provides compatibility with PBKDF2
* password hashes computed using legacy algorithms, as well as PHP
* installations lacking OpenSSL support.
*
* @since 1.40
*/
abstract class AbstractPbkdf2Password extends ParameterizedPassword {
/**
* Create a new AbstractPbkdf2Password object.
*
* In the default configuration, this is used as a factory function
* in order to select a PBKDF2 implementation automatically.
*
* @internal
* @see Password::__construct
* @param PasswordFactory $factory Factory object that created the password
* @param array $config Array of engine configuration options for hashing
* @param string|null $hash The raw hash, including the type
* @return AbstractPbkdf2Password The created object
*/
public static function newInstance(
PasswordFactory $factory,
array $config,
string $hash = null
): self {
if ( isset( $config['class'] ) && is_subclass_of( $config['class'], self::class ) ) {
// Use the configured subclass
return new $config['class']( $factory, $config, $hash );
} elseif ( self::canUseOpenSSL() ) {
return new Pbkdf2PasswordUsingOpenSSL( $factory, $config, $hash );
} else {
return new Pbkdf2PasswordUsingHashExtension( $factory, $config, $hash );
}
}
/**
* Check if OpenSSL can be used for computing PBKDF2 password hashes.
*
* @return bool
*/
protected static function canUseOpenSSL(): bool {
// OpenSSL 1.0.1f (released 2014-01-06) is the earliest version supported by
// PHP 7.1 through 8.0 that hashes the HMAC key blocks only once rather than
// on each iteration. Once support for these PHP versions is dropped, the
// version check can be removed. (PHP 8.1 requires OpenSSL >= 1.0.2)
return function_exists( 'openssl_pbkdf2' ) && OPENSSL_VERSION_NUMBER >= 0x1000106f;
}
protected function getDefaultParams(): array {
return [
'algo' => $this->config['algo'],
'rounds' => $this->config['cost'],
'length' => $this->config['length']
];
}
protected function getDelimiter(): string {
return ':';
}
public function crypt( string $password ): void {
if ( count( $this->args ) == 0 ) {
$this->args[] = base64_encode( random_bytes( 16 ) );
}
$algo = $this->params['algo'];
$salt = base64_decode( $this->args[0] );
$rounds = (int)$this->params['rounds'];
$length = (int)$this->params['length'];
$digestAlgo = $this->getDigestAlgo( $algo );
if ( $digestAlgo === null ) {
throw new PasswordError( "Unknown or unsupported algo: $algo" );
}
if ( $rounds <= 0 || $rounds >= 0x7fffffff ) {
throw new PasswordError( 'Invalid number of rounds.' );
}
if ( $length <= 0 || $length >= 0x7fffffff ) {
throw new PasswordError( 'Invalid length.' );
}
$hash = $this->pbkdf2( $digestAlgo, $password, $salt, $rounds, $length );
$this->hash = base64_encode( $hash );
}
/**
* Get the implementation specific name for a hash algorithm.
*
* @param string $algo Algorithm specified in the password hash string
* @return string|null $algo Implementation specific name, or null if unsupported
*/
abstract protected function getDigestAlgo( string $algo ): ?string;
/**
* Call the PBKDF2 implementation, which hashes the password.
*
* @param string $digestAlgo Implementation specific hash algorithm name
* @param string $password Password to hash
* @param string $salt Salt as a binary string
* @param int $rounds Number of iterations
* @param int $length Length of the hash value in bytes
* @return string Hash value as a binary string
* @throws PasswordError If an internal error occurs in hashing
*/
abstract protected function pbkdf2(
string $digestAlgo,
string $password,
string $salt,
int $rounds,
int $length
): string;
}