Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d18baac

Browse filesBrowse files
committed
[Security] Allow to use a specific password hashing algorithm
1 parent 6253369 commit d18baac
Copy full SHA for d18baac

File tree

Expand file treeCollapse file tree

11 files changed

+127
-63
lines changed
Filter options
Expand file treeCollapse file tree

11 files changed

+127
-63
lines changed

‎UPGRADE-4.3.md

Copy file name to clipboardExpand all lines: UPGRADE-4.3.md
-5Lines changed: 0 additions & 5 deletions
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

Copy file name to clipboardExpand all lines: UPGRADE-5.0.md
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,6 @@ SecurityBundle
537537
changed to underscores.
538538
Before: `my-cookie` deleted the `my_cookie` cookie (with an underscore).
539539
After: `my-cookie` deletes the `my-cookie` cookie (with a dash).
540-
* Configuring encoders using `argon2i` or `bcrypt` as algorithm is not supported anymore, use `auto` instead.
541540

542541
Serializer
543542
----------

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+53-14Lines changed: 53 additions & 14 deletions
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;
@@ -538,32 +537,72 @@ private function createEncoder(array $config)
538537

539538
// bcrypt encoder
540539
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-
543540
return [
544-
'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder',
545-
'arguments' => [$config['cost'] ?? 13],
541+
'class' => NativePasswordEncoder::class,
542+
'arguments' => [
543+
$config['time_cost'] ?? null,
544+
(($config['memory_cost'] ?? 0) << 10) ?: null,
545+
$config['cost'] ?? null,
546+
\PASSWORD_BCRYPT,
547+
],
546548
];
547549
}
548550

549551
// Argon2i encoder
550552
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);
553+
if (SodiumPasswordEncoder::isSupported() && !($hasSodiumArgon2id = \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13'))) {
554+
return [
555+
'class' => SodiumPasswordEncoder::class,
556+
'arguments' => [
557+
$config['time_cost'] ?? null,
558+
(($config['memory_cost'] ?? 0) << 10) ?: null,
559+
],
560+
];
561+
}
552562

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.');
563+
if (!\defined('PASSWORD_ARGON2I')) {
564+
if ($hasSodiumArgon2id ?? false) {
565+
throw new InvalidConfigurationException('Algorithm "argon2i" is not available. You should either use "argon2id", downgrade your sodium extension or use a different encoder.');
556566
}
567+
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.');
568+
}
557569

558-
throw new InvalidConfigurationException('Argon2i algorithm is not supported. Install the libsodium extension or use "auto" instead.');
570+
return [
571+
'class' => NativePasswordEncoder::class,
572+
'arguments' => [
573+
$config['time_cost'] ?? null,
574+
(($config['memory_cost'] ?? 0) << 10) ?: null,
575+
$config['cost'] ?? null,
576+
\PASSWORD_ARGON2I,
577+
],
578+
];
579+
}
580+
581+
if ('argon2id' === $config['algorithm']) {
582+
if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
583+
return [
584+
'class' => SodiumPasswordEncoder::class,
585+
'arguments' => [
586+
$config['time_cost'] ?? null,
587+
(($config['memory_cost'] ?? 0) << 10) ?: null,
588+
],
589+
];
590+
}
591+
592+
if (!\defined('PASSWORD_ARGON2ID')) {
593+
if (\defined('PASSWORD_ARGON2I')) {
594+
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'));
595+
}
596+
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'));
559597
}
560598

561599
return [
562-
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
600+
'class' => NativePasswordEncoder::class,
563601
'arguments' => [
564-
$config['memory_cost'],
565-
$config['time_cost'],
566-
$config['threads'],
602+
$config['time_cost'] ?? null,
603+
(($config['memory_cost'] ?? 0) << 10) ?: null,
604+
$config['cost'] ?? null,
605+
\PASSWORD_ARGON2ID,
567606
],
568607
];
569608
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
+6-14Lines changed: 6 additions & 14 deletions
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
@@ -377,14 +377,9 @@ public function testEncodersWithLibsodium()
377377
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
378378
}
379379

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

