¿Cómo se deben hashear realmente las contraseñas, y por qué SHA-256 solo no es suficiente?
SHA-256 es un hash rápido y de propósito general pensado para verificar integridad, no para proteger secretos — por eso un atacante puede probar miles de millones de combinaciones por segundo contra él.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo se deben hashear realmente las contraseñas, y por qué SHA-256 solo no es suficiente?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Por qué "simplemente hashealo" no es toda la respuesta
Hashear una contraseña antes de guardarla es lo mínimo indispensable — nadie debería guardar contraseñas en texto plano. Pero "hashealo con SHA-256" es una trampa, porque SHA-256 fue diseñado para ser rápido. Es un hash criptográfico de propósito general pensado para cosas como verificar la integridad de archivos o construir árboles de Merkle, donde la velocidad es una ventaja. Para guardar contraseñas, la velocidad es justo la vulnerabilidad: las GPU y ASIC modernas pueden calcular miles de millones de hashes SHA-256 por segundo, lo que significa que un atacante que roba una base de datos con valores SHA-256(contraseña) puede forzar por fuerza bruta una fracción enorme de ellos — sobre todo cualquiera que no sea perfectamente aleatoria — en horas, no en siglos.
#Dos problemas que SHA-256 solo no resuelve
No trae sal integrada. Si dos usuarios eligen la misma contraseña, SHA-256 puro produce el mismo hash para ambos, que es exactamente lo que permite que funcionen las rainbow tables precomputadas: un atacante construye una única tabla gigante de hash → contraseña común una sola vez, y luego rompe cada fila coincidente en cada base de datos filtrada para siempre. Una sal — un valor aleatorio guardado junto al hash y mezclado en la entrada — hace que cada hash sea único incluso para contraseñas idénticas, lo que mata las rainbow tables y obliga al atacante a volver a forzar contraseñas una por una.
No tiene costo ajustable. Incluso con sal, un hash SHA-256 crudo sigue siendo rápido de calcular, así que un atacante con un hash y una sal filtrados todavía puede probar decenas de miles de millones de intentos por segundo en GPUs comunes. Lo que el almacenamiento de contraseñas realmente necesita es un hash que sea deliberada y ajustablemente lento — e idealmente difícil de paralelizar en hardware especializado — para que el hardware volviéndose más rápido con el tiempo no facilite automáticamente romperlo.
#bcrypt: el estándar maduro
const bcrypt = require('bcrypt');async function registerUser(email, plainPassword) {const SALT_ROUNDS = 12; // factor de costo — súbelo a medida que el hardware mejoraconst passwordHash = await bcrypt.hash(plainPassword, SALT_ROUNDS);// p. ej. "$2b$12$KIXQ2r7z5r1z8N9m1s2H1uJ4h7q..." — versión del algoritmo,// costo, sal y hash quedan empaquetados en esta única cadena de 60 caracteres.await db.users.insert({ email, passwordHash });}async function login(email, plainPassword) {const user = await db.users.findByEmail(email);if (!user) return null; // no reveles si el email existe o noconst valid = await bcrypt.compare(plainPassword, user.passwordHash);return valid ? user : null;}
bcrypt.hash genera internamente una sal aleatoria nueva y la incrusta en la cadena de salida, así que no hay una columna de sal separada que mantener — y no hay forma de olvidarse de salar por accidente.
#argon2id: la mejor práctica actual
const argon2 = require('argon2');async function registerUser(email, plainPassword) {const passwordHash = await argon2.hash(plainPassword, {type: argon2.argon2id, // modo híbrido: resiste tanto cracking por GPU// como ataques de canal lateral por temporizaciónmemoryCost: 19456, // ~19 MB de RAM por intento de hashtimeCost: 2,parallelism: 1,});await db.users.insert({ email, passwordHash });}async function login(email, plainPassword) {const user = await db.users.findByEmail(email);if (!user) return null;try {return (await argon2.verify(user.passwordHash, plainPassword)) ? user : null;} catch {return null; // cadena de hash malformada — falla cerrado}}
La característica distintiva de Argon2 frente a bcrypt es su dureza de memoria (memory-hardness): romperlo rápido requiere no solo cómputo bruto, sino decenas de megabytes de RAM por cada intento en paralelo, que es justo el recurso en el que peor escalan las GPU y los ASIC. Por eso OWASP recomienda hoy argon2id como primera opción para sistemas nuevos, con bcrypt como respaldo sólido y probado en batalla donde argon2 no esté disponible.
#Comparándolos lado a lado
| SHA-256 (solo) | bcrypt | argon2id | |
|---|---|---|---|
| Diseñado para | Verificación de integridad, no secretos | Almacenamiento de contraseñas | Almacenamiento de contraseñas |
| Velocidad cruda | Miles de millones de hashes/seg en una GPU | Deliberadamente lento, rondas ajustables | Deliberadamente lento y duro en memoria |
| Sal | No integrada — fácil de olvidar o reutilizar | Integrada en la salida | Integrada en la salida |
| Resiste cracking por GPU/ASIC | No | Algo | Sí — costo de memoria grande y ajustable |
| Ajustable a medida que mejora el hardware | No | Sí (rounds) | Sí (memoryCost, timeCost, parallelism) |
| Recomendado hoy para contraseñas | No | Sí | Sí — primera opción actual de OWASP |