Almacenando contraseñas de forma segura en PHP con Bcrypt/Blowfish - Carlos Santiago | Desarrollador de Aplicaciones Web
avatar

Carlos Santiago

Desarrollador de Aplicaciones Web.


Publicidad

dattahost

Almacenando contraseñas de forma segura en PHP con Bcrypt/Blowfish

Cuando hablamos de seguridad en aplicaciones web y más especificamente de la seguridad en el almacenamiento de contraseñas/password, entramos en un campo donde debemos tener mucho cuidado ya que lo que se encuentra en juego son los datos de los usuarios de nuestra aplicación, por lo que entre otros puntos, el aspecto de la seguridad no debemos tomarlo tan a la ligera. Todo el mundo sabe que el dato más valioso en un sistema son las contraseñas; por lo tanto almacenarlas de forma segura es vital para que tanto los usuarios se sientan seguros al usar nuestra aplicación, como para que tenga éxito la misma.

Con los últimos avances tecnológicos, muchas páginas importantes han sufrido ataques por parte de crackers, los cuales tienen objetivos cuando deciden atacar un sistema y generalmente, por no decir siempre, el objetivo es tomar la base de datos para obtener datos de los usuarios y en particular las contraseñas.

LinkedIn sufrió un ataque terrible el cual le costó el robo de más de 6 millones de contraseñas. LinkedIn guardaba las contraseñas en forma de un Hash SHA-1.

Pero bien, antes de meternos en terreno fuerte y aprender como podemos asegurar este tipo de información en una base de datos, vale la pena comenzar por lo más básico.

Que es Hashing?


Un hash se puede visualizar como una huella digital relacionada a algún dato (sea un archivo, un string, etc). El hash se logra a través de un proceso matemático de una sola vía, esto 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. Pero esa situación no es algo que suceda con frecuencia, es poco probable que eso suceda.

LinkedIn guardaba las contraseñas como un hash SHA-1. Y 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.

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 llamados 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 ni siquiera 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. Hay quienes dicen que un pepper no añade beneficios gigantes a la seguridad de una aplicación, así que no nos enfocaremos mucho en ese punto.

Wordpress por su parte usa una combinación de los 2 (sales y pimientas) para distintas áreas.

Un poco 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 la podemos prevenir, 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 (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.

Vamos a visualizar un poco de código que represente lo que hemos hablado hasta ahora.

Perfecto! Sin embargo, no es suficiente, sobre todo si estamos hablando de almacenamiento de contraseñas.

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 optimo.

Haciendo el bien con Bcrypt/Blowfish


Blowfish es un algoritmo de una sola via que también sirve para generar hashes. 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). 

Puedes probarlo de la siguiente manera:

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 un salt 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. Expliquemos un poco:

$2y$ Indica que vamos a usar BlowFish.
$10$ Indica el costo del cálculo. Puede ir de 04 a 31. En los ejemplos usamos $07$ y $10$, generalmente 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.

Consolidación de Bcrypt


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 mostramos al inicio, 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.

Por último


  • 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 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.
  • 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? Cédulas de ciudadanía? Busca algoritmos de encripción bien establecidos que te permitan proteger y recuperar la información.

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.

Fuente: michael-pratt.com

DEJA UN COMENTARIO

Your email address will not be published. Required fields are marked *

0 COMENTARIOS



© 2017 Carlos Santiago - Todos los derechos reservados.