8000 bug #41703 [Security] Restore extension point in MessageDigestPasswor… · symfony/symfony@35bae6b · GitHub
[go: up one dir, main page]

Skip to content

Commit 35bae6b

Browse files
bug #41703 [Security] Restore extension point in MessageDigestPasswordEncoder (derrabus)
This PR was merged into the 5.3 branch. Discussion ---------- [Security] Restore extension point in MessageDigestPasswordEncoder | Q | A | ------------- | --- | Branch? | 5.3 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | #41696 (comment) | License | MIT | Doc PR | N/A Until Symfony 5.2, it was possible to extend `MessageDigestPasswordEncoder` and override the way password and salt are merged. This broke with #39802. I've restored the old logic and added a test case to cover that scenario. Commits ------- 4568876 [Security] Restore extension point in MessageDigestPasswordEncoder
2 parents f8222f7 + 4568876 commit 35bae6b

File tree

3 files changed

+95
-2
lines changed

3 files changed

+95
-2
lines changed

src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Security\Core\Encoder;
1313

1414
use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher;
15+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
1516

1617
trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', MessageDigestPasswordEncoder::class, MessageDigestPasswordHasher::class);
1718

@@ -24,7 +25,10 @@
2425
*/
2526
class MessageDigestPasswordEncoder extends BasePasswordEncoder
2627
{
27-
use LegacyEncoderTrait;
28+
private $algorithm;
29+
private $encodeHashAsBase64;
30+
private $iterations = 1;
31+
private $encodedLength = -1;
2832

2933
/**
3034
* @param string $algorithm The digest algorithm to use
@@ -33,6 +37,51 @@ class MessageDigestPasswordEncoder extends BasePasswordEncoder
3337
*/
3438
public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 5000)
3539
{
36-
$this->hasher = new MessageDigestPasswordHasher($algorithm, $encodeHashAsBase64, $iterations);
40+
$this->algorithm = $algorithm;
41+
$this->encodeHashAsBase64 = $encodeHashAsBase64;
42+
43+
try {
44+
$this->encodedLength = \strlen($this->encodePassword('', 'salt'));
45+
} catch (\LogicException $e) {
46+
// ignore algorithm not supported
47+
}
48+
49+
$this->iterations = $iterations;
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function encodePassword(string $raw, ?string $salt)
56+
{
57+
if ($this->isPasswordTooLong($raw)) {
58+
throw new BadCredentialsException('Invalid password.');
59+
}
60+
61+
if (!\in_array($this->algorithm, hash_algos(), true)) {
62+
throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
63+
}
64+
65+
$salted = $this->mergePasswordAndSalt($raw, $salt);
66+
$digest = hash($this->algorithm, $salted, true);
67+
68+
// "stretch" hash
69+
for ($i = 1; $i < $this->iterations; ++$i) {
70+
$digest = hash($this->algorithm, $digest.$salted, true);
71+
}
72+
73+
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
74+
}
75+
76+
/**
77+
* {@inheritdoc}
78+
*/
79+
public function isPasswordValid(string $encoded, string $raw, ?string $salt)
80+
{
81+
if (\strlen($encoded) !== $this->encodedLength || false !== strpos($encoded, '$')) {
82+
return false;
83+
}
84+
85+
return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
3786
}
3887
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Tests\Encoder\Fixtures;
13+
14+
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
15+
16+
final class MyMessageDigestPasswordEncoder extends MessageDigestPasswordEncoder
17+
{
18+
public function __construct()
19+
{
20+
parent::__construct('sha512', true, 1);
21+
}
22+
23+
protected function mergePasswordAndSalt(string $password, ?string $salt): string
24+
{
25+
return json_encode(['password' => $password, 'salt' => $salt]);
26+
}
27+
28+
protected function demergePasswordAndSalt(string $mergedPasswordSalt): array
29+
{
30+
['password' => $password, 'salt' => $salt] = json_decode($mergedPasswordSalt, true);
31+
32+
return [$password, $salt];
33+
}
34+
}

src/Symfony/Component/Security/Core/Tests/Encoder/MessageDigestPasswordEncoderTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
1616
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
17+
use Symfony\Component\Security\Core\Tests\Encoder\Fixtures\MyMessageDigestPasswordEncoder;
1718

1819
/**
1920
* @group legacy
@@ -60,4 +61,13 @@ public function testCheckPasswordLength()
6061

6162
$this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), 'salt'));
6263
}
64+
65+
public function testCustomEncoder()
66+
{
67+
$encoder = new MyMessageDigestPasswordEncoder();
68+
$encodedPassword = $encoder->encodePassword('p4ssw0rd', 's417');
69+
70+
$this->assertSame(base64_encode(hash('sha512', '{"password":"p4ssw0rd","salt":"s417"}', true)), $encodedPassword);
71+
$this->assertTrue($encoder->isPasswordValid($encodedPassword, 'p4ssw0rd', 's417'));
72+
}
6373
}

0 commit comments

Comments
 (0)
0