En una frase
Los algoritmos de hashing de contraseñas como bcrypt y Argon2 transforman la contraseña de un usuario en una cadena larga e irreversible de caracteres, diseñada para que su cómputo sea lento y costoso, haciendo que sea dolorosamente difícil para los atacantes crackearlas, incluso si roban tu base de datos de usuarios completa.
El problema que resuelve
En la edad oscura digital, los desarrolladores almacenaban las contraseñas de los usuarios en texto plano. Si un hacker vulneraba la base de datos, obtenía un archivo con cada nombre de usuario y su contraseña correspondiente. Game over. Te puedes imaginar la masacre.
El primer avance fue el "hashing". En lugar de almacenar P@ssword123, almacenabas su hash: una cadena de longitud fija generada por un algoritmo como MD5 o SHA-1. Cuando el usuario iniciaba sesión, generabas el hash de la contraseña que ingresaba y comprobabas si coincidía con el hash almacenado. Esto era mejor, pero tenía un fallo fatal: las tablas arcoíris (rainbow tables). Dado que una función de hash siempre produce la misma salida para la misma entrada (por ejemplo, password siempre genera el hash 5f4dcc3b5aa765d61d8327deb882cf99 con MD5), los atacantes precalculaban los hashes de miles de millones de contraseñas comunes y los almacenaban en una tabla de consulta gigante. Un hash robado podía revertirse a su contraseña original en milisegundos.
La siguiente evolución fue el "salting". Antes de hashear la contraseña, le agregas una cadena aleatoria y única llamada "salt". Así que en lugar de hashear P@ssword123, hasheabas P@ssword123 + aBcDeFg12345. El salt se almacena junto con el hash en la base de datos. Esto neutraliza por completo las tablas arcoíris, porque ahora el hash de cada usuario es único, incluso si todos eligieron la contraseña "password". Un atacante tendría que generar una nueva tabla arcoíris para el salt único de cada usuario, lo cual es computacionalmente inviable.
Pero un nuevo villano entró en escena: el poder de cómputo bruto. Las GPUs modernas y el hardware personalizado (ASICs) pueden probar miles de millones de combinaciones de contraseñas con salt por segundo contra un único hash robado. Los algoritmos de hashing como SHA-256 son rápidos por diseño, lo cual es genial para verificar la integridad de archivos, pero terrible para proteger contraseñas.
Este es el problema para el que nacieron bcrypt y Argon2. Son funciones de hashing de contraseñas (PHFs, por sus siglas en inglés), una clase especial de algoritmo de hash que es deliberadamente, dolorosamente lenta y consumidora de recursos. Hacen que verificar incluso una sola contraseña sea tan costoso que la fuerza bruta en una base de datos robada se convierte en una pesadilla económica y temporal para el atacante.
Cómo funciona por dentro
La magia detrás del hashing de contraseñas moderno no reside solo en el algoritmo; se trata de hacer que ese algoritmo realice una cantidad configurable de "trabajo". Esto a menudo se llama "key stretching".
La idea central: Costo adaptativo
Tanto bcrypt como Argon2 tienen factores de "costo" o "trabajo" ajustables. Este es su superpoder. Es un número que estableces que dicta cuán lento y exigente en recursos será el proceso de hashing. La idea es ajustar este costo para que hashear una contraseña en tu servidor tome una cantidad de tiempo razonable (digamos, 100-500 milisegundos), lo suficientemente lento como para frustrar a un atacante, pero lo suficientemente rápido como para no molestar a tus usuarios.
A medida que las computadoras se vuelven más rápidas según la Ley de Moore, simplemente puedes aumentar el factor de costo cada pocos años para mantener el mismo nivel de seguridad. Tu "rutina de ejercicio" de hashing se vuelve más dura a medida que el hardware del atacante se vuelve más fuerte.
Bcrypt: El veterano de confianza
Bcrypt, introducido en 1999, se basa en el cifrado Blowfish. Su principal defensa contra los ataques de fuerza bruta es que no solo es computacionalmente intensivo, sino también intensivo en memoria.
El algoritmo procesa repetidamente la contraseña y el salt, pero una parte clave de su estado interno se almacena de una manera que requiere acceso frecuente a un bloque de RAM. Mientras que las CPUs son buenas en esto, las GPUs —el arma preferida del atacante— prosperan con cálculos simples y paralelizables con un mínimo acceso a la memoria. Al forzar al proceso a recurrir constantemente a la memoria, bcrypt le pone un palo en la rueda a las plataformas de crackeo basadas en GPU.
Una cadena de hash de bcrypt típica se ve así:
$2b$12$J9yLsf9dTOZiCEpT1zO97uF2Yp3uG/yG4Q6V.C1rCg8u6d5qO5GqS
Vamos a desglosarlo:
| Parte | Ejemplo | Significado |
|---|---|---|
| Algoritmo | $2b |
Identifica el algoritmo bcrypt y su versión. |
| Factor de Costo | 12 |
El factor de trabajo. El número de iteraciones es 212. Aumentarlo en 1 duplica el tiempo. |
| Salt y Hash | J9yLsf...d5qO5GqS |
Los primeros 22 caracteres son el salt codificado en Base64. El resto es el digest del hash de la contraseña. |
Fundamentalmente, toda la información necesaria para verificar una contraseña —el algoritmo, el costo y el salt— se almacena ahí mismo en una única y conveniente cadena.
Argon2: El campeón moderno
Argon2 fue el ganador de la Password Hashing Competition (PHC) en 2015, un esfuerzo de varios años para encontrar un nuevo algoritmo de hashing de contraseñas estandarizado. Está diseñado para ser aún más resistente a los ataques de hardware modernos que bcrypt.
Ofrece tres palancas distintas para ajustar su costo:
- Costo de memoria (m): La cantidad de memoria (en KiB) que utilizará el algoritmo. Esta es su principal defensa contra la aceleración por hardware, ya que se puede ajustar para requerir más memoria de la disponible en un solo núcleo de GPU.
- Costo de tiempo (t): El número de iteraciones, o pasadas, que el algoritmo realiza sobre la memoria. Esto aumenta el tiempo total de cómputo.
- Paralelismo (p): El número de hilos o "carriles" que se pueden utilizar. Esto permite que el algoritmo aproveche las CPUs multinúcleo para el hashing, pero también significa que un atacante puede usar el mismo paralelismo. Es un parámetro de equilibrio.
La variante recomendada es Argon2id, que es un híbrido de otras dos versiones. Proporciona una resistencia equilibrada tanto contra ataques de canal lateral (donde un atacante analiza efectos del sistema como el consumo de energía) como contra ataques de crackeo con GPU.
Una cadena de hash de Argon2id es igualmente autocontenida:
$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTIT0T7cKLfgflAIk124
| Parte | Ejemplo | Significado |
|---|---|---|
| Algoritmo | $argon2id |
Identifica el algoritmo Argon2id. |
| Versión | v=19 |
El número de versión del algoritmo. |
| Parámetros | m=65536,t=3,p=4 |
Los costos de memoria, tiempo y paralelismo. |
| Salt | c29tZXNhbHQ |
El salt aleatorio codificado en Base64. (Esto se decodifica como "somesalt"). |
| Hash | Rdescudv...flAIk124 |
El digest del hash de la contraseña codificado en Base64. |
El proceso de verificación
Esta es la parte bonita. Nunca se descifra un hash. La verificación es un camino de una sola vía.
- Un usuario intenta iniciar sesión con
P@ssword123. - Tu aplicación recupera su cadena de hash completa de la base de datos (por ejemplo, la cadena Argon2id de la tabla anterior).
- Tu código parsea esa cadena, extrayendo el algoritmo (
argon2id), la versión (19), los factores de costo (m=65536, t=3, p=4), y el salt (c29tZXNhbHQ). - Luego, toma la contraseña que el usuario acaba de ingresar (
P@ssword123) y la hashea usando esos mismos parámetros exactos. - Finalmente, compara el hash recién generado con la parte del hash de la cadena de la base de datos. Si son idénticos, la contraseña es correcta. Si no, se deniega el acceso.
Historias del mundo real
La brecha de LinkedIn en 2012: Una lección sobre la velocidad
En 2012, LinkedIn sufrió una brecha masiva, y más de 6.5 millones de hashes de contraseñas de usuarios se filtraron en un foro de hackers ruso. ¿El problema? Los hashes eran SHA-1 sin salt. En cuestión de horas, tanto entusiastas de la seguridad como black hats comenzaron una carrera para crackearlos. Las listas de contraseñas crackeadas con éxito crecían por minuto. Debido a que los hashes eran "rápidos" y no tenían salt, todo lo que se necesitó fue una única y masiva tabla arcoíris. El daño reputacional fue inmenso.
Lección: Un hash rápido y sin salt es apenas mejor que el texto plano. La brecha de LinkedIn fue una enorme llamada de atención para la industria que impulsó a innumerables empresas a adoptar finalmente el hashing lento y con salt como bcrypt.
El e-commerce que ganó tiempo
Un desarrollador astuto en una startup, "CircuitCraze", insistió en usar Argon2id para su nueva base de datos de clientes, ajustando los parámetros para que cada inicio de sesión tardara unos 300ms. Un año después, un bucket de almacenamiento en la nube mal configurado expuso una copia de seguridad de la base de datos. La tabla users, con todos sus hashes de Argon2id, fue descargada por un atacante. El equipo forzó inmediatamente un reseteo de contraseñas global. El atacante, enfrentado a la perspectiva de gastar miles de dólares en computación en la nube solo para crackear un puñado de contraseñas de un conjunto de datos ahora inútil, simplemente se rindió y pasó a objetivos más fáciles.
Lección: Un hashing de contraseñas robusto es tu principal defensa después de una brecha. No previene la brecha, pero convierte una fuga de datos catastrófica en un incidente manejable al darte un tiempo crucial para reaccionar.
El aumento gradual del factor de costo
Un equipo lanzó una nueva aplicación web en 2015, eligiendo sabiamente bcrypt con un factor de costo de 10. Funcionó genial. Avancemos hasta hoy. La Ley de Moore ha hecho de las suyas, y un factor de costo de 10 ahora se considera débil. Una auditoría de seguridad lo marca como un riesgo de alta prioridad. El equipo ahora se enfrenta a un dilema: ¿cómo mejoran la seguridad para millones de usuarios sin forzar un reseteo masivo de contraseñas? La solución elegante: la próxima vez que un usuario inicie sesión, el sistema verifica su contraseña contra el antiguo hash de costo 10. Si es correcta, inmediatamente vuelve a hashear la misma contraseña con un nuevo costo de 13 y reemplaza el antiguo hash en la base de datos.
Lección: La seguridad de las contraseñas no es algo que se configura y se olvida. Los factores de costo son una configuración viva que debe revisarse y aumentarse periódicamente para mantener el ritmo de las capacidades de los atacantes.
Errores y trampas comunes
- Usar un hash rápido. No lo hagas. MD5, SHA-1, SHA-256, SHA-512... son para checksums, no para contraseñas. Están diseñados para ser rápidos, que es exactamente lo que no quieres.
- Implementar tu propia criptografía. Nunca intentes inventar tu propio esquema de hashing, como
sha512(password + salt). Usa una librería estándar, revisada por pares y probada en batalla para bcrypt o Argon2 que lo haga correctamente. - Usar un salt estático o reutilizado. Un salt debe ser criptográficamente aleatorio y único para cada contraseña que almacenes. Almacenar un salt único junto a cada hash es la práctica correcta y estándar.
- Establecer el costo demasiado bajo. Un factor de costo de bcrypt por debajo de 12 (en 2024) o parámetros débiles de Argon2 anula todo el propósito. Solo le estás dando al atacante un día fácil. Ajústalo para que sea tan lento como puedas tolerar.
- Establecer el costo demasiado alto. Si el hashing tarda 10 segundos, tu formulario de inicio de sesión expirará y se sentirá roto. Peor aún, expones tu servidor a un ataque de Denegación de Servicio (DoS) donde alguien puede llevar tus CPUs al límite simplemente publicando repetidamente en el formulario de inicio de sesión.
- Almacenar solo el digest del hash. Las cadenas generadas por bcrypt y Argon2 contienen el algoritmo, los costos y el salt por una razón. Debes almacenar esa cadena completa (por ejemplo, en una columna
VARCHAR(255)) para poder verificar las contraseñas en el futuro.
Por qué debe estar en tu radar
Si tu trabajo implica construir cualquier cosa que tenga un formulario de login, necesitas entender esto.
- Desarrolladores Backend y Full-Stack: Esta es tu responsabilidad directa. Si te equivocas en esto, pones en riesgo a cada uno de tus usuarios.
- Ingenieros de Seguridad y DevOps: Ustedes son los que establecen las políticas, realizan auditorías y gestionan la infraestructura. Necesitan saber cómo es un "buen" trabajo y cómo probarlo.
- Desarrolladores Frontend: Entender que un inicio de sesión seguro puede tardar unos cientos de milisegundos explica por qué necesitas proporcionar feedback al usuario (como un spinner de carga) para evitar que hagan clic con furia en el botón "Iniciar sesión".
En resumen, si las palabras "usuario", "cuenta" o "contraseña" aparecen en los requisitos de tu proyecto, el hashing de contraseñas es un concepto fundamental que no puedes permitirte ignorar.
Profundiza más
- OWASP Password Storage Cheat Sheet — La guía esencial y autorizada para desarrolladores.
- Argon2 Specification — El paper formal que describe cómo funciona Argon2.
- How To Safely Store A Password by Coda Hale — Un post clásico de 2010 que le explicó el "porqué" de bcrypt a toda una generación de desarrolladores.
- Bcrypt on Wikipedia — Un buen resumen de la historia y el diseño del algoritmo.
- Argon2 on Wikipedia — Contexto sobre la Password Hashing Competition y el diseño de Argon2.
- Password Hashing Competition — El sitio oficial de la competencia que coronó a Argon2.