Skip to content

Commit 0e7fbcb

Browse files
committed
[Security] Allow to configure a specific password hashing algorithm
1 parent 54e1d12 commit 0e7fbcb

18 files changed

+366
-73
lines changed

UPGRADE-4.3.md

-5
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,6 @@ Security
209209
* Not implementing the methods `__serialize` and `__unserialize` in classes implementing
210210
the `TokenInterface` is deprecated
211211

212-
SecurityBundle
213-
--------------
214-
215-
* Configuring encoders using `argon2i` or `bcrypt` as algorithm has been deprecated, use `auto` instead.
216-
217212
TwigBridge
218213
----------
219214

UPGRADE-5.0.md

-1
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,6 @@ SecurityBundle
509509
changed to underscores.
510510
Before: `my-cookie` deleted the `my_cookie` cookie (with an underscore).
511511
After: `my-cookie` deletes the `my-cookie` cookie (with a dash).
512-
* Configuring encoders using `argon2i` or `bcrypt` as algorithm is not supported anymore, use `auto` instead.
513512

514513
Serializer
515514
----------

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ CHANGELOG
33

44
4.4.0
55

6+
* Added `migrating_from` option to encoders configuration.
67
* Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories.
78

89
4.3.0
@@ -14,7 +15,6 @@ CHANGELOG
1415
option is deprecated and will be disabled in Symfony 5.0. This affects to cookies
1516
with dashes in their names. For example, starting from Symfony 5.0, the `my-cookie`
1617
name will delete `my-cookie` (with a dash) instead of `my_cookie` (with an underscore).
17-
* Deprecated configuring encoders using `argon2i` as algorithm, use `auto` instead
1818

1919
4.2.0
2020
-----

src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php

+4
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,10 @@ private function addEncodersSection(ArrayNodeDefinition $rootNode)
394394
->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
395395
->children()
396396
->scalarNode('algorithm')->cannotBeEmpty()->end()
397+
->arrayNode('migrating_from')
398+
->prototype('scalar')->end()
399+
->beforeNormalization()->castToArray()->end()
400+
->end()
397401
->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end()
398402
->scalarNode('key_length')->defaultValue(40)->end()
399403
->booleanNode('ignore_case')->defaultFalse()->end()

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

+57-14
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
use Symfony\Component\DependencyInjection\Reference;
2929
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
3030
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
31-
use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder;
3231
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
3332
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
3433
use Symfony\Component\Security\Core\User\UserProviderInterface;
@@ -513,6 +512,10 @@ private function createEncoder(array $config)
513512
return new Reference($config['id']);
514513
}
515514

