feature #26175 [Security] Add configuration for Argon2i encryption (CoalaJoe)
This PR was merged into the 4.1-dev branch.
Discussion
----------
[Security] Add configuration for Argon2i encryption
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #26174
| License | MIT
| Doc PR | [#9300](https://github.com/symfony/symfony-docs/pull/9300)
Feedback?
Current situation: Configuration only applies if argon2i is natively supported.
Commits
-------
1300fece5f
[Security] Add configuration for Argon2i encryption
This commit is contained in:
commit
2711d144b1
@ -385,6 +385,9 @@ class MainConfiguration implements ConfigurationInterface
|
||||
->max(31)
|
||||
->defaultValue(13)
|
||||
->end()
|
||||
->scalarNode('memory_cost')->defaultNull()->end()
|
||||
->scalarNode('time_cost')->defaultNull()->end()
|
||||
->scalarNode('threads')->defaultNull()->end()
|
||||
->scalarNode('id')->end()
|
||||
->end()
|
||||
->end()
|
||||
|
@ -523,7 +523,11 @@ class SecurityExtension extends Extension
|
||||
|
||||
return array(
|
||||
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
|
||||
'arguments' => array(),
|
||||
'arguments' => array(
|
||||
$config['memory_cost'],
|
||||
$config['time_cost'],
|
||||
$config['threads'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -285,6 +285,9 @@ abstract class CompleteConfigurationTest extends TestCase
|
||||
'key_length' => 40,
|
||||
'ignore_case' => false,
|
||||
'cost' => 13,
|
||||
'memory_cost' => null,
|
||||
'time_cost' => null,
|
||||
'threads' => null,
|
||||
),
|
||||
'JMS\FooBundle\Entity\User3' => array(
|
||||
'algorithm' => 'md5',
|
||||
@ -294,6 +297,9 @@ abstract class CompleteConfigurationTest extends TestCase
|
||||
'encode_as_base64' => true,
|
||||
'iterations' => 5000,
|
||||
'cost' => 13,
|
||||
'memory_cost' => null,
|
||||
'time_cost' => null,
|
||||
'threads' => null,
|
||||
),
|
||||
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
|
||||
'JMS\FooBundle\Entity\User5' => array(
|
||||
@ -307,16 +313,57 @@ abstract class CompleteConfigurationTest extends TestCase
|
||||
)), $container->getDefinition('security.encoder_factory.generic')->getArguments());
|
||||
}
|
||||
|
||||
public function testArgon2iEncoder()
|
||||
public function testEncodersWithLibsodium()
|
||||
{
|
||||
if (!Argon2iPasswordEncoder::isSupported()) {
|
||||
$this->markTestSkipped('Argon2i algorithm is not supported.');
|
||||
}
|
||||
|
||||
$this->assertSame(array(array('JMS\FooBundle\Entity\User7' => array(
|
||||
$container = $this->getContainer('argon2i_encoder');
|
||||
|
||||
$this->assertEquals(array(array(
|
||||
'JMS\FooBundle\Entity\User1' => array(
|
||||
'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
|
||||
'arguments' => array(false),
|
||||
),
|
||||
'JMS\FooBundle\Entity\User2' => array(
|
||||
'algorithm' => 'sha1',
|
||||
'encode_as_base64' => false,
|
||||
'iterations' => 5,
|
||||
'hash_algorithm' => 'sha512',
|
||||
'key_length' => 40,
|
||||
'ignore_case' => false,
|
||||
'cost' => 13,
|
||||
'memory_cost' => null,
|
||||
'time_cost' => null,
|
||||
'threads' => null,
|
||||
),
|
||||
'JMS\FooBundle\Entity\User3' => array(
|
||||
'algorithm' => 'md5',
|
||||
'hash_algorithm' => 'sha512',
|
||||
'key_length' => 40,
|
||||
'ignore_case' => false,
|
||||
'encode_as_base64' => true,
|
||||
'iterations' => 5000,
|
||||
'cost' => 13,
|
||||
'memory_cost' => null,
|
||||
'time_cost' => null,
|
||||
'threads' => null,
|
||||
),
|
||||
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
|
||||
'JMS\FooBundle\Entity\User5' => array(
|
||||
'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder',
|
||||
'arguments' => array('sha1', false, 5, 30),
|
||||
),
|
||||
'JMS\FooBundle\Entity\User6' => array(
|
||||
'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder',
|
||||
'arguments' => array(15),
|
||||
),
|
||||
'JMS\FooBundle\Entity\User7' => array(
|
||||
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
|
||||
'arguments' => array(),
|
||||
))), $this->getContainer('argon2i_encoder')->getDefinition('security.encoder_factory.generic')->getArguments());
|
||||
'arguments' => array(256, 1, 2),
|
||||
),
|
||||
)), $container->getDefinition('security.encoder_factory.generic')->getArguments());
|
||||
}
|
||||
|
||||
public function testRememberMeThrowExceptionsDefault()
|
||||
|
@ -1,18 +1,14 @@
|
||||
<?php
|
||||
|
||||
$this->load('container1.php', $container);
|
||||
|
||||
$container->loadFromExtension('security', array(
|
||||
'encoders' => array(
|
||||
'JMS\FooBundle\Entity\User7' => array(
|
||||
'algorithm' => 'argon2i',
|
||||
),
|
||||
),
|
||||
'providers' => array(
|
||||
'default' => array('id' => 'foo'),
|
||||
),
|
||||
'firewalls' => array(
|
||||
'main' => array(
|
||||
'form_login' => false,
|
||||
'http_basic' => null,
|
||||
'memory_cost' => 256,
|
||||
'time_cost' => 1,
|
||||
'threads' => 2,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
@ -1,18 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<srv:container xmlns="http://symfony.com/schema/dic/security"
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:srv="http://symfony.com/schema/dic/services"
|
||||
xmlns:sec="http://symfony.com/schema/dic/security"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" />
|
||||
<imports>
|
||||
<import resource="container1.xml"/>
|
||||
</imports>
|
||||
|
||||
<provider name="default" id="foo" />
|
||||
<sec:config>
|
||||
<sec:encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" memory_cost="256" time_cost="1" threads="2" />
|
||||
</sec:config>
|
||||
|
||||
<firewall name="main">
|
||||
<form-login login-path="/login" />
|
||||
</firewall>
|
||||
</config>
|
||||
|
||||
</srv:container>
|
||||
</container>
|
||||
|
@ -1,12 +1,10 @@
|
||||
imports:
|
||||
- { resource: container1.yml }
|
||||
|
||||
security:
|
||||
encoders:
|
||||
JMS\FooBundle\Entity\User7:
|
||||
algorithm: argon2i
|
||||
|
||||
providers:
|
||||
default: { id: foo }
|
||||
|
||||
firewalls:
|
||||
main:
|
||||
form_login: false
|
||||
http_basic: ~
|
||||
memory_cost: 256
|
||||
time_cost: 1
|
||||
threads: 2
|
||||
|
@ -18,7 +18,7 @@
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"ext-xml": "*",
|
||||
"symfony/security": "~3.4|~4.0",
|
||||
"symfony/security": "~4.1",
|
||||
"symfony/dependency-injection": "^3.4.3|^4.0.3",
|
||||
"symfony/http-kernel": "~3.4|~4.0"
|
||||
},
|
||||
|
@ -17,9 +17,30 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
* Argon2iPasswordEncoder uses the Argon2i hashing algorithm.
|
||||
*
|
||||
* @author Zan Baldwin <hello@zanbaldwin.com>
|
||||
* @author Dominik Müller <dominik.mueller@jkweb.ch>
|
||||
*/
|
||||
class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface
|
||||
{
|
||||
private $config = array();
|
||||
|
||||
/**
|
||||
* Argon2iPasswordEncoder constructor.
|
||||
*
|
||||
* @param int|null $memoryCost memory usage of the algorithm
|
||||
* @param int|null $timeCost number of iterations
|
||||
* @param int|null $threads number of parallel threads
|
||||
*/
|
||||
public function __construct(int $memoryCost = null, int $timeCost = null, int $threads = null)
|
||||
{
|
||||
if (\defined('PASSWORD_ARGON2I')) {
|
||||
$this->config = array(
|
||||
'memory_cost' => $memoryCost ?? \PASSWORD_ARGON2_DEFAULT_MEMORY_COST,
|
||||
'time_cost' => $timeCost ?? \PASSWORD_ARGON2_DEFAULT_TIME_COST,
|
||||
'threads' => $threads ?? \PASSWORD_ARGON2_DEFAULT_THREADS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
if (\defined('PASSWORD_ARGON2I')) {
|
||||
@ -81,7 +102,7 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE
|
||||
|
||||
private function encodePasswordNative($raw)
|
||||
{
|
||||
return password_hash($raw, \PASSWORD_ARGON2I);
|
||||
return password_hash($raw, \PASSWORD_ARGON2I, $this->config);
|
||||
}
|
||||
|
||||
private function encodePasswordSodiumFunction($raw)
|
||||
|
@ -111,7 +111,11 @@ class EncoderFactory implements EncoderFactoryInterface
|
||||
case 'argon2i':
|
||||
return array(
|
||||
'class' => Argon2iPasswordEncoder::class,
|
||||
'arguments' => array(),
|
||||
'arguments' => array(
|
||||
$config['memory_cost'],
|
||||
$config['time_cost'],
|
||||
$config['threads'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,14 @@ class Argon2iPasswordEncoderTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidationWithConfig()
|
||||
{
|
||||
$encoder = new Argon2iPasswordEncoder(4, 4, 1);
|
||||
$result = $encoder->encodePassword(self::PASSWORD, null);
|
||||
$this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null));
|
||||
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
|
||||
}
|
||||
|
||||
public function testValidation()
|
||||
{
|
||||
$encoder = new Argon2iPasswordEncoder();
|
||||
|
Reference in New Issue
Block a user