Introducción: La paradoja moderna de las contraseñas
Vivimos en una época en la que todo tiene que ser instantáneo: la web carga en 100 ms, el scroll es suave como la mantequilla y los usuarios esperan que su sesión se inicie antes de que terminen de teclear la última letra del email.
Y claro, como desarrolladores, nos matamos por reducir el TTFB, por hacer malabares con el cacheo, y por rascar milisegundos al tiempo de respuesta como si de eso dependiera nuestra dignidad profesional.
Pero luego está el login.
Aquí la web o aplicación debería tomarse su tiempo. Pero no, por es pereza ni por mal código. Justo lo contrario.
Porque a veces, si el login va demasiado rápido, lo que debería sonar en tu cabeza es una sirena.
🚨 ¿Seguro que esto está bien implementado?
🚨 ¿Estamos hasheando con un algoritmo lento como bcrypt o Argon2?
🚨 ¿O estamos tirando de MD5 con nostalgia de los 2000?
Sí, amigos, hay una paradoja en todo esto: una app rápida es buena, pero un login demasiado rápido puede ser un síntoma de desastre inminente. Porque en el mundo real, un login robusto tiene que dedicar unos preciosos milisegundos (o incluso segundos) a hacer cosas sucias pero necesarias: comparar hashes, añadir sal, y procesar algoritmos de hashing pensados para ralentizar los ataques por fuerza bruta.
Y eso, aunque moleste al usuario más impaciente, puede evitarte un marrón legendario. De esos que acaban en titulares o en correos a todos los clientes pidiendo disculpas “por una posible filtración de datos”.
Por qué? => Vamos a ello.
🛠 Primero lo primero: Cómo debería guardarse una contraseña (spoiler: no se guarda)
Vamos a empezar por lo básico, porque nunca está de más: una contraseña no se guarda.
👉 Sí, lo repito: «NO-SE-GUARDA». Nunca. Never. Jamás.
Ni en la base de datos, ni en un post-it, ni en una variable de entorno, ni (por favor) en un Excel en el escritorio llamado clientes_2025_FINAL_DEFINITIVO.xlsx
.
❌ Los passwords NO SE GUARDAN. Punto.
¿Pero entonces, cómo demonios sabe mi aplicación si están metiendo el password bien?
Lo que se guarda es un hash. Y no, eso no es una forma moderna de llamarlo. Es otra cosa.
🔐 Vale, ¿Qué es un hash?
Un hash es el resultado de aplicar una función matemática a un texto (en este caso, la contraseña). Y lo bonito es que:
- Para un mismo texto, siempre produce el mismo resultado.
- No se puede revertir. Si tienes una contraseña, puedes calcular su hash, pero si tienes un hash, no puedes calcular la contraseña (en teoría, claro, si no eres el FBI y tienes un ordenador cuántico).
- Cambios mínimos en la entrada dan resultados totalmente diferentes.
Un ejemplo de hash podría ser algo así:
hash("solodani123") → a21c8d4e5f4b7
😵💫 «Ehhh, ¿no lo entiendo… pero entonces, cuando alguien mete su contraseña, ¿cómo demonios sabe la web que es correcta?»
Fácil…
En la base de datos de tu web o aplicación, guardarás algo como esto:
user | password_hash |
dani | a21c8d4e5f4b7 |
julieta | f8783hu7374nf |
Cuando en los campos de login escribes «dani
» y «solodani123
«, la web no compara directamente esa contraseña con lo que hay en la base de datos.
Lo que hace es volver a hashearla, generar otro churro (de nuevo
) y compararlo con el que tenía guardado.a21c8d4e5f4b7
- Si coinciden: 🎉 pa’ dentro.
- Si no: ❌ a tu casa.
Y todo sin haber guardado jamás tu contraseña. ¿No es increíble?
Pues espera, que esto es solo el principio… 😏
😵💫 «Ehhh, vale, pero todavía no lo entiendo, ¿y como es eso de que no se puede revertir?»
Vamos con un ejemplo realísimo sacado de cualquier sobremesa familiar. Pongamos que el hijo de tu cuñao se ha metido a programador y se cree Linus Torvalds. Y en vez de usar bcrypt
como todo el mundo, se monta su propio algoritmo «más seguro», porque «si todo el mundo conoce cómo funciona bcrypt, eso no puede ser bueno».
Entonces se inventa esta función de hash:
function hash(password) {
let sum = 0;
for (let i = 0; i < password.length; i++) {
if (i % 2 === 0) { // solo posiciones pares: 0, 2, 4, ...
sum += parseInt(password[i]);
}
}
return sum;
}
Traducido para los que no son el hijo de tu cuñao, eso de arriba suma los dígitos de las posiciones pares de un password (suponiendo que es un password numérico).
O sea, que hace algo así con las contraseñas:
Password | Saca las posiciones pares | Hash (suma posiciones pares) |
1111 | 1,1 | 2 |
1210 | 2,0 | 2 |
2193 | 1,3 | 4 |
Y hala. Ya tiene su propio algoritmo de hashing. Original, simple, rápido… y tan seguro como una puerta de papel (sigue que te lo explico).
Supongo que ahora ya empiezas a ver porqué un hash no se puede revertir, verdad. Efectivamente, por dos cosas:
- Varios passwords generan el mismo hash.
- Por el camino nos hemos dejado información (las posiciones impares no se tienen en cuenta), así que alguien consiga revertirlo… solo podríamos obtener las posiciones pares…
De esta forma si guardas el hash en la base de datos de tu web y alguien te roba la lista de contraseñas de tus usuarios, no sabrá la contraseña de cada cuenta.
Vale, espera… pero entonces, esto es genial. Ya tenemos seguros los passwords, no?
Uhmmm, no mucho la verdad. Lo explico por si entra el hijo de tu cuñao.
Ahora si tu password es «1111», podrás entrar con el, como ya he explicado, pero si pones en el campo de contraseña «1210» !TAMBIÉN ENTRARÁS! Y si pones «2131» también, y con «2121» y con «3131», y con «4141»…
Ves… por eso no tienes que crear tu propio algoritmo de hash, ni jugar a ser más listo que quién se inventó el bcrypt
.
👉 ¡No crees tu propia seguridad si no sabes donde te metes! Nunca. Never. Jamás.
Ni siquiera después de leer este post enterito.
Los algoritmos públicos como bcrypt, scrypt o Argon2 están precisamente diseñados, revisados y auditados por gente que sabe lo que hace. Así que si alguien te suelta eso de:
«Si usas bcrypt cualquiera puede saber cómo funciona, eso es inseguro…«
Hazle un favor: cierra el portátil, respira hondo, y revoca su acceso a producción.
💣 Y entonces, ¿cómo se crackea una contraseña?
Pues en su modo más básico, la verdad es que no tiene mucha ciencia.
Cogen una lista de palabras (un «diccionario») y van probando. Una por una.
Si el usuario al que quieren suplantar es de esos que dice «bah, si yo no tengo nada importante, a mí qué más me da que me entren», y se pone una contraseña tipo 123456
o password123
, no va a costar mucho.
Ahora bien: si tu contraseña es de las buenas, de esas con letras, números, mayúsculas y esos caracteres especiales que tanto te molestan y que el sistema te obliga a poner… probablemente les lleve más tiempo.
Y aquí es, amigos, donde un login lento puede salvarte de un marrón legendario.
Porque si estás pensando que hay alguien tecleando contraseñas una a una, estás equivocado.
Las contraseñas las prueban bots. Y por si no lo sabías: son rápidos. Muy rápidos.
💡 Si tu sistema responde al intento de login a la velocidad de la luz, la estás cagando pero bien.
Porque cuanto más rápido respondas, más intentos por segundo podrá hacer el bot.
¿La solución?
Retrasa la respuesta intencionadamente. Sí, de verdad. Mételo a propósito.
Y si puedes hacer que el tiempo de respuesta además sea variable y aleatorio, aún mejor. Porque hay otra técnica de atacar contraseñas que se llama timing attack
. Esto te lo explica muy bien este artículo de Wikipedia
Pero y entonces que maldita historia me has contado de los hashes. Si lo hacen uno por uno, ¿que importa el hash?
Si te estás haciendo esta pregunta, es que has estado atento todo el post. Enhorabuena. Pero sigamos, que todo lo bueno, llega a su tiempo. Vamos poquito a poco.
🕵️♂️ Cómo los malos le pueden robar el Facebook a tus clientes
Volvamos a esta tabla y hagámoslo un poco más real. Real como esas implementaciones mal hechas «porque no había tiempo». Más real, pero todavía mal implementado.
Imagina que tienes 1M de usuarios en tu web porque te va genial. En tu base de datos guardas una tabla tal que así, pero con 1M de filas, claro.
user | password_hash (MD5) |
soy@solodani.com | e10adc3949ba59abbe56e057f20f883e |
otrousuario@gmail.com | 482c811da5d5b4bc6d497ffa98491e38 |
. . . | . . . |
Todo correcto, ¿no? ¡Tienes los passwords hasheados! Tranquilidad.
Como estabas muy seguro y querías publicar ya…
- Usaste MD5, que está más muerto que el Flash.
- No usate salt (más sobre eso en este otro artículo 👉 La sal y la pimienta de los passwords).
- Tienes el servidor sin actualizar porque lo harás «mañana».
- Y la contraseña de tu base de datos es
cambiarestoenproduccion
porque te dio pereza cambiarla después de copiar el.env
de local.
Pero hey, no pasa nada, ¿no? ¡Los passwords están hasheados! Nada malo puede pasar 😎
Spoiler: todo puede irse a la mierda.
Ahora llega un hacker —que ni siquiera hace falta que sea bueno— y consigue entrar en tu servidor, accede a tu base de datos y se lleva la tabla con ese 1.000.000 de usuarios y sus hashes.
Shhhh! Tranquilo… están hasheados. Todo controlado. 😎
No tan rápido, crack.
Abre esta web 👉 https://md5decrypt.net/ Copia uno de esos hashes de la tabla (por ejemplo «e10adc3949ba59abbe56e057f20f883e», pegalo y dale «Decrypt».
💥 Boom! Magia.

