[Security] Allow to set a fixed algorithm

This commit is contained in:
Robin Chalas 2019-10-27 11:08:13 +01:00 committed by Nicolas Grekas
parent 54e1d12f92
commit 6712d1e504
14 changed files with 141 additions and 83 deletions

View File

@ -209,11 +209,6 @@ Security
* Not implementing the methods `__serialize` and `__unserialize` in classes implementing * Not implementing the methods `__serialize` and `__unserialize` in classes implementing
the `TokenInterface` is deprecated the `TokenInterface` is deprecated
SecurityBundle
--------------
* Configuring encoders using `argon2i` or `bcrypt` as algorithm has been deprecated, use `auto` instead.
TwigBridge TwigBridge
---------- ----------

View File

@ -509,7 +509,6 @@ SecurityBundle
changed to underscores. changed to underscores.
Before: `my-cookie` deleted the `my_cookie` cookie (with an underscore). Before: `my-cookie` deleted the `my_cookie` cookie (with an underscore).
After: `my-cookie` deletes the `my-cookie` cookie (with a dash). After: `my-cookie` deletes the `my-cookie` cookie (with a dash).
* Configuring encoders using `argon2i` or `bcrypt` as algorithm is not supported anymore, use `auto` instead.
Serializer Serializer
---------- ----------

View File

@ -2,8 +2,10 @@ CHANGELOG
========= =========
4.4.0 4.4.0
-----
* Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories. * Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
* Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories.
4.3.0 4.3.0
----- -----
@ -14,7 +16,6 @@ CHANGELOG
option is deprecated and will be disabled in Symfony 5.0. This affects to cookies option is deprecated and will be disabled in Symfony 5.0. This affects to cookies
with dashes in their names. For example, starting from Symfony 5.0, the `my-cookie` with dashes in their names. For example, starting from Symfony 5.0, the `my-cookie`
name will delete `my-cookie` (with a dash) instead of `my_cookie` (with an underscore). name will delete `my-cookie` (with a dash) instead of `my_cookie` (with an underscore).
* Deprecated configuring encoders using `argon2i` as algorithm, use `auto` instead
4.2.0 4.2.0
----- -----

View File

@ -28,7 +28,6 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
@ -538,34 +537,37 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
// bcrypt encoder // bcrypt encoder
if ('bcrypt' === $config['algorithm']) { if ('bcrypt' === $config['algorithm']) {
@trigger_error('Configuring an encoder with "bcrypt" as algorithm is deprecated since Symfony 4.3, use "auto" instead.', E_USER_DEPRECATED); $config['algorithm'] = 'native';
$config['native_algorithm'] = PASSWORD_BCRYPT;
return [ return $this->createEncoder($config);
'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder',
'arguments' => [$config['cost'] ?? 13],
];
} }
// Argon2i encoder // Argon2i encoder
if ('argon2i' === $config['algorithm']) { if ('argon2i' === $config['algorithm']) {
@trigger_error('Configuring an encoder with "argon2i" as algorithm is deprecated since Symfony 4.3, use "auto" instead.', E_USER_DEPRECATED); if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
if (!Argon2iPasswordEncoder::isSupported()) { } elseif (\defined('PASSWORD_ARGON2I')) {
if (\extension_loaded('sodium') && !\defined('SODIUM_CRYPTO_PWHASH_SALTBYTES')) { $config['algorithm'] = 'native';
throw new InvalidConfigurationException('The installed libsodium version does not have support for Argon2i. Use "auto" instead.'); $config['native_algorithm'] = PASSWORD_ARGON2I;
} } else {
throw new InvalidConfigurationException(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 InvalidConfigurationException('Argon2i algorithm is not supported. Install the libsodium extension or use "auto" instead.');
} }
return [ return $this->createEncoder($config);
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder', }
'arguments' => [
$config['memory_cost'], if ('argon2id' === $config['algorithm']) {
$config['time_cost'], if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['threads'], $config['algorithm'] = 'sodium';
], } elseif (\defined('PASSWORD_ARGON2ID')) {
]; $config['algorithm'] = 'native';
$config['native_algorithm'] = PASSWORD_ARGON2ID;
} else {
throw new InvalidConfigurationException(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", ' : ''));
}
return $this->createEncoder($config);
} }
if ('native' === $config['algorithm']) { if ('native' === $config['algorithm']) {
@ -574,8 +576,8 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
'arguments' => [ 'arguments' => [
$config['time_cost'], $config['time_cost'],
(($config['memory_cost'] ?? 0) << 10) ?: null, (($config['memory_cost'] ?? 0) << 10) ?: null,
$config['cost'], $config['cost']
], ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
]; ];
} }

