PHP 7.0.6 Released

hash_equals

(PHP 5 >= 5.6.0, PHP 7)

hash_equalsTiming attack safe string comparison

Description

bool hash_equals ( string $known_string , string $user_string )

Compares two strings using the same time whether they're equal or not.

This function should be used to mitigate timing attacks; for instance, when testing crypt() password hashes.

Parameters

known_string

The string of known length to compare against

user_string

The user-supplied string

Return Values

Returns TRUE when the two strings are equal, FALSE otherwise.

Errors/Exceptions

Emits an E_WARNING message when either of the supplied parameters is not a string.

Examples

Example #1 example

<?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));
?>

The above example will output:

bool(true)
bool(false)

Notes

Note:

Both arguments must be of the same length to be compared successfully. When arguments of differing length are supplied, FALSE is returned immediately and the length of the known string may be leaked in case of a timing attack.

Note:

It is important to provide the user-supplied string as the second parameter, rather than the first.

User Contributed Notes

asphp at dsgml dot com
1 year ago
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;
    }
  }
}
?>
Cedric Van Bockhaven
1 year ago
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;
    }
}
?>
Markus P. N.
1 year ago
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')) {

   
/**
     * Timing attack safe string comparison
     *
     * Compares two strings using the same time whether they're equal or not.
     * This function should be used to mitigate timing attacks; for instance, when testing crypt() password hashes.
     *
     * @param string $known_string The string of known length to compare against
     * @param string $user_string The user-supplied string
     * @return boolean Returns TRUE when the two strings are equal, FALSE otherwise.
     */
   
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;
    }

}

?>
David Grudl
5 months ago
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);
}
?>
To Top