Para utilizar todas las funcionalidades que ofrece este sitio, es necesario tener JavaScript habilitado.

De un tiempo para acá algunas páginas importantes han sufrido por el robo de sus contraseñas. Recientemente LinkedIn perdió más de 6 Millones de passwords y a Yahoo le metieron un gol de 450 mil contraseñas. Lo más preocupante de estas fugas es que es posible deducir las contraseñas con relativa facilidad.

LinkedIn guardaba las contraseñas en forma de un Hash SHA-1 y Yahoo por su parte tenía los passwords en texto plano, sin nigún tipo de protección.... eso amigos, no se hace!

Antes de hablar sobre como asegurar ese tipo de información en una base de datos, empezemos por lo más básico....

Que es Hashing?

Un hash (también conocido como digest) se puede visualizar como una huella digital relacionada a algún dato (sea un archivo, un string, etc). El hash se logra atraves de un proceso matemático de una sola vía, lo que quiere decir que es complicado recuperar el dato/texto original a partir del hash. 

SHA y MD5 son algoritmos para calcular hashes, cada uno genera strings con una cantidad fija de caracteres (que depende del algoritmo que se use). Hay muchos más algoritmos, la función hash_hmac en php nos sirve para calcular muchos más.

Ahora, recordemos algo, como los algoritmos para hashear datos, convierten una cantidad arbitraria de datos a un string con una longitúd fija. Eso deja abierta la posibilidad de que 2 strings distintos resulten en un mismo hash. Esa situación no es algo que suceda con frecuencia, de hecho es muy improbable!

LinkedIn guardaba las contraseñas como un hash SHA-1. Cual es el problema? Que pueden usar un ataque de fuerza bruta (o con un diccionario) para dar con el texto que resulte en ese hash. Este es un problema aún más preocupante si la contraseña es muy popular, como por ejemplo '123456'. De ahí la importancia de usar una contraseña segura... pero ese ya es otro tema.

Adicionalmente también existen las tablas arcoíris y lookup tables que también son herramientas que se usan a la hora de crackear hashes.

Salt y Pepper (Sal y Pimienta) 

Para aumentarle la dificultad a los crackers, hay 2 métodos muy comunes que son salting y peppering. En el caso de un salt, La idea es crear un string aleatoreo y añadirlo al password. Ese string nunca lo debe saber el usuario, de hecho el nisiquiera debería saber que existe. El objetivo de este método es aumentar la longitud y complejidad del password original.

Por otro lado un pepper sería otro string aleatoreo, que se guarda generalmente por fuera de la base de datos y se le añade a un salt. Su función principal es generar distintos hashes para distintos dominios/páginas. Personalmente pienso que un pepper no añade beneficios gigantes a la seguridad de una aplicación, asi que no hablaré mucho al respecto.

Si quieres saber de Software en PHP que use sales para la autenticación de usuarios, SMF se me viene a la mente y PHPBB hace algo parecido. Wordpress por su parte usa una combinación de los 2 (sales y pimientas) para distintas áreas.

Algo de Seguridad

Las Tablas Arcoiris y las Lookup Tables, solo funcionan cuando cada password ha sido hasheado de la misma forma. Si 2 usuarios tienen el mismo password, su hash sería el mismo. Esa situación se previene, creando un salt para cada password, cosa que si 2 usuarios usan el mismo password, el resultado serían 2 hashes distintos!

La clave en todo este asunto es nunca reusar un mismo salt, siempre generar uno nuevo y preferiblemente, que tenga una longitúd decente (pongale unos 20 o 32 caracteres). Para generar salts aleatoreos en PHP, lo mejor es usar las funciónes mcrypt_create_iv o openssl_random_pseudo_bytes - nada de mt_rand, rand, uniqid, ni algo inventado por nosotros mismos!

Veamos algo de código, representando lo que hemos leido:

Eso es todo! Sinembargo, no es suficiente, sobretodo si estamos hablando de almacenamiento de contraseñas.

Ahora vivimos en un mundo con procesadores rápidos, GPU y la nube. Actualmente es común alquilar un par de instancias de AWS EC2 y usar todo su poder para crackear hashes de forma relativamente rápida, económica y efectiva!

Y ahora? Quien podrá defendernos? - Las Iteraciones!. SHA es un algoritmo rápido, que usa poca memoria y su ventaja radica en que el cálculo de un hash sale "barato".  Una forma de dificultarle el trabajo a los crackers, es haciendo iteraciones! La idea es convertir un proceso que es barato a algo más costoso:

Hemos mejorado mucho! De hecho esto se parece un poco al bucle interior de PBKDF2! Estamos haciendo 5mil iteraciones, el proceso es un poco más costoso y podríamos decir que estamos en un punto medio decente de seguridad.

Opcionalmente también se podría aumentar el número de iteraciones, para volverlo aún más costoso. Sinembargo, todavía no estamos en el punto optimal.

Haciendo el bien con Bcrypt/Blowfish

