Skip to content
This repository was archived by the owner on May 31, 2024. It is now read-only.

Commit eea4a9b

Browse files
committed
minor #11822 [Security] Use hash_equals for constant-time string comparison (again) (dunglas)
This PR was merged into the 2.3 branch. Discussion ---------- [Security] Use hash_equals for constant-time string comparison (again) | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a Use the `hash_equals` function (introduced in PHP 5.6) for timing attack safe string comparison when available. Add in the DocBlock that length will leak (symfony/symfony#11797 (comment)). Commits ------- 3071557 [Security] Add more tests for StringUtils::equals 03bd74b [Security] Use hash_equals for constant-time string comparison
2 parents aee2d20 + 6695a8e commit eea4a9b

File tree

2 files changed

+50
-4
lines changed

2 files changed

+50
-4
lines changed

Core/Util/StringUtils.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ private function __construct() {}
2727
* Compares two strings.
2828
*
2929
* This method implements a constant-time algorithm to compare strings.
30+
* Regardless of the used implementation, it will leak length information.
3031
*
3132
* @param string $knownString The string of known length to compare against
3233
* @param string $userInput The string that the user can control
@@ -35,6 +36,13 @@ private function __construct() {}
3536
*/
3637
public static function equals($knownString, $userInput)
3738
{
39+
$knownString = (string) $knownString;
40+
$userInput = (string) $userInput;
41+
42+
if (function_exists('hash_equals')) {
43+
return hash_equals($knownString, $userInput);
44+
}
45+
3846
$knownLen = strlen($knownString);
3947
$userLen = strlen($userInput);
4048

@@ -45,7 +53,7 @@ public static function equals($knownString, $userInput)
4553
$result = $knownLen - $userLen;
4654

4755
// Note that we ALWAYS iterate over the user-supplied length
48-
// This is to prevent leaking length information
56+
// This is to mitigate leaking length information
4957
for ($i = 0; $i < $userLen; $i++) {
5058
$result |= (ord($knownString[$i]) ^ ord($userInput[$i]));
5159
}

Tests/Core/Util/StringUtilsTest.php

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,49 @@
1313

1414
use Symfony\Component\Security\Core\Util\StringUtils;
1515

16+
/**
17+
* Data from PHP.net's hash_equals tests
18+
*/
1619
class StringUtilsTest extends \PHPUnit_Framework_TestCase
1720
{
18-
public function testEquals()
21+
public function dataProviderTrue()
22+
{
23+
return array(
24+
array('same', 'same'),
25+
array('', ''),
26+
array(123, 123),
27+
array(null, ''),
28+
array(null, null),
29+
);
30+
}
31+
32+
public function dataProviderFalse()
33+
{
34+
return array(
35+
array('not1same', 'not2same'),
36+
array('short', 'longer'),
37+
array('longer', 'short'),
38+
array('', 'notempty'),
39+
array('notempty', ''),
40+
array(123, 'NaN'),
41+
array('NaN', 123),
42+
array(null, 123),
43+
);
44+
}
45+
46+
/**
47+
* @dataProvider dataProviderTrue
48+
*/
49+
public function testEqualsTrue($known, $user)
50+
{
51+
$this->assertTrue(StringUtils::equals($known, $user));
52+
}
53+
54+
/**
55+
* @dataProvider dataProviderFalse
56+
*/
57+
public function testEqualsFalse($known, $user)
1958
{
20-
$this->assertTrue(StringUtils::equals('password', 'password'));
21-
$this->assertFalse(StringUtils::equals('password', 'foo'));
59+
$this->assertFalse(StringUtils::equals($known, $user));
2260
}
2361
}

0 commit comments

Comments
 (0)