©
This document uses PHP Chinese website manual Release
(PHP 5 >= 5.6.0)
hash_equals — 可防止时序攻击的字符串比较
$known_string
, string $user_string
)比较两个字符串,无论它们是否相等,本函数的时间消耗是恒定的。
本函数可以用在需要防止时序攻击的字符串比较场景中, 例如,可以用在比较 crypt() 密码哈希值的场景。
known_string
已知长度的、要参与比较的 string
user_string
用户提供的字符串
当两个字符串相等时返回 TRUE
,否则返回 FALSE
。
如果所提供的 2 个参数中任何一个不是字符串,
会导致 E_WARNING
消息。
Example #1 例程
<?php
$expected = crypt ( '12345' , '$2a$07$usesomesillystringforsalt$' );
$correct = crypt ( '12345' , '$2a$07$usesomesillystringforsalt$' );
$incorrect = crypt ( 'apple' , '$2a$07$usesomesillystringforsalt$' );
var_dump ( hash_equals ( $expected , $correct ));
var_dump ( hash_equals ( $expected , $incorrect ));
?>
以上例程会输出:
bool(true) bool(false)
Note:
要想成功进行比较,那么所提供的 2 个参数必须是相同长度的字符串。 如果所提供的字符串长度不同,那么本函数会立即返回
FALSE
, 在时序攻击的场景下,已知字符串的长度可能会被泄露。
Note:
非常重要的一点是,用户提供的字符串必须是第二个参数。
[#1] David Grudl [2015-11-25 17:55:56]
Very short timing attack safe string comparison for PHP < 5.6
<?php
function hash_equals($a, $b) {
return substr_count($a ^ $b, "\0") * 2 === strlen($a . $b);
}
?>
[#2] Cedric Van Bockhaven [2015-04-15 09:01:34]
Our server does not support the hash_equals function. We are using the following snippet which also has support for strings of different length:
<?php
if(!function_exists('hash_equals')) {
function hash_equals($a, $b) {
$ret = strlen($a) ^ strlen($b);
$ret |= array_sum(unpack("C*", $a^$b));
return !$ret;
}
}
?>
[#3] Markus P. N. [2014-09-04 21:46:24]
I don't know why asphp at dsgml dot com got that many downvotes, the function seems to work.
I extended it a bit to support strings of diffent length and to handle errors and ran some tests:
The test results and how to reproduce them: http://pastebin.com/mLMXJeva
The function:
<?php
if (!function_exists('hash_equals')) {
function hash_equals($known_string, $user_string)
{
if (func_num_args() !== 2) {
// handle wrong parameter count as the native implentation
trigger_error('hash_equals() expects exactly 2 parameters, ' . func_num_args() . ' given', E_USER_WARNING);
return null;
}
if (is_string($known_string) !== true) {
trigger_error('hash_equals(): Expected known_string to be a string, ' . gettype($known_string) . ' given', E_USER_WARNING);
return false;
}
$known_string_len = strlen($known_string);
$user_string_type_error = 'hash_equals(): Expected user_string to be a string, ' . gettype($user_string) . ' given'; // prepare wrong type error message now to reduce the impact of string concatenation and the gettype call
if (is_string($user_string) !== true) {
trigger_error($user_string_type_error, E_USER_WARNING);
// prevention of timing attacks might be still possible if we handle $user_string as a string of diffent length (the trigger_error() call increases the execution time a bit)
$user_string_len = strlen($user_string);
$user_string_len = $known_string_len + 1;
} else {
$user_string_len = $known_string_len + 1;
$user_string_len = strlen($user_string);
}
if ($known_string_len !== $user_string_len) {
$res = $known_string ^ $known_string; // use $known_string instead of $user_string to handle strings of diffrent length.
$ret = 1; // set $ret to 1 to make sure false is returned
} else {
$res = $known_string ^ $user_string;
$ret = 0;
}
for ($i = strlen($res) - 1; $i >= 0; $i--) {
$ret |= ord($res[$i]);
}
return $ret === 0;
}
}
?>
[#4] asphp at dsgml dot com [2014-08-28 19:48:47]
To transparently support this function on older versions of PHP use this:
<?php
if(!function_exists('hash_equals')) {
function hash_equals($str1, $str2) {
if(strlen($str1) != strlen($str2)) {
return false;
} else {
$res = $str1 ^ $str2;
$ret = 0;
for($i = strlen($res) - 1; $i >= 0; $i--) $ret |= ord($res[$i]);
return !$ret;
}
}
}
?>