¿MD5, SHA1, SHA256, SHA3? Olvidalos... bcrypt!


La verdad es que “nacer” en un foro de seguridad, me ha tocado lo suyo y siempre intento meterle seguridad a mis paginas.. desde inputs trampa para los bots hasta site-salts para las bases de datos. Pero aquí llega algo en lo que no me había parado a pensar. ¿De que sirve tanto hasheo ante una maquina potente?

Hoy en día te puedes comprar hardware tan potente que hasta da miedo por algo mas de 2000 euros. Con un buen SLI y muchos CUDA's puedes llegar a probar hasta 600,000 hashs por segundo. ¿De que sirve ahí, un algoritmo hecho para calcular con velocidad?

Indice

¿Por qué no usar sha1, sha512, md5 o parecidas?

Básicamente porque son algoritmos hechos para calcular cantidades grandes de información en el menor tiempo posible. Son muy útiles para asegurar la integridad de la información pero no son “aptos” para guardar contraseñas.

Un servidor moderno puede calcular un hash MD5 de 330mb por segundo. Si un usuario utiliza una contraseña alfanumérica, letras minúsculas y de 6 caracteres de longitud, podrías intentar cualquier posible combinación de ese tipo en alrededor de 40 segundos.

Los salts no ayudan

Hay que mencionar que si un atacante obtiene acceso a la BD los salts automáticamente se vuelven inútiles. Se puede “tapar” un poco utilizando los (como yo los llamo) site-salts que básicamente son salts implementados en el código en vez de en la BD, pero eso no es mas que eso... tapar. Da igual si el salt es único o diferente para cada usuario, si la BD cae, también lo hacen los salts. De este modo, aunque cueste mas tiempo, se puede atacar al hash simplemente añadiendo el salt.

bCrypt

Al rescate viene bcrypt. Usa una variante del Key schedule de Blowfish e introduce un factor de trabajo el cual te permite determinar que tan costoso seria el hecho de generar un hash. En otras palabras, puedes limitar el tiempo en el que tarda en generar un hash.

Gracias a esto, mientras mas rápido es el ordenador que procesa, se puede incrementar mas el factor de trabajo para que tarde mas en calcular el hash.

¿Que tan lento es bcrypt comparado con SHA1?

En realidad depende del factor de trabajo, pero para dar un ejemplo, con un factor de trabajo 12 y un Pentium 4:

SHA1 = 0.00 segundos.
BCRYPT = 0.40332102775574 segundos.

Utilizar bCrypt en PHP

Lo cierto es que es un poco liante. Yo mismo cuando me puse a leer posts, me perdí a medio camino. Cada uno recomendaba una cosa... que si funciones build-in en PHP.. que si librerías, que si métodos propios..

Lo que si que queda claro, es que si queremos usar funciones nativas de PHP, hay que utilizar SI o SI una versión igual o superior a 5.3.0... y dado que se han encontrado errores en dichas versiones, 5.3.7 seria la mínima.

Alternativamente podemos utilizar una librería o framework. Uno de los mas famosos (implementado en proyectos como Wordpress 2.5+ o phpBB3) es PHpass. Otras son la de ZEND y/o PHP-Password-Lib. Pero vamos por partes:

Usando crypt nativo (>=5.3.7) (recomendado)

Desde PHP 5.3.0 se implementa nativamente crypt. Aunque crypt esta presente desde PHP 4, no es hasta 5.3.0 cuando bCrypt se vuelve “utilizable”.

<?php
// El salt para bcrypt debe ser de 22 caracteres base64 (solo [./0-9A-Za-z])
// Esto es un ejemplo; Por favor usa algo mas seguro/aleatorio que sha1(microtime) :)
$salt = substr(base64_encode(sha1(microtime(true), true))), 0, 22);
$salt = str_replace(array('.','='), '', $salt);

// 2a es el selector para el algoritmo bcrypt, lee http://php.net/crypt
// 12 es el factor de trabajo (unos 300ms en un Core i7).
$hash = crypt('foo', '$2a$12$' . $salt);

// Ahora podemos usar el hash generado como argumento para crypt
// ya que incluye $2a$12 y crypt lo detecta automaticamente.
var_dump($hash == crypt('foo', $hash)); // true
var_dump($hash == crypt('bar', $hash)); // false

?>

Fuentes - bcrypt in PHP, PHP: crypt.


Usando password_hash nativo (PHP>=5.5-DEV)

A partir de PHP 5.5 se implementa password_hash. Bastante útil y sencillo de utilizar.

