Guardar las contraseñas de los usuarios de forma segura es siempre fuente de dolores de cabeza. |
Querido administrador: ¿Sabes cómo estás almacenando las claves en tu sitio web? ¿Tienes idea del desastre que puede significar que te las roben?
El problema
En cualquier sistema donde haya que dar servicio a usuarios concretos (sistema operativo, base de datos, aplicación web, etc.), normalmente se usan contraseñas para identificarlos. El sistema tiene que almacenar necesariamente en algún soporte las contraseñas para poder verificarlas cada vez que un usuario se autentica. La solución simple es guardar un archivo o una tabla con las contraseñas (en texto claro). Aparentemente esto debería ser seguro, siempre que dicho soporte esté debidamente protegido de accesos no autorizados. Pero en realidad es muy mala idea.
El método de almacenar las contraseñas en claro, que sorprendentemente se sigue usando hoy día muy a menudo, se demuestra inseguro por la simple razón de que proteger un archivo eternamente es imposible. Nunca podemos suponer a priori que el almacén de claves no va a ser comprometido. Lo peor de todo es que un atacante que accediera a las contraseñas en claro, aunque sea por un breve lapso, compromente automáticamente a todos los usuarios y podría suplantar inadvertidamente a cualquiera de ellos en cualquier momento. Si combinamos esto con el hecho de que muchos usuarios usan las mismas contraseñas en diferentes sitios, las consecuencias son catastróficas.
En 2009, el sitio de juegos sociales rockyou.com sufrió un ataque que reveló las contraseñas en claro de 32 millones de usuarios. Aún hoy, en muchos sitios se ofrece la posibilidad de recuperar por email (canal inseguro donde los haya) la misma contraseña que ya usábamos, demostrando que se almacena en claro.
Encriptar las contraseñas
Se puede abordar el problema de la protección del almacén de contraseñas aplicandoles algún algoritmo de encriptación, como los cifrados simétricos. Sin embargo, tampoco es buena idea, porque:
- El atacante conocería la longitud de cada una de las contraseñas.
- El atacante podría saber qué usuarios comparten la misma contraseña, lo que va a facilitar enormemente averiguarla.
- Si se viera comprometida la clave de cifrado, se revelarían automáticamente todas las claves y estaríamos en el mismo problema del almacén en claro.
Uso de las funciones de hash para almacenar contraseñas
Una idea clave para abordar el problema del almacén de contraseñas es darse cuenta de que no es necesario desencriptarlas. Cada vez que el usuario accede podemos volver a encriptar y comparar los valores encriptados. En la práctica no es necesario tener una clave de cifrado, ni siquiera un cifrado propiamente dicho, sino que podemos almacenar valores de hash de cada clave. Las propiedades de las funciones de hash son ideales para este propósito:
- Todas las contraseñas producen un hash del mismo tamaño, por lo que el atacante no puede conocer la longitud de cada clave.
- No hay una clave de cifrado, que pueda revelar automáticamente todas las contraseñas.
- Por definición es muy difícil revertir una función de hash. El atacante necesitaría un enorme esfuerzo computacional para sacar partido del almacén.
El uso del hash para almacenar claves es, por tanto, un salto enorme en seguridad respecto a los anteriores métodos. En las primeras versiones de Unix se utilizaba un enfoque similar, y era tal la confianza en este método, que el almacén de claves era nada menos que un archivo de acceso público (el conocido /etc/passwd). Sin embargo, un análisis más profundo revela dos defectos fundamentales:
- El atacante sigue sabiendo qué usuarios comparten contraseñas, puesto que los valores de hash son idénticos para entradas idénticas.
- El almacén es vulnerable a los ataques de diccionario, es decir, el atacante puede usar la misma función de hash para probar coincidencias con palabras comunes y sus combinaciones.
Echandole "sal" al hash
Los dos inconvenientes anteriores se pueden mitigar añadiendo al sistema un valor aleatorio que se asocia a cada contraseña. A este valor se le suele llamar sal (del inglés salt), y al método resultante hash salado (salted hash). Lo que vamos a hacer es elegir una sal diferente para cada usuario y almacenarla junto al resultado de aplicar la función de hash a la contraseña concatenada con la propia sal. Es decir:
- Elegimos una sal s para la contraseña x.
- Aplicamos la función de hash H tal que h=H(x||s)
- Almacenamos los valores s y h (ya sea juntos o por separado).
El valor de la sal no tiene que ser secreto necesariamente, aunque puede mantenerse aparte en un archivo protegido para añadir un nivel más de seguridad. En versiones modernas de Unix (y sus derivados) es común guardar la sal y el hash en un archivo protegido llamado /etc/shadow.
La consecuencia más directa de este mecanismo es que dos contraseñas iguales no producen hashes iguales, evitando el problema de las contraseñas compartidas. El problema de los ataques de diccionario no se soluciona por completo, dado que atacar una contraseña sigue costando lo mismo (a no ser que guardemos la sal en secreto). Sin embargo, los ataques masivos a todo el almacén de claves se hacen mucho más costosos computacionalmente, dado que el atacante no va a poder usar tablas precalculadas de los hashes (llamadas rainbow tables). Por ejemplo, para hacer una tabla usando una sal de 32 bits tendríamos que calcular y almacenar unos 4.300 millones de hashes (232) por cada entrada del diccionario, en lugar de uno sólo si no hubiera sal.
Cadenas de hashes
El método del hash con sal es el mejor que conocemos para el problema de almacenar contraseñas en el sentido clásico, es decir, que se han de usar múltiples veces para verificar al usuario (por ejemplo cada vez que entra en el sistema). Para sistemas que requieran un nivel de seguridad mayor, se pueden usar también las propiedades del hash para generar contraseñas de un solo uso (lo que se conoce como OTP, one-time password).
Esto se hace eligiendo un valor aleatorio inicial y encadenando la función de hash tantas veces como contraseñas queramos generar. Luego daremos al usuario la lista de los hashes generados (h1, h2, ..., hn) y almacenaremos localmente sólo el último valor hn+1=H(H(H(H(...(x)...))). A partir de ahí, cada vez que el usuario se autentique, le vamos pidiendo sucesivamente las contraseñas en orden inverso y actualizando el valor del hash almacenado. Por ejemplo, si tenemos almacenado el valor h100, hacemos lo siguiente:
- El usuario nos proporciona h99 como clave.
- Calculamos h100=H(h99) y vemos que coincide con el almacenado.
- Guardamos h99 como nuevo valor almacenado.
La siguiente vez que se autentique, el usuario tendrá que proporcionar h98 para entrar. Nunca se usa dos veces la misma contraseña.
Las ventajas son importantes:
- No hay secreto almacenado. Un ataque de diccionario tendría poco sentido y el almacén incluso se puede hacer público.
- Hay menor riesgo de que el usuario revele inadvertidamente su clave al teclearla, o sufra un ataque de key-logger (monitorización del teclado).
- El usuario no puede elegir las contraseñas, por lo que son más aleatorias.
El inconveniente claro es que es un mecanismo molesto para el usuario, que tendrá que llevar consigo una lista de claves. Esto se puede mitigar un poco usando un programa de utilidad que guarde esa lista y vaya proporcionando las claves a medida que se necesitan, o bien utilizando algún tipo de soporte para la clave (por ejemplo una tarjeta inteligente).
No hay comentarios:
Publicar un comentario
Expresa tu opinión respetando a los demás y a unas mínimas reglas del lenguaje castellano.
Nota: solo los miembros de este blog pueden publicar comentarios.