Todo lo que debes saber sobre el hash de contraseñas
El software es hackeado, ocurren violaciones de datos, se filtran datos. No es una excepción rara, pero ocurre todo el tiempo. Debemos reconocer que el software no es perfecto. Aquí es donde entra en juego el concepto de defensa en profundidad.
La defensa en profundidad significa que no dependemos de un único mecanismo de seguridad, sino que tenemos múltiples capas de seguridad.
El hash de contraseñas es un excelente ejemplo de defensa en profundidad. Si nuestra base de datos es segura, no necesitamos hash de contraseñas.
Esperamos que sea así y defendemos la base de datos lo mejor posible, pero nos preparamos para el peor de los casos. Nos preparamos para que un atacante obtenga acceso a las credenciales de inicio de sesión de nuestros usuarios.
Entra en juego una idea clave de la privacidad: no tienes que preocuparte por los datos que no tienes. En el caso de las contraseñas, no almacenamos las contraseñas. Ni siquiera una versión cifrada. Almacenamos una versión hash.
De esta manera, el efecto se limita a nuestro servicio. Necesitamos decirles a los usuarios que sus cuentas y datos podrían haber estado expuestos. Empero, al menos nadie usará la misma combinación de nombre de usuario/contraseña para iniciar sesión en sus cuentas bancarias/de Amazon/sociales.
¿Qué es el hash?
Hash es como cocinar. Dada la receta (el algoritmo de hash) y los ingredientes (la contraseña), siempre puedes obtener el mismo resultado. Solo con el resultado (el hash), es prácticamente imposible revertir el proceso (averiguar la contraseña).
Hay funciones de hash no criptográficas y criptográficas. Los criptográficos están diseñados para ser difíciles de calcular. Lo que significa que requieren mucha potencia de CPU/tiempo para aplicarse. Esto es a propósito. Si necesitas unos milisegundos para aplicarlo en tu servidor (débil), es de esperar que el atacante también necesite bastante tiempo para aplicarlo miles de millones de veces en diccionarios enormes. Esto para descifrar las cuentas cifradas con fuerza bruta.
Por qué importa
Hay tantas fugas que es difícil hacer un seguimiento de las fugas.
- 2012: LinkedIn filtró 6.5 millones de contraseñas. Las contraseñas se habían codificado con SHA-1 y no se habían salteado.
- 2013: Adobe filtró 130 millones de contraseñas. Las contraseñas fueron cifradas, no aplicaron hash.
- 2016: LinkedIn fue hackeado y usó un algoritmo de hash débil.
- 2019: 1.2 millones de contraseñas se filtraron a través del sitio porno Luscious.
- 2019: Facebook tenía cientos de millones de contraseñas almacenadas en texto sin formato.
- 2019: Zynga filtró 170 millones de contraseñas. Zynga usaba hashing y salteado.
Puede ver si has sido afectado por una filtración en haveibeenpwned.com
Sal – porque el hash no es suficiente
Cuando se filtran las credenciales, se trata esencialmente de una tabla grande con nombres de usuario y las contraseñas (con suerte) hash. Si aplicate el mismo algoritmo a todas las contraseñas, puedes ver qué usuarios tienen las mismas contraseñas. La información adicional, como los nombres de usuario, el uso de la plataforma u otros ataques, como el phishing, puede generar información sobre esas contraseñas.
Para contrarrestar estos ataques de descifrado de contraseñas, se agrega una cadena a la contraseña. Esta cadena se genera aleatoriamente para cada usuario. La cadena se almacena lado a lado con la contraseña. La única razón para tener esa cadena es hacer que la misma contraseña de texto sin formato tenga diferentes valores hash.
¿Cómo puedo codificar contraseñas?
Calcular una clave a partir de una contraseña que luego se puede almacenar ahora se volvió un poco más complejo. Necesitamos la contraseña, una función de hash, una sal aleatoria por usuario y, a veces, incluso una serie de rondas para hash. Hay muchas posibilidades de equivocarse. Además, ¿qué haces para migrar de una función hash a otra? ¿Qué haces para aumentar el número de rondas a medida que mejora el hardware?
Seguro que no querrás obligar siempre a los usuarios a introducir una nueva contraseña. Deseas poder permitir que los usuarios migren con el tiempo.
Una función de derivación clave implementada en el paquete werkzeug de Pythons con dificultad computacional ajustable como PBKDF2 es tu amiga. Es una función que toma la contraseña, la función hash, la sal, el número de rondas. Devuelve la clave. En la mayoría de los lenguajes de programación tienes dos funciones:
generate_key(password, hash_function, salt_length) -> key
check_key(password, key)
Como ejemplo:
>>> from werkzeug.security import generate_password_hash as gen_key
>>> key = gen_key("foobar", "pbkdf2:sha512:1000", salt_length=8)
>>> key
'pbkdf2:sha512:1000$qc8Q9uqK$4f28daacb10dea6667e00c866607073b7a740817e8c4a267c1cedd05cf36cbdf609b14cf446d73d76819f37a3e0475160d444a4fab39526e72aca611960e4c77'
>>> from werkzeug.security import check_password_hash as check_key
>>> check_key(key, "foobar")
True
Puedes ver que la primera parte del método contiene todos los parámetros necesarios para el método. Esto significa que es fácil de extender. La segunda parte (delimitada por el símbolo del dólar) son los 8 caracteres de la sal. Luego viene la contraseña que a la que se ha aplicado hash con el método y la sal dados.
Hay otras funciones clave derivadas. Lo más notable es scrypt, que no solo fue diseñada para ser exigente con la CPU, sino que también requiere mucha memoria. Para Python, existe passlib
que ofrece muchas funciones hash y funciones de derivación de claves. Sin embargo, no parece muy extendido. En su lugar, puedes crear algo similar por tu cuenta utilizando funciones centrales de Python como hashlib.scrypt
. Una función de derivación clave digna de mención es Argon2.
Errores comunes
Hagamos una lista de verificación. Si eres un desarrollador, espero que puedas marcar las siguientes:
☑ No almaceno contraseñas en texto sin formato.
☑ No uso cifrado para contraseñas.
☑ No uso una función hash no criptográfica (por ejemplo, CRC-32)
☑ No uso una función de hash criptográfica débil (por ejemplo, MD5, SHA-1)
☑ Uso una sal diferente calculada aleatoriamente para cada usuario para calcular los valores hash.
Como usuario, espero que puedas marcar los siguientes puntos:
☑ No reutilizo las contraseñas. Siempre.
☑ No comparto mis contraseñas.
☑ No uso contraseñas débiles.
☑ Me aseguro de no filtrar mis secretos.
☑ Soy consciente del phishing. (Si no es así, aparecerás en una publicación futura aquí😀)
Como desarrollador, puedes evitar algunos errores del usuario mediante una política de contraseñas. Por ejemplo, hacer que sea obligatorio tener al menos 8 caracteres. Tal vez podrías probar la contraseña a través de un simple ataque de diccionario antes de permitirlo. Sin embargo, no establecerías reglas de contraseña.
¿Qué puedo hacer como usuario?
Como usuario, debes utilizar diferentes contraseñas para diferentes servicios. Las contraseñas tampoco deben ser muy débiles (por ejemplo, adivinables). Esta combinación me hace imposible simplemente memorizar. Solo tengo un puñado de contraseñas seguras que memorizo. Por lo demás, necesito usar un administrador de contraseñas. El administrador de contraseñas también puede sugerir contraseñas seguras.
Una contraseña segura tiene una alta entropía. Esto significa:
- Al menos 8 caracteres. Preferimos estar seguros y tener al menos 10 caracteres.
- Un rico conjunto de caracteres (por ejemplo, letras mayúsculas y minúsculas, dígitos, caracteres especiales)
- No es una combinación de solo dos o tres palabras en un diccionario.
También puedes cambiar tus contraseñas con regularidad. Esto asegurará que las personas que tuvieron acceso durante un tiempo sin ser notadas serán bloqueadas nuevamente.