PHP Manual
/
Seguridad

Cadenas y contraseñas de Hashing

11. 09. 2019

El proceso de hashing (a diferencia de la encriptación) produce una salida a partir de la entrada de la que ya no se puede derivar la cadena original.

Por lo tanto, es muy adecuado para proteger cadenas sensibles, contraseñas y sumas de comprobación.

Otra buena característica de las funciones hashing es que siempre generan salidas de la misma longitud, y un pequeño cambio en la entrada siempre cambia por completo la salida.

Funciones de hashing

Hay muchas funciones hash en PHP, las más importantes son:

  • Bcrypt: password_hash() - El hash más seguro para contraseñas, computacionalmente lento, usa la sal interna y hace el hash de forma iterativa.
  • md5() - Función muy rápida adecuada para el hash de archivos. La salida es siempre de 32 caracteres.
  • sha1() - Función hash rápida para el hash de archivos, utilizada internamente por Git para el hash de confirmaciones. La salida es siempre de 40 caracteres.

Hashing

$password = 'contraseña secreta';
echo password_hash($password); // Bcrypt
echo md5($password);
echo sha1($password);

Atención: Ni md5() ni sha1() son adecuados para el hash de contraseñas, porque es computacionalmente fácil descubrir la contraseña original, o al menos precalcular las contraseñas. Es mucho mejor utilizar bcrypt, que fue desarrollado para el hash de contraseñas.

El sitio web md5cracker.com contiene una base de datos de sumas de comprobación (hashes), prueba a buscar el hash: 79c2b46ce2594ecbcb5b73e928345492, como puedes ver, así que el md5() puro no es tan seguro para palabras y contraseñas comunes.

La única solución correcta: Bcrypt + salt.

En la charla Cómo no meter la pata en el plano de destino, David Grudl abordó las formas de hashear y almacenar correctamente las contraseñas.

La única solución correcta es: Bcrypt + salt.

Específicamente:

$password = 'hash';
// Genera un hash seguro
echo password_hash($password, PASSWORD_BCRYPT);
// Alternativamente con mayor complejidad (por defecto es 10):
echo password_hash($password, PASSWORD_BCRYPT, ['costo' => 12]);

La ventaja de Bcrip radica principalmente en su rapidez y en el salado automático.

El hecho de que se tarde mucho en generar, digamos 100 ms, hace que sea muy costoso para un atacante probar muchas contraseñas.

Además, el hash de salida se trata automáticamente con sal aleatoria, lo que significa que cuando se hace hash de la misma contraseña repetidamente, la salida es siempre un hash diferente. Por lo tanto, un atacante no podrá utilizar una tabla hash precalculada.

Por lo tanto, no podremos verificar la exactitud de la contraseña mediante el hash repetido, sino que tendremos que llamar a una función especializada:

if (password_verify($password, $hash)) {
// La contraseña es correcta
} else {
// La contraseña es incorrecta
}

Salado de contraseñas

Para dificultar el descifrado del hash, es una buena idea insertar alguna cadena adicional en la entrada original. Lo ideal sería uno al azar. Este proceso se denomina Salting de la contraseña.

La seguridad se basa en la idea de que un atacante no podrá utilizar una tabla precalculada de contraseñas y hashes, porque no conocerá la sal y tendrá que romper las contraseñas individualmente.

Por ejemplo:

$password = 'pasaporte_secreto';
$salt = 'fghjgtzjhg';
$hash = md5($password . $salt);
echo $password; // imprime la contraseña original
echo $hash; // imprime el hash de la contraseña incluyendo la sal

Funciones hash compuestas

Podrías pensar que sería una buena idea realizar la función hash repetidamente, aumentando así la complejidad de su crackeo, ya que la contraseña original necesitará ser hashada repetidamente.

Por ejemplo:

$password = 'contraseña';
for ($i = 0; $i <= 1000; $i++) {
$password = md5($password);
}
echo $password; // 1000x hashed via md5()

Paradójicamente, la dificultad para abrirse paso se reduce o permanece casi igual.

La razón es que la función md5() es extremadamente rápida y puede calcular más de un millón de hashes por segundo en un ordenador normal, por lo que probar las contraseñas una por una no ralentiza mucho.

La segunda razón es más bien una teoría, a saber, la posibilidad de encontrarse con una supuesta colisión. Si hacemos un hash de una contraseña repetidamente, con el tiempo puede ocurrir que demos con un hash que el atacante ya conoce, y esto le permitirá hacer un hash de la contraseña utilizando la base de datos.

Por lo tanto, es mejor utilizar una función de hashing segura y lenta y realizar el hashing una sola vez, sin dejar de tratar la salida final con salting.

Comparación extremadamente segura de dos hashes/cadenas

¿Sabías que el operador === no es la opción más segura para la comparación de hash en la verificación de contraseñas?

Al comparar cadenas, se recorren las dos cadenas carácter por carácter hasta llegar al final (éxito, son iguales) o hay una diferencia (las cadenas son diferentes).

Y esto puede ser explotado en un ataque. Si se mide el tiempo con suficiente precisión, se puede estimar cuántos caracteres más quedan por añadir para conseguir una coincidencia exacta y llegar al final, o se puede estimar hasta dónde han llegado las cadenas al compararlas.

La solución es utilizar la función hash_equals() en todos los casos en que se comparan las cadenas, y sería importante si un atacante pudiera averiguar la posición en la que falló la comparación.

¿Y cómo lo hace la función? Se asegura de que la comparación de 2 cadenas cualesquiera siempre lleva la misma cantidad de tiempo, por lo que no se puede saber midiendo el tiempo dónde se ha producido la diferencia. Algunos tipos de ataques me parecen realmente muy improbables y difíciles de aplicar. Este es uno de ellos.

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
5.
Status:
All systems normal.
2024