@@ -429,15 +424,12 @@ public function testEncodersWithArgon2i()
429424
'arguments' => [8, 102400, 15],
430425
],
431426
'JMS\FooBundle\Entity\User7' => [
432-
'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
433-
'arguments' => [256, 1, 2],
427+
'class' => $sodium ? SodiumPasswordEncoder::class : NativePasswordEncoder::class,
428+
'arguments' => $sodium ? [256, 1] : [1, 262144, null, \PASSWORD_ARGON2I],
434429
],
435430
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
436431
}
437432

438-
/**
439-
* @group legacy
440-
*/
441433
public function testEncodersWithBCrypt()
442434
{
443435
$container = $this->getContainer('bcrypt_encoder');
@@ -481,8 +473,8 @@ public function testEncodersWithBCrypt()
481473
'arguments' => [8, 102400, 15],
482474
],
483475
'JMS\FooBundle\Entity\User7' => [
484-
'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder',
485-
'arguments' => [15],
476+
'class' => NativePasswordEncoder::class,
477+
'arguments' => [null, null, 15, \PASSWORD_BCRYPT],
486478
],
487479
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
488480
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_encoder.php
-1Lines changed: 0 additions & 1 deletion
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
]);

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_encoder.xml
+1-1Lines changed: 1 addition & 1 deletion
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>

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml
-1Lines changed: 0 additions & 1 deletion
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

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php
+5-16Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand;
1616
use Symfony\Component\Console\Application as ConsoleApplication;
1717
use Symfony\Component\Console\Tester\CommandTester;
18-
use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder;
19-
use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder;
2018
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
2119
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
2220
use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder;
@@ -55,9 +53,6 @@ public function testEncodeNoPasswordNoInteraction()
5553
$this->assertEquals($statusCode, 1);
5654
}
5755

58-
/**
59-
* @group legacy
60-
*/
6156
public function testEncodePasswordBcrypt()
6257
{
6358
$this->setupBcrypt();
@@ -70,18 +65,15 @@ public function testEncodePasswordBcrypt()
7065
$output = $this->passwordEncoderCommandTester->getDisplay();
7166
$this->assertStringContainsString('Password encoding succeeded', $output);
7267

73-
$encoder = new BCryptPasswordEncoder(17);
68+
$encoder = new NativePasswordEncoder(null, null, 17, \PASSWORD_BCRYPT);
7469
preg_match('# Encoded password\s{1,}([\w+\/$.]+={0,2})\s+#', $output, $matches);
7570
$hash = $matches[1];
7671
$this->assertTrue($encoder->isPasswordValid($hash, 'password', null));
7772
}
7873

