Page MenuHomePhabricator

D14025.diff
No OneTemporary

D14025.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -428,6 +428,7 @@
'phutil_get_library_root' => 'moduleutils/moduleutils.php',
'phutil_get_library_root_for_path' => 'moduleutils/moduleutils.php',
'phutil_get_signal_name' => 'future/exec/execx.php',
+ 'phutil_hashes_are_identical' => 'utils/utils.php',
'phutil_implode_html' => 'markup/render.php',
'phutil_ini_decode' => 'utils/utils.php',
'phutil_is_hiphop_runtime' => 'utils/utils.php',
diff --git a/src/utils/__tests__/PhutilUtilsTestCase.php b/src/utils/__tests__/PhutilUtilsTestCase.php
--- a/src/utils/__tests__/PhutilUtilsTestCase.php
+++ b/src/utils/__tests__/PhutilUtilsTestCase.php
@@ -792,4 +792,30 @@
$this->assertTrue(($caught instanceof Exception));
}
+ public function testHashComparisons() {
+ $tests = array(
+ array('1', '12', false),
+ array('0', '0e123', false),
+ array('0e123', '0e124', false),
+ array('', '0', false),
+ array('000', '0e0', false),
+ array('001', '002', false),
+ array('0', '', false),
+ array('987654321', '123456789', false),
+ array('A', 'a', false),
+ array('123456789', '123456789', true),
+ array('hunter42', 'hunter42', true),
+ );
+
+ foreach ($tests as $key => $test) {
+ list($u, $v, $expect) = $test;
+ $actual = phutil_hashes_are_identical($u, $v);
+ $this->assertEqual(
+ $expect,
+ $actual,
+ pht('Test Case: "%s" vs "%s"', $u, $v));
+ }
+ }
+
+
}
diff --git a/src/utils/utils.php b/src/utils/utils.php
--- a/src/utils/utils.php
+++ b/src/utils/utils.php
@@ -1360,3 +1360,50 @@
$regex = '(\A'.$regex.'\z)';
return (bool)preg_match($regex, $path);
}
+
+
+/**
+ * Compare two hashes for equality.
+ *
+ * This function defuses two attacks: timing attacks and type juggling attacks.
+ *
+ * In a timing attack, the attacker observes that strings which match the
+ * secret take slightly longer to fail to match because more characters are
+ * compared. By testing a large number of strings, they can learn the secret
+ * character by character. This defuses timing attacks by always doing the
+ * same amount of work.
+ *
+ * In a type juggling attack, an attacker takes advantage of PHP's type rules
+ * where `"0" == "0e12345"` for any exponent. A portion of of hexadecimal
+ * hashes match this pattern and are vulnerable. This defuses this attack by
+ * performing bytewise character-by-character comparison.
+ *
+ * It is questionable how practical these attacks are, but they are possible
+ * in theory and defusing them is straightforward.
+ *
+ * @param string First hash.
+ * @param string Second hash.
+ * @return bool True if hashes are identical.
+ */
+function phutil_hashes_are_identical($u, $v) {
+ if (!is_string($u)) {
+ throw new Exception(pht('First hash argument must be a string.'));
+ }
+
+ if (!is_string($v)) {
+ throw new Exception(pht('Second hash argument must be a string.'));
+ }
+
+ if (strlen($u) !== strlen($v)) {
+ return false;
+ }
+
+ $len = strlen($v);
+
+ $bits = 0;
+ for ($ii = 0; $ii < $len; $ii++) {
+ $bits |= (ord($u[$ii]) ^ ord($v[$ii]));
+ }
+
+ return ($bits === 0);
+}

File Metadata

Mime Type
text/plain
Expires
Wed, May 8, 10:30 PM (3 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6272506
Default Alt Text
D14025.diff (3 KB)

Event Timeline