/**
* Formats a password using the current encryption. If the user ID is given
* and the hash does not fit the current hashing algorithm, it automatically
* updates the hash.
*
* @param string $password The plaintext password to check.
* @param string $hash The hash to verify against.
* @param integer $userId ID of the user if the password hash should be updated
*
* @return boolean True if the password and hash match, false otherwise
*
* @since 3.2.1
*/
public static function verifyPassword($password, $hash, $userId = 0)
{
$passwordAlgorithm = self::HASH_BCRYPT;
$container = Factory::getContainer();
// Cheaply try to determine the algorithm in use otherwise fall back to the chained handler
if (strpos($hash, '$P$') === 0) {
/** @var PHPassHandler $handler */
$handler = $container->get(PHPassHandler::class);
} elseif (strpos($hash, '$argon2id') === 0) {
/** @var Argon2idHandler $handler */
$handler = $container->get(Argon2idHandler::class);
$passwordAlgorithm = self::HASH_ARGON2ID;
} elseif (strpos($hash, '$argon2i') === 0) {
/** @var Argon2iHandler $handler */
$handler = $container->get(Argon2iHandler::class);
$passwordAlgorithm = self::HASH_ARGON2I;
} elseif (strpos($hash, '$2') === 0) {
/** @var BCryptHandler $handler */
$handler = $container->get(BCryptHandler::class);
} else {
/** @var ChainedHandler $handler */
$handler = $container->get(ChainedHandler::class);
}
$match = $handler->validatePassword($password, $hash);
$rehash = $handler instanceof CheckIfRehashNeededHandlerInterface ? $handler->checkIfRehashNeeded($hash) : false;
// If we have a match and rehash = true, rehash the password with the current algorithm.
if ((int) $userId > 0 && $match && $rehash) {
$user = new User($userId);
$user->password = static::hashPassword($password, $passwordAlgorithm);
$user->save();
}
return $match;
}