<?php
// Definir el factor de trabajo. Tambien podemos utilizar 'salt'=>salt
// para definir nuestro propio salt
// lee http://php.net/manual/en/function.password-hash.php
$options = array('cost' => 12);
$hash = password_hash('foo', PASSWORD_BCRYPT, $options);

// Para verificar utilizamos password_verify
var_dump(password_verify('foo', $hash)); // true
var_dump(password_verify('bar',$hash)); // false

?>

Fuentes - PHP: password_hash, PHP: password_verify.


Usando PHPASS (terceros) (PHP 5.3*)

Notese que aunque el framework ofrece soporte hasta para PHP 3, bCrypt no se puede utilizar si no se cuenta con PHP 5.3.. a cambio ofrece otros métodos seguros y compatibles.

<?php
require('PasswordHash.php');
// FALSE para no utilizar portable_hash (demostrado no seguro).
$Hasher = new PasswordHash(8, FALSE);
$hash = $Hasher->HashPassword('foo');

// Usamos CheckPassword para verificar.
var_dump($pwdHasher->CheckPassword('foo', $hash)); // true
var_dump($pwdHasher->CheckPassword('bar', $hash)); // false

?>

Fuentes - Pagina oficial.


Usando Zend\Crypt (Zend framework v2)

El framework de Zend también permite usar bCrypt. Bastante fácil y sencillo.

<?php
use Zend\Crypt\Password\Bcrypt;
$bcrypt = new Bcrypt();
// Factor de trabajo 12.
$bcrypt->setCost(12);
// Hash
$hash = $bcrypt->create('foo');

// Validar
var_dump($bcrypt->verify('foo', $hash)); // true
var_dump($bcrypt->verify('bar', $hash)); // false

?>

Fuentes - Documentacion Zend, Fuente y mas info.


Usando PHP-PasswordLib (terceros) (PHP>=5.3.2)

Esta es la librería que recomendaría si no existiese crypt(). Es capaz de generar una serie de hashs (Drupal, Joomla, phpbb, bcrypt..) y esta hecha para ser portable y fácil de usar.

<?php
require_once '/path/to/PasswordLib.phar';
// Muy similar a crypt. 'cost' seria el factor de trabajo, 2a seria bcrypt.
$hash = $lib->createPasswordHash('foo', '$2a$', array('cost' => 12));

var_dump($lib->verifyPasswordHash('foo', $hash)); // true
var_dump($lib->verifyPasswordHash('bar', $hash)); // false

?>

Fuentes - Github


Posibles problemas

La verdad es que el uso de bCrypt puede generar unos cuantos problemas (tanto a nivel código como de seguridad).

Versiones inferiores a PHP 5.3

El metodo crypt en versiones inferiores puede ocasionar muchos problemas. Por ejemplo utilizar un salt al estilo bCrypt en las versiones inferiores puede hacer que crypt calcule el hash en DES en vez de blowfish haciendo lo mucho mas débil.

var_dump(crypt('foo', '$2a$04$thisisasaltthisisasale'));

Usar el prefix equivocado

Cuando se descubrieron los errores y fueron arreglados en PHP 5.3.7, crypt paso a utilizar varios prefix para bCrypt. El de por defecto ($2a$), el “legacy” ($2x$) y el nuevo ($2y$). Así que si vas a utilizar tu script en servers nuevos y con PHP >=5.3.7 quizás deberías utilizar $2y$ en vez de $2a$.

Ataques de timing

Un ataque de timing es cuando un atacante manda muchos request cambiando ligeramente cada uno de ellos. Esto sirve para averiguar cuanto tarda el servidor en procesar la consulta. En ciertos casos podrá medir los tiempos para discernir los resultados de una comparación. (suponiendo que sabe como su input afecta a uno de ellos).

¿DoS? (denial-of-service)

No se si se acuerdan de un reciente caso en el que django tuvo que parchear su codigo para prevenir DoS a base de contraseñas largas.

El problema era que Django no ponía limite a las contraseñas (¿Quien lo haría?). Eso con un algoritmo tipo md5 o sha1 no es gran problema ya que (como mencione) están hechos para calcular el hash a la mayor velocidad posible.. pero que pasa si el algoritmo esta hecho para tardar ?

Resulta que a base de mandar contraseñas de 1mb o mas un servidor corriendo Django podía tardar hasta 1 minuto en calcular el hash... haciendo el DoS perfectamente viable.

La solución de Django fue limitar las contraseñas a 4096 bytes (4Kb). Difícilmente alguien va a utilizar una contraseña tan larga en un sitio web corriente....

Saludos

[arriba]

comments powered byDisqus