515+
if ($config['migrating_from'] ?? false) {
516+
return $config;
517+
}
518+
516519
// plaintext encoder
517520
if ('plaintext' === $config['algorithm']) {
518521
$arguments = [$config['ignore_case']];
@@ -538,32 +541,72 @@ private function createEncoder(array $config)
538541

539542
// bcrypt encoder
540543
if ('bcrypt' === $config['algorithm']) {
541-
@trigger_error('Configuring an encoder with "bcrypt" as algorithm is deprecated since Symfony 4.3, use "auto" instead.', E_USER_DEPRECATED);
542-
543544
return [
544-
'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder',
545-
'arguments' => [$config['cost'] ?? 13],
545+
'class' => NativePasswordEncoder::class,
546+
'arguments' => [
547+
$config['time_cost'] ?? null,
548+
(($config['memory_cost'] ?? 0) << 10) ?: null,
549+
$config['cost'] ?? null,
550+
\PASSWORD_BCRYPT,
551+
],
546552
];
547553
}
548554

549555
// Argon2i encoder
550556
if ('argon2i' === $config['algorithm']) {
551-
@trigger_error('Configuring an encoder with "argon2i" as algorithm is deprecated since Symfony 4.3, use "auto" instead.', E_USER_DEPRECATED);
557+
if (SodiumPasswordEncoder::isSupported() && !($hasSodiumArgon2id = \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13'))) {
558+
return [
559+
'class' => SodiumPasswordEncoder::class,
560+
'arguments' => [
561+
$config['time_cost'] ?? null,
562+
(($config['memory_cost'] ?? 0) << 10) ?: null,
563+
],
564+
];
565+
}
552566

553-
if (!Argon2iPasswordEncoder::isSupported()) {
554-
if (\extension_loaded('sodium') && !\defined('SODIUM_CRYPTO_PWHASH_SALTBYTES')) {
555-
throw new InvalidConfigurationException('The installed libsodium version does not have support for Argon2i. Use "auto" instead.');
567+
if (!\defined('PASSWORD_ARGON2I')) {
568+
if ($hasSodiumArgon2id ?? false) {
569+
throw new InvalidConfigurationException('Algorithm "argon2i" is not available. You should either use "argon2id", downgrade your sodium extension or use a different encoder.');
556570
}
571+
throw new InvalidConfigurationException('Algorithm "argon2i" is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
572+
}
557573

558-
throw new InvalidConfigurationException('Argon2i algorithm is not supported. Install the libsodium extension or use "auto" instead.');
574+
return [
575+
'class' => NativePasswordEncoder::class,
576+
'arguments' => [
577+
$config['time_cost'] ?? null,
578+
(($config['memory_cost'] ?? 0) << 10) ?: null,
579+
$config['cost'] ?? null,
580+
\PASSWORD_ARGON2I,
581+
],
582+
];
583+
}
584+
585+
if ('argon2id' === $config['algorithm']) {
586+
if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
587+
return [
588+
'class' => SodiumPasswordEncoder::class,
589+
'arguments' => [
590+
$config['time_cost'] ?? null,
591+
(($config['memory_cost'] ?? 0) << 10) ?: null,
592+
],
593+
];
594+
}
595+
596+
if (!\defined('PASSWORD_ARGON2ID')) {
597+
if (\defined('PASSWORD_ARGON2I') || $hasSodium) {
598+
throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. You can either use "argon2i", upgrade to PHP 7.3+, %s sodium extension or use a different encoder.', $hasSodium ? 'upgrade your' : 'install the'));
599+
}
600+
throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. You should either %s sodium extension, upgrade to PHP 7.3+ or use a different encoder.', $hasSodium ? 'upgrade your' : 'install the'));
559601
}
560602

561603
return [
562-
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
604+
'class' => NativePasswordEncoder::class,
563605
'arguments' => [
564-
$config['memory_cost'],
565-
$config['time_cost'],
566-
$config['threads'],
606+
$config['time_cost'] ?? null,
607+
(($config['memory_cost'] ?? 0) << 10) ?: null,
608+
$config['cost'] ?? null,
609+
\PASSWORD_ARGON2ID,
567610
],
568611
];
569612
}

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php

+79-14
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Reference;
2020
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
21-
use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder;
21+
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
2222
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
2323

2424
abstract class CompleteConfigurationTest extends TestCase
@@ -287,6 +287,7 @@ public function testEncoders()
287287
'memory_cost' => null,
288288
'time_cost' => null,
289289
'threads' => null,
290+
'migrating_from' => [],
290291
],
291292
'JMS\FooBundle\Entity\User3' => [
292293
'algorithm' => 'md5',
@@ -299,6 +300,7 @@ public function testEncoders()
299300
'memory_cost' => null,
300301
'time_cost' => null,
301302
'threads' => null,
303+
'migrating_from' => [],
302304
],
303305
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
304306
'JMS\FooBundle\Entity\User5' => [
@@ -320,6 +322,7 @@ public function testEncoders()
320322
'memory_cost' => null,
321323
'time_cost' => null,
322324
'threads' => null,
325+
'migrating_from' => [],
323326
],
324327
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
325328
}
@@ -348,6 +351,7 @@ public function testEncodersWithLibsodium()
348351
'memory_cost' => null,
349352
'time_cost' => null,
350353
'threads' => null,
354+
'migrating_from' => [],
351355
],
352356
'JMS\FooBundle\Entity\User3' => [
353357
'algorithm' => 'md5',
@@ -360,6 +364,7 @@ public function testEncodersWithLibsodium()
360364
'memory_cost' => null,
361365
'time_cost' => null,
362366
'threads' => null,
367+
'migrating_from' => [],
363368
],
364369
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
365370
'JMS\FooBundle\Entity\User5' => [
@@ -377,14 +382,9 @@ public function testEncodersWithLibsodium()
377382
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
378383
}
379384

380-
/**
381-
* @group legacy
382-
*
383-
* @expectedDeprecation Configuring an encoder with "argon2i" as algorithm is deprecated since Symfony 4.3, use "auto" instead.
384-
*/
385385
public function testEncodersWithArgon2i()
386386
{
387-
if (!Argon2iPasswordEncoder::isSupported()) {
387+
if (!($sodium = SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
388388
$this->markTestSkipped('Argon2i algorithm is not supported.');
389389
}
390390

@@ -406,6 +406,7 @@ public function testEncodersWithArgon2i()
406406
'memory_cost' => null,
407407
'time_cost' => null,
408408
'threads' => null,
409+
'migrating_from' => [],
409410
],
410411
'JMS\FooBundle\Entity\User3' => [
411412
'algorithm' => 'md5',
@@ -418,6 +419,7 @@ public function testEncodersWithArgon2i()
418419
'memory_cost' => null,
419420
'time_cost' => null,
420421
'threads' => null,
422+
'migrating_from' => [],
421423
],
422424
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
423425
'JMS\FooBundle\Entity\User5' => [
@@ -429,15 +431,76 @@ public function testEncodersWithArgon2i()
429431
'arguments' => [8, 102400, 15],
430432
],
431433
'JMS\FooBundle\Entity\User7' => [
432-
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
433-
'arguments' => [256, 1, 2],
434+
'class' => $sodium ? SodiumPasswordEncoder::class : NativePasswordEncoder::class,
435+
'arguments' => $sodium ? [256, 1] : [1, 262144, null, \PASSWORD_ARGON2I],
436+
],
437+
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
438+
}
439+
440+
public function testMigratingEncoder()
441+
{
442+
if (!($sodium = SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
443+
$this->markTestSkipped('Argon2i algorithm is not supported.');
444+
}
445+
446+
$container = $this->getContainer('migrating_encoder');
447+
448+
$this->assertEquals([[
449+
'JMS\FooBundle\Entity\User1' => [
450+
'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
451+
'arguments' => [false],
452+
],
453+
'JMS\FooBundle\Entity\User2' => [
454+
'algorithm' => 'sha1',
455+
'encode_as_base64' => false,
456+
'iterations' => 5,
457+
'hash_algorithm' => 'sha512',
458+
'key_length' => 40,
459+
'ignore_case' => false,
460+
'cost' => null,
461+
'memory_cost' => null,
462+
'time_cost' => null,
463+
'threads' => null,
464+
'migrating_from' => [],
465+
],
466+
'JMS\FooBundle\Entity\User3' => [
467+
'algorithm' => 'md5',
468+
'hash_algorithm' => 'sha512',
469+
'key_length' => 40,
470+
'ignore_case' => false,
471+
'encode_as_base64' => true,
472+
'iterations' => 5000,
473+
'cost' => null,
474+
'memory_cost' => null,
475+
'time_cost' => null,
476+
'threads' => null,
477+
'migrating_from' => [],
478+
],
479+
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
480+
'JMS\FooBundle\Entity\User5' => [
481+
'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder',
482+
'arguments' => ['sha1', false, 5, 30],
483+
],
484+
'JMS\FooBundle\Entity\User6' => [
485+
'class' => 'Symfony\Component\Security\Core\Encoder\NativePasswordEncoder',
486+
'arguments' => [8, 102400, 15],
487+
],
488+
'JMS\FooBundle\Entity\User7' => [
489+
'algorithm' => 'argon2i',
490+
'hash_algorithm' => 'sha512',
491+
'key_length' => 40,
492+
'ignore_case' => false,
493+
'encode_as_base64' => true,
494+
'iterations' => 5000,
495+
'cost' => null,
496+
'memory_cost' => 256,
497+
'time_cost' => 1,
498+
'threads' => null,
499+
'migrating_from' => ['bcrypt'],
434500
],
435501
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
436502
}
437503

438-
/**
439-
* @group legacy
440-
*/
441504
public function testEncodersWithBCrypt()
442505
{
443506
$container = $this->getContainer('bcrypt_encoder');
@@ -458,6 +521,7 @@ public function testEncodersWithBCrypt()
458521
'memory_cost' => null,
459522
'time_cost' => null,
460523
'threads' => null,
524+
'migrating_from' => [],
461525
],
462526
'JMS\FooBundle\Entity\User3' => [
463527
'algorithm' => 'md5',
@@ -470,6 +534,7 @@ public function testEncodersWithBCrypt()
470534
'memory_cost' => null,
471535
'time_cost' => null,
472536
'threads' => null,
537+
'migrating_from' => [],
473538
],
474539
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
475540
'JMS\FooBundle\Entity\User5' => [
@@ -481,8 +546,8 @@ public function testEncodersWithBCrypt()
481546
'arguments' => [8, 102400, 15],
482547
],
483548
'JMS\FooBundle\Entity\User7' => [
484-
'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder',
485-
'arguments' => [15],
549+
'class' => NativePasswordEncoder::class,
550+
'arguments' => [null, null, 15, \PASSWORD_BCRYPT],
486551
],
487552
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
488553
}

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
'algorithm' => 'argon2i',
99
'memory_cost' => 256,
1010
'time_cost' => 1,
11-
'threads' => 2,
1211
],
1312
],
1413
]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
$this->load('container1.php', $container);
4+
5+
$container->loadFromExtension('security', [
6+
'encoders' => [
7+
'JMS\FooBundle\Entity\User7' => [
8+
'algorithm' => 'argon2i',
9+
'memory_cost' => 256,
10+
'time_cost' => 1,
11+
'migrating_from' => 'bcrypt',
12+
],
13+
],
14+
]);

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_encoder.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</imports>
1111

1212
<sec:config>
13-
<sec:encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" memory_cost="256" time_cost="1" threads="2" />
13+
<sec:encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" memory_cost="256" time_cost="1" />
1414
</sec:config>
1515

1616
</container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:sec="http://symfony.com/schema/dic/security"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
7+
8+
<imports>
9+
<import resource="container1.xml"/>
10+
</imports>
11+
12+
<sec:config>
13+
<sec:encoder class="JMS\FooBundle\Entity\User7" algorithm="argon2i" memory_cost="256" time_cost="1">
14+
<sec:migrating_from>bcrypt</sec:migrating_from>
15+
</sec:encoder>
16+
</sec:config>
17+
18+
</container>

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml

-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@ security:
77
algorithm: argon2i
88
memory_cost: 256
99
time_cost: 1
10-
threads: 2

0 commit comments

Comments
 (0)