79-
/**
80-
* @group legacy
81-
*/
8274
public function testEncodePasswordArgon2i()
8375
{
84-
if (!Argon2iPasswordEncoder::isSupported()) {
76+
if (!($sodium = (SodiumPasswordEncoder::isSupported()) && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
8577
$this->markTestSkipped('Argon2i algorithm not available.');
8678
}
8779
$this->setupArgon2i();
@@ -94,8 +86,8 @@ public function testEncodePasswordArgon2i()
9486
$output = $this->passwordEncoderCommandTester->getDisplay();
9587
$this->assertStringContainsString('Password encoding succeeded', $output);
9688

97-
$encoder = new Argon2iPasswordEncoder();
98-
preg_match('# Encoded password\s+(\$argon2id?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches);
89+
$encoder = $sodium ? new SodiumPasswordEncoder() : new NativePasswordEncoder(null, null, null, \PASSWORD_ARGON2I);
90+
preg_match('# Encoded password\s+(\$argon2i?\$[\w,=\$+\/]+={0,2})\s+#', $output, $matches);
9991
$hash = $matches[1];
10092
$this->assertTrue($encoder->isPasswordValid($hash, 'password', null));
10193
}
@@ -195,12 +187,9 @@ public function testEncodePasswordNativeOutput()
195187
$this->assertStringNotContainsString(' Generated salt ', $this->passwordEncoderCommandTester->getDisplay());
196188
}
197189

198-
/**
199-
* @group legacy
200-
*/
201190
public function testEncodePasswordArgon2iOutput()
202191
{
203-
if (!Argon2iPasswordEncoder::isSupported()) {
192+
if (!($sodium = (SodiumPasswordEncoder::isSupported()) && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
204193
$this->markTestSkipped('Argon2i algorithm not available.');
205194
}
206195

‎src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php
+52-8Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Security\Core\Encoder;
1313

14+
use Symfony\Component\Security\Core\Exception\LogicException;
15+
1416
/**
1517
* A generic encoder factory implementation.
1618
*
@@ -114,11 +116,15 @@ private function getEncoderConfigFromAlgorithm(array $config): array
114116
],
115117
];
116118

117-
/* @deprecated since Symfony 4.3 */
118119
case 'bcrypt':
119120
return [
120-
'class' => BCryptPasswordEncoder::class,
121-
'arguments' => [$config['cost']],
121+
'class' => NativePasswordEncoder::class,
122+
'arguments' => [
123+
$config['time_cost'] ?? null,
124+
(($config['memory_cost'] ?? 0) << 10) ?: null,
125+
$config['cost'] ?? null,
126+
\PASSWORD_BCRYPT,
127+
],
122128
];
123129

124130
case 'native':
@@ -140,14 +146,52 @@ private function getEncoderConfigFromAlgorithm(array $config): array
140146
],
141147
];
142148

143-
/* @deprecated since Symfony 4.3 */
144149
case 'argon2i':
150+
if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
151+
return [
152+
'class' => SodiumPasswordEncoder::class,
153+
'arguments' => [
154+
$config['time_cost'] ?? null,
155+
(($config['memory_cost'] ?? 0) << 10) ?: null,
156+
],
157+
];
158+
}
159+
160+
if (!\defined('PASSWORD_ARGON2I')) {
161+
throw new LogicException('Algorithm "argon2i" is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
162+
}
163+
145164
return [
146-
'class' => Argon2iPasswordEncoder::class,
165+
'class' => NativePasswordEncoder::class,
147166
'arguments' => [
148-
$config['memory_cost'],
149-
$config['time_cost'],
150-
$config['threads'],
167+
$config['time_cost'] ?? null,
168+
(($config['memory_cost'] ?? 0) << 10) ?: null,
169+
$config['cost'] ?? null,
170+
\PASSWORD_ARGON2I,
171+
],
172+
];
173+
case 'argon2id':
174+
if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
175+
return [
176+
'class' => SodiumPasswordEncoder::class,
177+
'arguments' => [
178+
$config['time_cost'] ?? null,
179+
(($config['memory_cost'] ?? 0) << 10) ?: null,
180+
],
181+
];
182+
}
183+
184+
if (!\defined('PASSWORD_ARGON2ID')) {
185+
throw new LogicException(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'));
186+
}
187+
188+
return [
189+
'class' => NativePasswordEncoder::class,
190+
'arguments' => [
191+
$config['time_cost'] ?? null,
192+
(($config['memory_cost'] ?? 0) << 10) ?: null,
193+
$config['cost'] ?? null,
194+
\PASSWORD_ARGON2ID,
151195
],
152196
];
153197
}

‎src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
2727
private $algo;
2828
private $options;
2929

30-
public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null)
30+
public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, int $algo = null)
3131
{
3232
$cost = $cost ?? 13;
3333
$opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4);
@@ -45,7 +45,7 @@ public function __construct(int $opsLimit = null, int $memLimit = null, int $cos
4545
throw new \InvalidArgumentException('$cost must be in the range of 4-31.');
4646
}
4747

48-
$this->algo = \defined('PASSWORD_ARGON2I') ? max(PASSWORD_DEFAULT, \defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_ARGON2I) : PASSWORD_DEFAULT;
48+
$this->algo = $algo ?? (\defined('PASSWORD_ARGON2I') ? max(PASSWORD_DEFAULT, \defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_ARGON2I) : PASSWORD_DEFAULT);
4949
$this->options = [
5050
'cost' => $cost,
5151
'time_cost' => $opsLimit,

‎src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ public function testValidation()
5555
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
5656
}
5757

58+
public function testConfiguredAlgorithm()
59+
{
60+
$encoder = new NativePasswordEncoder(null, null, null, \PASSWORD_BCRYPT);
61+
$result = $encoder->encodePassword('password', null);
62+
$this->assertTrue($encoder->isPasswordValid($result, 'password', null));
63+
$this->assertStringStartsWith('$2', $result);
64+
}
65+
5866
public function testCheckPasswordLength()
5967
{
6068
$encoder = new NativePasswordEncoder(null, null, 4);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.