Blowfish es un algoritmo de una sola via que también sirve para generar hashes. Mi predicción es que a partir de PHP 5.5 va a ser uno de los más usados, puesto que hay un par de funciones nuevas para hashear contraseñas usando este algoritmo (password_hash).

La forma de probar si lo puedes usar en tu sistema, sería viendo si la constante CRYPT_BLOWFISH esta definida. También es sumamente importante usar una version reciente de PHP (>= 5.3.7), puesto que en versiones anteriores hubo un error de implementación y eso puede resultar en strings extraños.

PHP tiene una función llamada crypt que sirve para generar hashes con distintos algoritmos. El primer parámetro es un string y el segundo es una sal con varios comandos. Veamos los siguientes ejemplos:

Como podemos ver, el segundo parámetro es un poco raro, pero en realidad lo que hay ahí son instrucciones. No quiero entrar en detalles muy técnicos, así que por encima:

  • $2y$ Indica que vamos a usar BlowFish.
  • $10$ Indica el costo del cálculo. Puede ir de 04 a 31. En los ejemplos usé $07$ y $10$, generalmente me parece que 10 puede ser un buen valor.
  • $esteesuntextoaleatoreo$ Indica un salt para el cálculo del algoritmo. Una palabra/frase alfa-numérica de 22 caractéres (por lo menos así es para Blowfish).

Teniendo en cuenta todo lo anterior, un script para registrar un usuario sería más o menos así:

Y para validar que el usuario esta dando una contraseña correcta:

La ventaja de bcrypt es que nos protege de varios ataques como las tablas arcoiris y lookup tables.

La desventaja de este método es que utiliza más recursos. Por eso es importante experimentar con el costo del calculo. Entre más alto sea el valor, mayor tiempo de procesamiento va a necesitar. Eso puede ser complicado si estamos trabajando con una página que reciba millones de visitas. Sinembargo, lo importante aquí es la seguridad, así que lento = bueno.

En este sentido estamos sacrificando un poquito el rendimiento para obtener una buena seguridad.

Unos Númeritos

Para consolidar el argumento de que debemos usar Bcrypt, veamos unos números que encontré en la red.

Alguién creó un cluster de 25 GPU's solamente con el propósito de crackear hashes. Estos fueron sus resultados (usando a-zA-Z0-9~`!@#$%^&*()_+-={}|[]\:";'<>,.?/ como su alfabeto):

  • Para crackear md5($password), le necesitó 9.4 horas para encontrar todas las posibles combinaciones de su alfabeto, para strings con 8 caracteres de longitúd. Estaba generando 180 Billones de resultados por segundo!!
  • sha1($password), 61 Billones de resultados por segundo, 27 horas para encontrar todos los posibles resultados para strings de 8 caracteres de longitud (con su alfabeto).
  • md5crypt($password) (Que se parece un poco a la técnica de iteraciones que mostre al inicio de la entrada, pero en vez de usar SHA512, calcula con MD5), 77 Millones de resultados por segundo, le tomaría 2.5 años encontrar todos las posibles combinaciones de su alfabeto para una longitúd de 8 caracteres.
  • bcrypt con un costo de 5, obtuvo 71mil resultados por segundo. Le tomaría 2700 años encontrar todas las posibles combinaciones para una longitúd de 8 caracteres con su alfabeto.

Que más se puede decir?

En el tintero

Se me quedan algunas cosas en el tintero, voy a tratar de resumirlas brevemente:

  • Si quieres usar Bcrypt en tu sistema de autenticación, las columnas en la base de datos MySQL deberian ser del tipo CHAR(60) o BINARY(60).
  • Además de Bcrypt, también puedes darle un vistazo a PBKDF2.
  • Si vas a usar SHA porfavor usa SHA512 para hashear datos importantes, puesto que tiene más bits de longitúd. También usa la técnica de las iteraciones! Recuerda: nunca uses MD5 o SHA1! Nunca Nunca!
  • Para generar datos aleatoreos, considera usar mcrypt_create_iv o openssl_random_pseudo_bytes.
  • No todos los datos importantes son contraseñas. Que pasa con números de tarjetas de credito? Cedulas de ciudadanía? Busca algoritmos de encripción bien establecidos que te permitan proteger y recuperar la información.
  • Si no te sientes comodo en cuanto a temas de seguridad, usa otras librerías para que te faciliten el trabajo. Recomendadas: Password Compat y PasswordLib.

Algún error en esta entrada? Hazmelo saber en los comentarios! Si tienes problemas viendo el código, puedes ir a https://gist.github.com/mpratt/5671743.

Siendo usuario de varias páginas, espero que el servicio que me ofrezcan sea robusto, eficiente y que mis datos estén seguros contra ataques de terceros.

Por nuestra parte, como usuarios debemos procurar usar contraseñas complejas, para aumentar un poco más nuestra seguridad.

Finalmente, uno como programador tiene la obligación de usar métodos seguros y eficientes para almacenar datos importantes, guardar contraseñas sin protección alguna es una falta de respeto al usuario y al trabajo que desempeñamos.

Comentarios