View File

@ -18,7 +18,7 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder; use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
abstract class CompleteConfigurationTest extends TestCase abstract class CompleteConfigurationTest extends TestCase
@ -377,14 +377,9 @@ abstract class CompleteConfigurationTest extends TestCase
]], $container->getDefinition('security.encoder_factory.generic')->getArguments()); ]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
} }
/**
* @group legacy
*
* @expectedDeprecation Configuring an encoder with "argon2i" as algorithm is deprecated since Symfony 4.3, use "auto" instead.
*/
public function testEncodersWithArgon2i() public function testEncodersWithArgon2i()
{ {
if (!Argon2iPasswordEncoder::isSupported()) { if (!($sodium = SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Argon2i algorithm is not supported.'); $this->markTestSkipped('Argon2i algorithm is not supported.');
} }
@ -429,19 +424,15 @@ abstract class CompleteConfigurationTest extends TestCase
'arguments' => [8, 102400, 15], 'arguments' => [8, 102400, 15],
], ],
'JMS\FooBundle\Entity\User7' => [ 'JMS\FooBundle\Entity\User7' => [
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder', 'class' => $sodium ? SodiumPasswordEncoder::class : NativePasswordEncoder::class,
'arguments' => [256, 1, 2], 'arguments' => $sodium ? [256, 1] : [1, 262144, null, \PASSWORD_ARGON2I],
], ],
]], $container->getDefinition('security.encoder_factory.generic')->getArguments()); ]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
} }
/**
* @group legacy
*/
public function testEncodersWithBCrypt() public function testEncodersWithBCrypt()
{ {
$container = $this->getContainer('bcrypt_encoder'); $container = $this->getContainer('bcrypt_encoder');
$this->assertEquals([[ $this->assertEquals([[
'JMS\FooBundle\Entity\User1' => [ 'JMS\FooBundle\Entity\User1' => [
'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder', 'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
@ -481,8 +472,8 @@ abstract class CompleteConfigurationTest extends TestCase
'arguments' => [8, 102400, 15], 'arguments' => [8, 102400, 15],
], ],
'JMS\FooBundle\Entity\User7' => [ 'JMS\FooBundle\Entity\User7' => [
'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder', 'class' => NativePasswordEncoder::class,
'arguments' => [15], 'arguments' => [null, null, 15, \PASSWORD_BCRYPT],
], ],
]], $container->getDefinition('security.encoder_factory.generic')->getArguments()); ]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
} }

View File

@ -8,7 +8,6 @@ $container->loadFromExtension('security', [
'algorithm' => 'argon2i', 'algorithm' => 'argon2i',
'memory_cost' => 256, 'memory_cost' => 256,
'time_cost' => 1, 'time_cost' => 1,
'threads' => 2,
], ],
], ],
]); ]);

View File

@ -10,7 +10,7 @@
</imports> </imports>
<sec:config> <sec:config>
<sec:encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" memory_cost="256" time_cost="1" threads="2" /> <sec:encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" memory_cost="256" time_cost="1" />
</sec:config> </sec:config>
</container> </container>

View File

@ -7,4 +7,3 @@ security:
algorithm: argon2i algorithm: argon2i
memory_cost: 256 memory_cost: 256
time_cost: 1 time_cost: 1
threads: 2

View File

