Cada cierto tiempo los medios se hacen eco de ciberataques a plataformas u organizaciones donde se comprometen miles e incluso millones de datos de usuarios. En uno de los casos más mediáticos, el que afectó a la red social LinkedIn, se expusieron más de 164 millones de cuentas de usuarios incluyendo las contraseñas almacenadas de forma insegura (SHA1 sin salt). En otro caso, el de 000Webhost, se expusieron más de 13 millones de cuentas de usuarios y lo más increíble es que entre los datos robados se encontraban las contraseñas en texto plano.

Con estos antecedentes, parece necesario tomarse muy enserio la forma de almacenar la contraseñas en las bases de datos. En este artículo haremos un repaso de las distintas posibilidades con las que contamos, desde la menos aconsejadas y erróneas a las más robustas.

Almacenar las contraseñas en claro

La peor estrategia de todas, sin ninguna duda, consiste en almacenar las contraseñas directamente en claro. En este caso, cualquier intrusión a la base de datos que tenga éxito implica el inmediato conocimiento de la totalidad de las contraseñas de los usuarios de la aplicación, tal y como pasó en el caso anteriormente citado de la empresa de hosting gratuito 000Webhost.

¿Alguna vez has recibido tus credenciales (usuario y contraseña) en tu correo tras registrarte o solicitar la recuperación de tu contraseña? Probablemente tu contraseña se almacena en claro.

contrasñeas en claro email

Existe cierta tendencia a justificar esta situación alegando que el sistema o los propios administradores deben tener acceso a la contraseña para recordarla de forma inmediata a los usuarios en caso de pérdida. Pero la propia definición de contraseña desmonta este argumento, ya que debemos considerarla como una forma de identificación personal que no debe concernir a nadie más, ni siquiera a administradores.

Cifrar las contraseñas

Siguiendo con el argumento anterior y para abordar la recuperación de contraseña de forma segura, se puede llegar a considerar el cifrado simétrico como una solución óptima. En este caso, debemos prestar especial atención al lugar y las condiciones de almacenamiento de la clave de cifrado, porque ¿Qué sentido tendría utilizar el algoritmo de cifrado más complejo conocido si almacenamos la clave de forma insegura? Mantener las claves en secreto se convierte en un nuevo problema.

Pero aun dedicándole especial atención al almacenamiento de la clave, la realidad es que este enfoque sigue tiendo similitudes al primer caso, ya que nos encontramos ante algoritmos reversibles que permiten recuperar la contraseña. Por tanto, aunque es cierto que la contraseña se encuentra cifrada, no eliminamos el riesgo de que alguien sea capaz de obtener la clave de cifrado de algún modo.

"HASHEAR" las contraseñas

A estas alturas del artículo, deberíamos tener claro que recuperar la contraseña de forma directa o mediante algoritmos reversibles no es buena idea. Es hora de pensar en la utilización de algoritmos irreversibles. Es ahí donde entran en escena las funciones hash, que no son más que algoritmos que transforman de forma irreversible un bloque de datos en un conjunto de bytes de tamaño fijo. Para verificar la contraseña de un usuario, se aplica la función hash a la contraseña introducida por el usuario y se comparara el resultado con el hash almacenado en la base de datos.

No obstante, aunque más cerca de ser una solución satisfactoria, esta presenta algunos inconvenientes. En primer lugar, existe el handicap de que una contraseña siempre genera el mismo hash, es decir que dos usuarios con la misma contraseña tendrán almacenado el mismo hash en la base de datos. En segundo lugar, nos encontramos ante algoritmos computacionalmente muy rápidos, lo que se traduce en la capacidad de calcular el hash de un gran número de contraseñas por segundo con una GPU reciente. Ambas circunstancias hacen que estemos expuestos a ataques de diccionario o de fuerza bruta.

Un ataque de diccionario utiliza un fichero que contiene cadenas que pueden ser utilizadas como contraseñas (palabras, frases, contraseñas comunes…). Cada cadena en el fichero es hasheada y comparada con el hash de la contraseña objetivo. Si coinciden, la cadena es la contraseña que buscamos.

Un ataque de fuerza bruta prueba cada combinación posible de caracteres hasta una determinada longitud. Estos ataques son muy costosos desde el punto de vista computacional y, por lo general, son los menos eficientes en términos de hash agrietados por tiempo de procesador, pero siempre encontrarán la contraseña.

Pero aún más preocupante que estos ataques, son los ataques precomputados. Estos ataques consisten en crear una base de datos, como las lookup tables y las rainbow tables, que contiene un par de valores: el hash y su equivalente en texto plano. Este tipo de ataque supone una gran ventaja para el atacante, y es que existen servicios online (como crackstation.net y hashkiller.co.uk) que se dedican a engordar constantemente su base de datos de hashes. De este modo, un usuario puede buscar un hash en busca de su valor en texto plano sin necesidad de invertir tiempo en crackear (bien sea por fuerza o bruta o diccionario) ningún hash.

Añadir un poco de Salt

Las lookup tables y rainbow tables, como hemos mencionado anteriormente, funcionan porque todas las contraseñas son hasheadas del mismo modo. Una forma de prevenir este tipo de ataque consiste en dotar de aleatoriedad a los hashes< generados, de forma que cuando una misma password es hasheada dos veces, los hashes resultantes sean distintos.

Podemos hacer que cada hash sea aleatorio añadiendo una cadena aleatoria a la contraseña antes de aplicar la función de hash. Esta cadena añadida es lo que conocemos por salt. Para comprobar que la contraseña es correcta, el salt suele ser guardado en claro junto al hash en la base de datos. Aunque pueda parecer extraño almacenarlo de este modo, hay que recordar que el único propósito del salt es evitar el uso de base de datos pre-generadas, por lo que no necesita ser cifrado.

Utilizando salt hacemos que las bases de datos de hashes sean inútiles. No obstante, aunque solucionamos un inconveniente importante de las funciones de hash simples, el uso de salt no previene sobre ataques de fuerza bruta o de diccionario.

¿Qué podemos hacer?

Key Stretching: Hazlo lento

Mediante el uso de salt hemos conseguido solucionar el uso de lookup tables y rainbow tables, sin embargo, queda una última piedra en el camino: los ataques de fuerza bruta y diccionario. Para hacer que estos ataques sean menos efectivos, podemos hacer que el proceso de hash sea más lento, haciendo que la función de hash incluya un número alto de iteraciones internas. Esta técnica es conocida como Key stretching.

Pero… ¿Cuantas iteraciones son aconsejables? La respuesta es: cuanto más mejor. El motivo es sencillo, las funciones hash están en constante guerra contra la capacidad de cálculo de hashes. Por tanto, para mejorar la seguridad, se debe configurar ese número tan alto como pueda tolerar el servidor, dadas las funciones que este debe cumplir y sin que el usuario se vea perjudicado.

En definitiva, si queremos almacenar las contraseñas de forma segura debemos incluir en la receta: una función hash, un salt adecuado e iterarlo. Existen funciones hash muy conocidas que cumplen con estos requisitos que podremos utilizar para nuestro propósito: PBKDF2, bcrypt, Argon2… Sin olvidar que, para que todo esto tenga sentido, es necesario implementar una buena política de contraseñas.