Acabas de obtener la contraseña real. En texto plano. En segundos.
¿Lo adivinas?
Sí, ahora si tu usuario soy@solodani.com usa la misma contraseña en tu web y en Facebook, adivina quién tiene acceso a ese Facebook.
😱 WTF! Pero… ¿los hashes no eran irreversibles?
Lo son, lo son. No te he mentido. De verdad… nunca lo haría…
Peeero… los hackers no son el hijo de tu cuñao. Son listos. Muy listos.
Lo que hacen es precalcular los hashes de miles (o millones) de contraseñas comunes, y luego comparan los hashes robados con esas tablas ya filtradas y listas para usar. Esto se llama Rainbow Table. Y si hay coincidencia: 🎯 jackpot.
Y como los usuarios somos perezosos y además usamos la misma contraseña en 50 sitios distintos, pues boom 💥… si tu password es malo y te pillan el hash, te pueden robar hasta la cartera.
🔐 Consejos que deberías dar a tus usuarios siempre
🔁 No reutilices contraseñas. Nunca. Si se filtran los hashes de una web que no lo ha hecho bien (por ejemplo la de tu gimnasio), te pueden pillar otras que, aunque lo tengan todo en orden (por ejemplo las de tu banco).
🧠 Como no vas a ser capaz de acordarte de 50 contraseñas difíciles sin apuntarlas, y no está bien apuntarlas en un post-it. Hazte con un gestor de contraseñas. Hay muchos, y hacen lo que deben hacer bien hecho.
🔐 Activa la doble autenticación siempre que el servicio lo permita. Te salvará el culo si tu contraseña se filtra. Los servicios importantes siempre lo tienen (el banco, el email…). Actívalo, me lo agradecerás. Ah, y si es bueno, te ayudará con la doble autenticación.
🎰 Deja ya de usar contraseñas fáciles tipo «verano2025»
🎯 No te pongas preguntas de seguridad si la respuesta está en tu Instagram (¿Cómo se llama mi mascota?)
🔍 7. Mira si tu email ha estado en alguna filtración 👉 https://haveibeenpwned.com. Entra, reza un poco, y si aparece, cambia esa contraseña ya mismo!
🦹♂️ 8. Desconfía de cualquier email que te pida contraseñas. Nadie pide contraseñas. Ni tu banco, ni Netflix, ni el príncipe nigeriano. No la pongas donde no toca.
🧑💻 ¿Y no decías que me ibas a enseñar a implementar un login como Dios manda?
Tranqui, que ya voy.
Hacer un login funcional es fácil. Hacerlo seguro… es otro rollo. Aquí va la checklist que todo dev debería tatuarse en la frente (o al menos en el README):
🔐 Nunca guardes contraseñas. Guarda hashes.
Y no, MD5 no cuenta. SHA1 tampoco. SHA256 sin sal tampoco. Usa bcrypt, scrypt o Argon2.
Son lentos, y eso es bueno. Esa es la gracia.
🧂 Usa sal. Siempre.
Sal aleatoria, única por usuario. Así aunque dos personas tengan la misma contraseña, los hashes serán distintos. Y las rainbow tables se van a freír monas.
Que sí que ya lo se. Que no he explicado qué es el Salt en un hash, pero es porque el post me quedaría kilométrico. Lo explico en este otro 👉 La sal y la pimienta de los passwords
🌶️ Si puedes, añade pepper.
Un valor secreto, fijo y externo a la base de datos (en config, env, etc).
Otro empujoncito de seguridad, por si alguien se lleva la base de datos.
Otra cosa no explicada, muy similar al Salt. Más info 👉 La sal y la pimienta de los passwords
🚫 No inventes tu propio algoritmo de hash.
Jamás. Nunca. Ni aunque se te haya ocurrido uno buenísimo mientras te duchabas.
Usa algoritmos públicos, revisados, auditados y battle-tested.
🧠 Haz que el proceso de login sea intencionadamente lento.
Sí, has leído bien. Añade retrasos.
Evita que un bot pueda probar miles de combinaciones por segundo. Y si puedes, haz que ese tiempo sea aleatorio. Para que no puedan usar timing atacks.
🔐 Aplica 2FA si puedes (y no obligues solo a los admins).
Porque aunque el login en tu web sea seguro, las contraseñas pueden filtrarse igual por otras webs mal implementadas y las contraseñas reutilizadas.
Y cuando eso pase, solo la autenticación en dos pasos salvará la cuenta.
📈 Registra los intentos (ojo, sin contraseña).
Ten trazabilidad. Controla si un usuario ha fallado 20 veces en 2 minutos.
Pero nunca registres lo que ha intentando. Nunca.
🔐 Aplica rate-limiting al endpoint del login.
Y sí, incluso si tienes CAPTCHA.
Throttling por IP, por cuenta, por cookie… pero ponle freno.
👀 Nunca reveles información extra en el error.
Decir «El usuario no existe» o «La contraseña es incorrecta» ayudan la leche a un atacante.
Mejor un error genérico: «Credenciales inválidas». Punto.
🧪 Haz tests de login con intención de romperlo.
Prueba mil veces, con contraseñas raras, emojis, inyecciones…
Si no intentas romper tu login, alguien más lo hará por ti.
🤖 Añade CAPTCHA (pero con cabeza)
No te va a proteger de todo, pero ayuda mucho contra bots básicos que van a saco probando contraseñas.
- Úsalo solo tras varios intentos fallidos, para no machacar la UX desde el principio.
- Si puedes, usa reCAPTCHA v3 o algo invisible: menos fricción, mismo efecto.
- Si usas uno visible, que funcione en móviles y no sea un dolor.
CAPTCHA no es una muralla, pero sí una buena trampa para frenar a los malos más torpes.
Hey! Qué opinas sobre el artículo?