[Security] Add configuration for Argon2i encryption

This commit is contained in:
Ashura 2018-02-14 16:32:53 +01:00 committed by Nicolas Grekas
parent 49759e3138
commit 1300fece5f
10 changed files with 118 additions and 39 deletions

View File

@ -385,6 +385,9 @@ class MainConfiguration implements ConfigurationInterface
->max(31) ->max(31)
->defaultValue(13) ->defaultValue(13)
->end() ->end()
->scalarNode('memory_cost')->defaultNull()->end()
->scalarNode('time_cost')->defaultNull()->end()
->scalarNode('threads')->defaultNull()->end()
->scalarNode('id')->end() ->scalarNode('id')->end()
->end() ->end()
->end() ->end()

View File

@ -523,7 +523,11 @@ class SecurityExtension extends Extension
return array( return array(
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder', 'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
'arguments' => array(), 'arguments' => array(
$config['memory_cost'],
$config['time_cost'],
$config['threads'],
),
); );
} }

View File

@ -285,6 +285,9 @@ abstract class CompleteConfigurationTest extends TestCase
'key_length' => 40, 'key_length' => 40,
'ignore_case' => false, 'ignore_case' => false,
'cost' => 13, 'cost' => 13,
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
), ),
'JMS\FooBundle\Entity\User3' => array( 'JMS\FooBundle\Entity\User3' => array(
'algorithm' => 'md5', 'algorithm' => 'md5',
@ -294,6 +297,9 @@ abstract class CompleteConfigurationTest extends TestCase
'encode_as_base64' => true, 'encode_as_base64' => true,
'iterations' => 5000, 'iterations' => 5000,
'cost' => 13, 'cost' => 13,
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
), ),
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'), 'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => array( 'JMS\FooBundle\Entity\User5' => array(
@ -307,16 +313,57 @@ abstract class CompleteConfigurationTest extends TestCase
)), $container->getDefinition('security.encoder_factory.generic')->getArguments()); )), $container->getDefinition('security.encoder_factory.generic')->getArguments());
} }
public function testArgon2iEncoder() public function testEncodersWithLibsodium()
{ {
if (!Argon2iPasswordEncoder::isSupported()) { if (!Argon2iPasswordEncoder::isSupported()) {
$this->markTestSkipped('Argon2i algorithm is not supported.'); $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', 'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
'arguments' => array(), 'arguments' => array(256, 1, 2),
))), $this->getContainer('argon2i_encoder')->getDefinition('security.encoder_factory.generic')->getArguments()); ),
)), $container->getDefinition('security.encoder_factory.generic')->getArguments());
} }
public function testRememberMeThrowExceptionsDefault() public function testRememberMeThrowExceptionsDefault()

View File

@ -1,18 +1,14 @@
<?php <?php
$this->load('container1.php', $container);
$container->loadFromExtension('security', array( $container->loadFromExtension('security', array(
'encoders' => array( 'encoders' => array(
'JMS\FooBundle\Entity\User7' => array( 'JMS\FooBundle\Entity\User7' => array(
'algorithm' => 'argon2i', 'algorithm' => 'argon2i',
), 'memory_cost' => 256,
), 'time_cost' => 1,
'providers' => array( 'threads' => 2,
'default' => array('id' => 'foo'),
),
'firewalls' => array(
'main' => array(
'form_login' => false,
'http_basic' => null,
), ),
), ),
)); ));

View File

@ -1,18 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?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: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"> xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<config> <imports>
<encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" /> <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"> </container>
<form-login login-path="/login" />
</firewall>
</config>
</srv:container>

View File

@ -1,12 +1,10 @@
imports:
- { resource: container1.yml }
security: security:
encoders: encoders:
JMS\FooBundle\Entity\User7: JMS\FooBundle\Entity\User7:
algorithm: argon2i algorithm: argon2i
memory_cost: 256
providers: time_cost: 1
default: { id: foo } threads: 2
firewalls:
main:
form_login: false
http_basic: ~

View File

@ -18,7 +18,7 @@
"require": { "require": {
"php": "^7.1.3", "php": "^7.1.3",
"ext-xml": "*", "ext-xml": "*",
"symfony/security": "~3.4|~4.0", "symfony/security": "~4.1",
"symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/dependency-injection": "^3.4.3|^4.0.3",
"symfony/http-kernel": "~3.4|~4.0" "symfony/http-kernel": "~3.4|~4.0"
}, },

View File

@ -17,9 +17,30 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException;
* Argon2iPasswordEncoder uses the Argon2i hashing algorithm. * Argon2iPasswordEncoder uses the Argon2i hashing algorithm.
* *
* @author Zan Baldwin <hello@zanbaldwin.com> * @author Zan Baldwin <hello@zanbaldwin.com>
* @author Dominik Müller <dominik.mueller@jkweb.ch>
*/ */
class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface 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() public static function isSupported()
{ {
if (\defined('PASSWORD_ARGON2I')) { if (\defined('PASSWORD_ARGON2I')) {
@ -81,7 +102,7 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE
private function encodePasswordNative($raw) private function encodePasswordNative($raw)
{ {
return password_hash($raw, \PASSWORD_ARGON2I); return password_hash($raw, \PASSWORD_ARGON2I, $this->config);
} }
private function encodePasswordSodiumFunction($raw) private function encodePasswordSodiumFunction($raw)

View File

@ -111,7 +111,11 @@ class EncoderFactory implements EncoderFactoryInterface
case 'argon2i': case 'argon2i':
return array( return array(
'class' => Argon2iPasswordEncoder::class, 'class' => Argon2iPasswordEncoder::class,
'arguments' => array(), 'arguments' => array(
$config['memory_cost'],
$config['time_cost'],
$config['threads'],
),
); );
} }

View File

@ -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() public function testValidation()
{ {
$encoder = new Argon2iPasswordEncoder(); $encoder = new Argon2iPasswordEncoder();