Skip to content

Commit d2e5ea6

Browse files
author
Robin Chalas
committed
[Security] Replace Argon2*PasswordEncoder by SodiumPasswordEncoder
This reverts commit dc95a6fec6d44e1dfcd7ab5895d6e68bf71676bb.
1 parent a29cd76 commit d2e5ea6

7 files changed

+153
-222
lines changed

Encoder/Argon2Trait.php

Lines changed: 0 additions & 52 deletions
This file was deleted.

Encoder/Argon2iPasswordEncoder.php

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,39 @@
1111

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

14+
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', Argon2iPasswordEncoder::class, SodiumPasswordEncoder::class), E_USER_DEPRECATED);
15+
1416
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
1517

1618
/**
1719
* Argon2iPasswordEncoder uses the Argon2i hashing algorithm.
1820
*
1921
* @author Zan Baldwin <[email protected]>
2022
* @author Dominik Müller <[email protected]>
23+
*
24+
* @deprecated since Symfony 4.3, use SodiumPasswordEncoder instead
2125
*/
2226
class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface
2327
{
24-
use Argon2Trait;
28+
private $config = [];
29+
30+
/**
31+
* Argon2iPasswordEncoder constructor.
32+
*
33+
* @param int|null $memoryCost memory usage of the algorithm
34+
* @param int|null $timeCost number of iterations
35+
* @param int|null $threads number of parallel threads
36+
*/
37+
public function __construct(int $memoryCost = null, int $timeCost = null, int $threads = null)
38+
{
39+
if (\defined('PASSWORD_ARGON2I')) {
40+
$this->config = [
41+
'memory_cost' => $memoryCost ?? \PASSWORD_ARGON2_DEFAULT_MEMORY_COST,
42+
'time_cost' => $timeCost ?? \PASSWORD_ARGON2_DEFAULT_TIME_COST,
43+
'threads' => $threads ?? \PASSWORD_ARGON2_DEFAULT_THREADS,
44+
];
45+
}
46+
}
2547

2648
public static function isSupported()
2749
{
@@ -46,12 +68,9 @@ public function encodePassword($raw, $salt)
4668
}
4769

4870
if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I')) {
49-
return $this->encodePasswordNative($raw, \PASSWORD_ARGON2I);
50-
} elseif (\function_exists('sodium_crypto_pwhash_str')) {
51-
if (Argon2idPasswordEncoder::isDefaultSodiumAlgorithm()) {
52-
@trigger_error(sprintf('Using "%s" while only the "argon2id" algorithm is supported is deprecated since Symfony 4.3, use "%s" instead.', __CLASS__, Argon2idPasswordEncoder::class), E_USER_DEPRECATED);
53-
}
54-
71+
return $this->encodePasswordNative($raw);
72+
}
73+
if (\function_exists('sodium_crypto_pwhash_str')) {
5574
return $this->encodePasswordSodiumFunction($raw);
5675
}
5776
if (\extension_loaded('libsodium')) {
@@ -66,20 +85,10 @@ public function encodePassword($raw, $salt)
6685
*/
6786
public function isPasswordValid($encoded, $raw, $salt)
6887
{
69-
if ($this->isPasswordTooLong($raw)) {
70-
return false;
71-
}
72-
73-
// If $encoded was created via "sodium_crypto_pwhash_str()", the hashing algorithm may be "argon2id" instead of "argon2i"
74-
if ($isArgon2id = (0 === strpos($encoded, Argon2idPasswordEncoder::HASH_PREFIX))) {
75-
@trigger_error(sprintf('Calling "%s()" with a password hashed using argon2id is deprecated since Symfony 4.3, use "%s" instead.', __METHOD__, Argon2idPasswordEncoder::class), E_USER_DEPRECATED);
76-
}
77-
78-
if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I')) {
79-
// Remove the right part of the OR in 5.0
80-
if (\defined('PASSWORD_ARGON2I') || $isArgon2id && \defined('PASSWORD_ARGON2ID')) {
81-
return password_verify($raw, $encoded);
82-
}
88+
// If $encoded was created via "sodium_crypto_pwhash_str()", the hashing algorithm may be "argon2id" instead of "argon2i".
89+
// In this case, "password_verify()" cannot be used.
90+
if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I') && (false === strpos($encoded, '$argon2id$'))) {
91+
return !$this->isPasswordTooLong($raw) && password_verify($raw, $encoded);
8392
}
8493
if (\function_exists('sodium_crypto_pwhash_str_verify')) {
8594
$valid = !$this->isPasswordTooLong($raw) && \sodium_crypto_pwhash_str_verify($encoded, $raw);
@@ -97,6 +106,23 @@ public function isPasswordValid($encoded, $raw, $salt)
97106
throw new \LogicException('Argon2i algorithm is not supported. Please install the libsodium extension or upgrade to PHP 7.2+.');
98107
}
99108

109+
private function encodePasswordNative($raw)
110+
{
111+
return password_hash($raw, \PASSWORD_ARGON2I, $this->config);
112+
}
113+
114+
private function encodePasswordSodiumFunction($raw)
115+
{
116+
$hash = \sodium_crypto_pwhash_str(
117+
$raw,
118+
\SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
119+
\SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
120+
);
121+
\sodium_memzero($raw);
122+
123+
return $hash;
124+
}
125+
100126
private function encodePasswordSodiumExtension($raw)
101127
{
102128
$hash = \Sodium\crypto_pwhash_str(

Encoder/Argon2idPasswordEncoder.php

Lines changed: 0 additions & 88 deletions
This file was deleted.

Encoder/EncoderFactory.php

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,16 @@ private function getEncoderConfigFromAlgorithm($config)
108108
'arguments' => [$config['cost']],
109109
];
110110

111-
case 'argon2i':
111+
case 'sodium':
112112
return [
113-
'class' => Argon2iPasswordEncoder::class,
114-
'arguments' => [
115-
$config['memory_cost'],
116-
$config['time_cost'],
117-
$config['threads'],
118-
],
113+
'class' => SodiumPasswordEncoder::class,
114+
'arguments' => [],
119115
];
120-
case 'argon2id':
116+
117+
/* @deprecated since Symfony 4.3 */
118+
case 'argon2i':
121119
return [
122-
'class' => Argon2idPasswordEncoder::class,
120+
'class' => Argon2iPasswordEncoder::class,
123121
'arguments' => [
124122
$config['memory_cost'],
125123
$config['time_cost'],

Encoder/SodiumPasswordEncoder.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Core\Encoder;
13+
14+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
15+
use Symfony\Component\Security\Core\Exception\LogicException;
16+
17+
/**
18+
* Hashes passwords using libsodium.
19+
*
20+
* @author Robin Chalas <[email protected]>
21+
* @author Zan Baldwin <[email protected]>
22+
* @author Dominik Müller <[email protected]>
23+
*
24+
* @final
25+
*/
26+
class SodiumPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface
27+
{
28+
public static function isSupported(): bool
29+
{
30+
if (\class_exists('ParagonIE_Sodium_Compat') && \method_exists('ParagonIE_Sodium_Compat', 'crypto_pwhash_is_available')) {
31+
return \ParagonIE_Sodium_Compat::crypto_pwhash_is_available();
32+
}
33+
34+
return \function_exists('sodium_crypto_pwhash_str') || \extension_loaded('libsodium');
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function encodePassword($raw, $salt)
41+
{
42+
if ($this->isPasswordTooLong($raw)) {
43+
throw new BadCredentialsException('Invalid password.');
44+
}
45+
46+
if (\function_exists('sodium_crypto_pwhash_str')) {
47+
return \sodium_crypto_pwhash_str(
48+
$raw,
49+
\SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
50+
\SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
51+
);
52+
}
53+
54+
if (\extension_loaded('libsodium')) {
55+
return \Sodium\crypto_pwhash_str(
56+
$raw,
57+
\Sodium\CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
58+
\Sodium\CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
59+
);
60+
}
61+
62+
throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
public function isPasswordValid($encoded, $raw, $salt)
69+
{
70+
if ($this->isPasswordTooLong($raw)) {
71+
return false;
72+
}
73+
74+
if (\function_exists('sodium_crypto_pwhash_str_verify')) {
75+
return \sodium_crypto_pwhash_str_verify($encoded, $raw);
76+
}
77+
78+
if (\extension_loaded('libsodium')) {
79+
return \Sodium\crypto_pwhash_str_verify($encoded, $raw);
80+
}
81+
82+
throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
83+
}
84+
}

0 commit comments

Comments
 (0)