Forum PHP 2017

hash_equals

(PHP 5 >= 5.6.0, PHP 7)

hash_equalsComparación de strings segura contra ataques de temporización

Descripción

bool hash_equals ( string $known_string , string $user_string )

Compara dos strings empleando el mismo tiempo, sin importar si son iguales o no.

Esta función debería utilizarse para mitigar los ataques de temporización, por ejemplo, al probar hash de contraseñas de crypt().

Parámetros

known_string

El string de longitud conocida con el que comparar

user_string

El string proporcionado por el usuario

Valores devueltos

Devuelve TRUE cuando los dos strings son iguales, o FALSE si no.

Errores/Excepciones

Emite un mensaje de nivel E_WARNING cuando ninguno de los parámetros proporcionados es un string.

Ejemplos

Ejemplo #1 Ejemplo

<?php
$esperado   
crypt('12345''$2a$07$usesomesillystringforsalt$');
$correcto   crypt('12345''$2a$07$usesomesillystringforsalt$');
$incorrecto crypt('apple',  '$2a$07$usesomesillystringforsalt$');

var_dump(hash_equals($esperado$correcto));
var_dump(hash_equals($esperado$incorrecto));
?>

El resultado del ejemplo sería:

bool(true)
bool(false)

Notas

Nota:

Ambos argumentos deber tener la misma longitud para que se puedan comparar. Cuando se proporcionan argumentos con diferente longitud, se devuelve FALSE inmediatamente, pudiéndose filtrar la longitud del string conocido en caso de un ataque de temporización.

Nota:

Es importante proveer el string proporcionado por el usuario como segundo parámetro, en vez de como el primero.

add a note add a note

User Contributed Notes 5 notes

up
55
asphp at dsgml dot com
2 years 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;
    }
  }
}
?>
up
8
s rotondo90 at gmail com
11 months ago
asphp has done a great job and that one of Markus P. N. is also good too.

However i made my own more concise version of the asphp code that supports different lenght strings and i used the same tests of Markus in order see how it works.

<?php
if(!function_exists('hash_equals')) {
    function
hash_equals($known_string, $user_string) {
       
$ret = 0;
       
        if (
strlen($known_string) !== strlen($user_string)) {
           
$user_string = $known_string;
           
$ret = 1;
        }
       
       
$res = $known_string ^ $user_string;
       
        for (
$i = strlen($res) - 1; $i >= 0; --$i) {
           
$ret |= ord($res[$i]);
        }
       
        return !
$ret;
    }
}
?>
up
20
Markus P. N.
2 years 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;
    }

}

?>
up
8
Cedric Van Bockhaven
2 years 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;
    }
}
?>
up
3
David Grudl
1 year 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