[Security] Allow to set a fixed algorithm
This commit is contained in:
parent
54e1d12f92
commit
6712d1e504
@ -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
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -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
|
||||||
----------
|
----------
|
||||||
|
@ -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
|
||||||
-----
|
-----
|
||||||
|
@ -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']] : []),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -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>
|
||||||
|
@ -7,4 +7,3 @@ security:
|
|||||||
algorithm: argon2i
|
algorithm: argon2i
|
||||||
memory_cost: 256
|
memory_cost: 256
|
||||||
time_cost: 1
|
time_cost: 1
|
||||||
threads: 2
|
|
||||||
|
@ -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)));
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
imports:
|
||||||
|
- { resource: config.yml }
|
||||||
|
|
||||||
|
security:
|
||||||
|
encoders:
|
||||||
|
Custom\Class\Argon2id\User:
|
||||||
|
algorithm: argon2id
|
@ -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
|
||||||
-----
|
-----
|
||||||
|
@ -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 [
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
Reference in New Issue
Block a user