@ -15,8 +15,6 @@ use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand; use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand;
use Symfony\Component\Console\Application as ConsoleApplication; use Symfony\Component\Console\Application as ConsoleApplication;
use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder; use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder;
@ -55,9 +53,6 @@ class UserPasswordEncoderCommandTest extends AbstractWebTestCase
$this->assertEquals($statusCode, 1); $this->assertEquals($statusCode, 1);
} }
/**
* @group legacy
*/
public function testEncodePasswordBcrypt() public function testEncodePasswordBcrypt()
{ {
$this->setupBcrypt(); $this->setupBcrypt();
@ -70,18 +65,15 @@ class UserPasswordEncoderCommandTest extends AbstractWebTestCase
$output = $this->passwordEncoderCommandTester->getDisplay(); $output = $this->passwordEncoderCommandTester->getDisplay();
$this->assertStringContainsString('Password encoding succeeded', $output); $this->assertStringContainsString('Password encoding succeeded', $output);
$encoder = new BCryptPasswordEncoder(17); $encoder = new NativePasswordEncoder(null, null, 17, PASSWORD_BCRYPT);
preg_match('# Encoded password\s{1,}([\w+\/$.]+={0,2})\s+#', $output, $matches); preg_match('# Encoded password\s{1,}([\w+\/$.]+={0,2})\s+#', $output, $matches);
$hash = $matches[1]; $hash = $matches[1];
$this->assertTrue($encoder->isPasswordValid($hash, 'password', null)); $this->assertTrue($encoder->isPasswordValid($hash, 'password', null));
} }
/**
* @group legacy
*/
public function testEncodePasswordArgon2i() public function testEncodePasswordArgon2i()
{ {
if (!Argon2iPasswordEncoder::isSupported()) { if (!($sodium = SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Argon2i algorithm not available.'); $this->markTestSkipped('Argon2i algorithm not available.');
} }
$this->setupArgon2i(); $this->setupArgon2i();
@ -94,7 +86,28 @@ class UserPasswordEncoderCommandTest extends AbstractWebTestCase
$output = $this->passwordEncoderCommandTester->getDisplay(); $output = $this->passwordEncoderCommandTester->getDisplay();
$this->assertStringContainsString('Password encoding succeeded', $output); $this->assertStringContainsString('Password encoding succeeded', $output);
$encoder = new Argon2iPasswordEncoder(); $encoder = $sodium ? new SodiumPasswordEncoder() : new NativePasswordEncoder(null, null, null, PASSWORD_ARGON2I);
preg_match('# Encoded password\s+(\$argon2i?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches);
$hash = $matches[1];
$this->assertTrue($encoder->isPasswordValid($hash, 'password', null));
}
public function testEncodePasswordArgon2id()
{
if (!($sodium = (SodiumPasswordEncoder::isSupported() && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13'))) && !\defined('PASSWORD_ARGON2ID')) {
$this->markTestSkipped('Argon2id algorithm not available.');
}
$this->setupArgon2id();
$this->passwordEncoderCommandTester->execute([
'command' => 'security:encode-password',
'password' => 'password',
'user-class' => 'Custom\Class\Argon2id\User',
], ['interactive' => false]);
$output = $this->passwordEncoderCommandTester->getDisplay();
$this->assertStringContainsString('Password encoding succeeded', $output);
$encoder = $sodium ? new SodiumPasswordEncoder() : new NativePasswordEncoder(null, null, null, PASSWORD_ARGON2ID);
preg_match('# Encoded password\s+(\$argon2id?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches); preg_match('# Encoded password\s+(\$argon2id?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches);
$hash = $matches[1]; $hash = $matches[1];
$this->assertTrue($encoder->isPasswordValid($hash, 'password', null)); $this->assertTrue($encoder->isPasswordValid($hash, 'password', null));
@ -195,12 +208,9 @@ class UserPasswordEncoderCommandTest extends AbstractWebTestCase
$this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay()); $this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay());
} }
/**
* @group legacy
*/
public function testEncodePasswordArgon2iOutput() public function testEncodePasswordArgon2iOutput()
{ {
if (!Argon2iPasswordEncoder::isSupported()) { if (!(SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Argon2i algorithm not available.'); $this->markTestSkipped('Argon2i algorithm not available.');
} }
@ -214,6 +224,22 @@ class UserPasswordEncoderCommandTest extends AbstractWebTestCase
$this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay()); $this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay());
} }
public function testEncodePasswordArgon2idOutput()
{
if (!(SodiumPasswordEncoder::isSupported() && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2ID')) {
$this->markTestSkipped('Argon2id algorithm not available.');
}
$this->setupArgon2id();
$this->passwordEncoderCommandTester->execute([
'command' => 'security:encode-password',
'password' => 'p@ssw0rd',
'user-class' => 'Custom\Class\Argon2id\User',
], ['interactive' => false]);
$this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay());
}
public function testEncodePasswordSodiumOutput() public function testEncodePasswordSodiumOutput()
{ {
if (!SodiumPasswordEncoder::isSupported()) { if (!SodiumPasswordEncoder::isSupported()) {
@ -317,6 +343,19 @@ EOTXT
$this->passwordEncoderCommandTester = new CommandTester($passwordEncoderCommand); $this->passwordEncoderCommandTester = new CommandTester($passwordEncoderCommand);
} }
private function setupArgon2id()
{
putenv('COLUMNS='.(119 + \strlen(PHP_EOL)));
$kernel = $this->createKernel(['test_case' => 'PasswordEncode', 'root_config' => 'argon2id.yml']);
$kernel->boot();
$application = new Application($kernel);
$passwordEncoderCommand = $application->get('security:encode-password');
$this->passwordEncoderCommandTester = new CommandTester($passwordEncoderCommand);
}
private function setupBcrypt() private function setupBcrypt()
{ {
putenv('COLUMNS='.(119 + \strlen(PHP_EOL))); putenv('COLUMNS='.(119 + \strlen(PHP_EOL)));

View File

@ -0,0 +1,7 @@
imports:
- { resource: config.yml }
security:
encoders:
Custom\Class\Argon2id\User:
algorithm: argon2id

View File

@ -13,6 +13,7 @@ CHANGELOG
* Marked all dispatched event classes as `@final` * Marked all dispatched event classes as `@final`
* Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. * Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`.
* Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` * Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()`
* Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
4.3.0 4.3.0
----- -----

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Security\Core\Encoder; namespace Symfony\Component\Security\Core\Encoder;
use Symfony\Component\Security\Core\Exception\LogicException;
/** /**
* A generic encoder factory implementation. * A generic encoder factory implementation.
* *
@ -114,12 +116,11 @@ class EncoderFactory implements EncoderFactoryInterface
], ],
]; ];
/* @deprecated since Symfony 4.3 */
case 'bcrypt': case 'bcrypt':
return [ $config['algorithm'] = 'native';
'class' => BCryptPasswordEncoder::class, $config['native_algorithm'] = PASSWORD_BCRYPT;
'arguments' => [$config['cost']],
]; return $this->getEncoderConfigFromAlgorithm($config);
case 'native': case 'native':
return [ return [
@ -127,8 +128,8 @@ class EncoderFactory implements EncoderFactoryInterface
'arguments' => [ 'arguments' => [
$config['time_cost'] ?? null, $config['time_cost'] ?? null,
(($config['memory_cost'] ?? 0) << 10) ?: null, (($config['memory_cost'] ?? 0) << 10) ?: null,
$config['cost'] ?? null, $config['cost'] ?? null
], ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
]; ];
case 'sodium': case 'sodium':
@ -140,16 +141,29 @@ class EncoderFactory implements EncoderFactoryInterface
], ],
]; ];
/* @deprecated since Symfony 4.3 */
case 'argon2i': case 'argon2i':
return [ if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
'class' => Argon2iPasswordEncoder::class, $config['algorithm'] = 'sodium';
'arguments' => [ } elseif (\defined('PASSWORD_ARGON2I')) {
$config['memory_cost'], $config['algorithm'] = 'native';
$config['time_cost'], $config['native_algorithm'] = PASSWORD_ARGON2I;
$config['threads'], } 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", ' : ''));
]; }
return $this->getEncoderConfigFromAlgorithm($config);
case 'argon2id':
if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2ID')) {
$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", ' : ''));
}
return $this->getEncoderConfigFromAlgorithm($config);
} }
return [ return [

View File

@ -27,7 +27,10 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
private $algo; private $algo;
private $options; private $options;
public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null) /**
* @param string|null $algo An algorithm supported by password_hash() or null to use the stronger available algorithm
*/
public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, string $algo = null)
{ {
$cost = $cost ?? 13; $cost = $cost ?? 13;
$opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); $opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4);
@ -45,7 +48,7 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
throw new \InvalidArgumentException('$cost must be in the range of 4-31.'); throw new \InvalidArgumentException('$cost must be in the range of 4-31.');
} }
$this->algo = \defined('PASSWORD_ARGON2I') ? max(PASSWORD_DEFAULT, \defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_ARGON2I) : PASSWORD_DEFAULT; $this->algo = $algo ?? (\defined('PASSWORD_ARGON2I') ? max(PASSWORD_DEFAULT, \defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_ARGON2I) : PASSWORD_DEFAULT);
$this->options = [ $this->options = [
'cost' => $cost, 'cost' => $cost,
'time_cost' => $opsLimit, 'time_cost' => $opsLimit,

View File

@ -55,6 +55,14 @@ class NativePasswordEncoderTest extends TestCase
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
} }
public function testConfiguredAlgorithm()
{
$encoder = new NativePasswordEncoder(null, null, null, PASSWORD_BCRYPT);
$result = $encoder->encodePassword('password', null);
$this->assertTrue($encoder->isPasswordValid($result, 'password', null));
$this->assertStringStartsWith('$2', $result);
}
public function testCheckPasswordLength() public function testCheckPasswordLength()
{ {
$encoder = new NativePasswordEncoder(null, null, 4); $encoder = new NativePasswordEncoder(null, null, 4);