diff --git a/src/Symfony/Component/PasswordHasher/CHANGELOG.md b/src/Symfony/Component/PasswordHasher/CHANGELOG.md index 22693a3bf9dca..53ea3f1c17b6e 100644 --- a/src/Symfony/Component/PasswordHasher/CHANGELOG.md +++ b/src/Symfony/Component/PasswordHasher/CHANGELOG.md @@ -2,3 +2,4 @@ --- * Add the component + * Use `bcrypt` as default algorithm in `NativePasswordHasher` diff --git a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php index f147868ad8dfc..b6090bc2aca3c 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php @@ -29,7 +29,7 @@ final class NativePasswordHasher implements PasswordHasherInterface private $options; /** - * @param string|null $algo An algorithm supported by password_hash() or null to use the stronger available algorithm + * @param string|null $algo An algorithm supported by password_hash() or null to use the best available algorithm */ public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, ?string $algo = null) { @@ -52,11 +52,11 @@ public function __construct(int $opsLimit = null, int $memLimit = null, int $cos $algos = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT]; if (\defined('PASSWORD_ARGON2I')) { - $this->algo = $algos[2] = $algos['argon2i'] = (string) \PASSWORD_ARGON2I; + $algos[2] = $algos['argon2i'] = (string) \PASSWORD_ARGON2I; } if (\defined('PASSWORD_ARGON2ID')) { - $this->algo = $algos[3] = $algos['argon2id'] = (string) \PASSWORD_ARGON2ID; + $algos[3] = $algos['argon2id'] = (string) \PASSWORD_ARGON2ID; } if (null !== $algo) { diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php index 03bbe7350c2cf..da2e262c82f4e 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php @@ -11,9 +11,9 @@ namespace Symfony\Component\PasswordHasher\Hasher; -use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; use Symfony\Component\PasswordHasher\Exception\LogicException; use Symfony\Component\PasswordHasher\PasswordHasherInterface; +use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; /** * A generic hasher factory implementation. @@ -103,9 +103,15 @@ private function createHasher(array $config, bool $isExtra = false): PasswordHas private function getHasherConfigFromAlgorithm(array $config): array { if ('auto' === $config['algorithm']) { - $hasherChain = []; // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly - foreach ([SodiumPasswordHasher::isSupported() ? 'sodium' : 'native', 'pbkdf2', $config['hash_algorithm']] as $algo) { + if (SodiumPasswordHasher::isSupported()) { + $algos = ['native', 'sodium', 'pbkdf2', $config['hash_algorithm']]; + } else { + $algos = ['native', 'pbkdf2', $config['hash_algorithm']]; + } + + $hasherChain = []; + foreach ($algos as $algo) { $config['algorithm'] = $algo; $hasherChain[] = $this->createHasher($config, true); } @@ -186,7 +192,7 @@ private function getHasherConfigFromAlgorithm(array $config): array $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2I; } else { - throw new LogicException(sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : '')); + throw new LogicException(sprintf('Algorithm "argon2i" is not available. Use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id" or "auto' : 'auto')); } return $this->getHasherConfigFromAlgorithm($config); @@ -198,7 +204,7 @@ private function getHasherConfigFromAlgorithm(array $config): array $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2ID; } else { - throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : '')); + throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); } return $this->getHasherConfigFromAlgorithm($config); diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php index 90f48267ce36f..8132bc76933f9 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/NativePasswordHasherTest.php @@ -21,13 +21,13 @@ class NativePasswordHasherTest extends TestCase { public function testCostBelowRange() { - $this->expectException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new NativePasswordHasher(null, null, 3); } public function testCostAboveRange() { - $this->expectException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); new NativePasswordHasher(null, null, 32); } @@ -73,6 +73,14 @@ public function testConfiguredAlgorithm() $this->assertStringStartsWith('$2', $result); } + public function testDefaultAlgorithm() + { + $hasher = new NativePasswordHasher(); + $result = $hasher->hash('password'); + $this->assertTrue($hasher->verify($result, 'password')); + $this->assertStringStartsWith('$2', $result); + } + public function testConfiguredAlgorithmWithLegacyConstValue() { $hasher = new NativePasswordHasher(null, null, null, '1');