Guías de seguridad

Cómo guardar contraseñas con seguridad: la forma correcta de aplicar hash y salt

En el servidor: por qué fallan el texto plano y los hashes simples, el salt por usuario y por qué usar un hash lento (Argon2id, bcrypt).

Publicado 2026-06-27 Actualizado 2026-10-06 9 min de lectura
En esta página

"¿Cómo debería guardar exactamente las contraseñas de los usuarios en la base de datos?" — todo desarrollador se topa con esta pregunta una vez. La respuesta es clara. Aquí va la forma segura de guardarlas, por orden, sin pasos de ataque.

Por qué el texto plano, el cifrado y los hashes simples fallan todos

Asume que la base de datos se filtrará algún día. Cuando ocurra, el daño difiere enormemente según el método de almacenamiento.

  • Texto plano: toda contraseña queda expuesta en el instante en que se filtra. Peor aún, la reutilización de contraseñas encadena la brecha hacia los otros servicios del usuario. El peor caso.
  • Cifrado (reversible): la clave lo revierte — así que si la clave se filtra junto con ello, vuelves al texto plano. Una contraseña nunca necesita leerse de vuelta, así que ser reversible no te aporta nada.
  • Hash simple (MD5/SHA-256): un hash rápido permite a un atacante probar conjeturas a gran velocidad. Las contraseñas comunes caen ante las tablas rainbow y la fuerza bruta.

Cuatro etapas hacia un almacenamiento seguro

Lo más rápido es entenderlo como ir añadiendo una corrección a la vez a un método débil.

1. texto plano: todas las contraseñas legibles si se filtra
↓ hazlo de un solo sentido
2. hash simple: débil ante las tablas rainbow
↓ añade un salt por usuario
3. hash con salt: tablas derrotadas, pero la fuerza bruta sigue siendo rápida
↓ hazlo deliberadamente lento
4. hash con salt + lento (Argon2id / bcrypt): esta es la respuesta
texto plano → hash simple → con salt → hash lento. Solo al final es realmente 'almacenamiento seguro'.

La idea clave: un salt y un hash lento hacen trabajos distintos. Un salt derrota la precomputación (tablas rainbow) y el descifrado masivo por reutilización. Un hash lento reduce la fuerza bruta a un ritmo impracticable. Necesitas ambos antes de que sea seguro (→ qué es el hashing).

En la práctica: qué hacer ahora

1

Usa Argon2id (o bcrypt)

Para sistemas nuevos, haz de Argon2id la primera opción — también puede exigir memoria, lo que resiste la fuerza bruta por GPU. Si prefieres una implementación ampliamente probada, bcrypt es de sobra suficiente en la práctica. Usa la implementación de la biblioteca estándar tal cual para cualquiera de las dos.

2

Deja que la biblioteca genere el salt

bcrypt/Argon2 generan un salt por usuario y lo incrustan dentro de la cadena del hash. No mantienes una columna de salt aparte. Construir a mano MD5(salt + contraseña) es el clásico error que no hay que cometer.

3

Ajusta el coste para tu entorno

Fija el factor de coste de bcrypt, o la memoria/iteraciones/paralelismo de Argon2, tan alto como puedas mientras un inicio de sesión legítimo siga sintiéndose instantáneo. A medida que los servidores se vuelven más rápidos, también los atacantes — revísalo y súbelo cada año.

4

Verifica con la función de comparación estándar

Al iniciar sesión, coteja la entrada con el hash almacenado usando la función de verificación de la biblioteca (la mayoría usa una comparación de tiempo constante, segura ante ataques de temporización). No escribas tu propia igualdad de cadenas.

5

Migra los hashes débiles re-hasheando al iniciar sesión

Si ya guardas MD5/SHA-256, en el momento en que un usuario inicia sesión correctamente, re-hashea con el nuevo esquema y guárdalo. Para los usuarios que no han iniciado sesión, puedes reforzarlos mientras tanto volviendo a aplicar bcrypt sobre el hash existente (un hash en capas).

Errores comunes vs. la implementación correcta

Error común

  • Cifrar las contraseñas para guardarlas (la clave se filtra → se acabó)
  • Guardar MD5(contraseña) o SHA-256(contraseña) directamente
  • Usar un único salt compartido para todos los usuarios
  • Construir a mano hash(salt + contraseña)

Implementación correcta

  • Hash de un solo sentido con Argon2id / bcrypt
  • Un salt por usuario (la biblioteca lo añade automáticamente)
  • Coste tan alto como resulte aceptable, subido periódicamente
  • Usar las funciones estándar de hash/verificación tal cual

Hash frente a cifrado frente a codificación

Estos tres se confunden, pero se diferencian por completo en si se pueden revertir. Solo el hash es aceptable para guardar contraseñas.

HashCifradoCodificación (Base64, etc.)
¿Reversible?No (un solo sentido)Sí, con la claveSí, cualquiera
ClaveNingunaNecesariaNinguna
Uso típicoGuardar contraseñas, detectar manipulacionesMantener confidenciales los datos o el tráficoCambiar el formato de los datos
Para guardar contraseñasÚsalo (salt + hash lento)NoNo (equivale al texto plano)

Ajustes recomendados (OWASP)

Los ajustes mínimos de la OWASP Password Storage Cheat Sheet. Tómalos como un suelo y súbelos según tu servidor.

AlgoritmoAjuste mínimoCuándo usarlo
Argon2id19 MiB de memoria, 2 iteraciones, paralelismo 1Primera opción para sistemas nuevos
scryptN=2^17 (128 MiB), r=8, p=1Cuando Argon2id no está disponible
bcryptCoste 10 o más. Entrada limitada a 72 bytesSistemas existentes, trayectoria probada
PBKDF2-HMAC-SHA256600.000 iteraciones o másCuando se exige cumplir FIPS 140

Revisa también lo que trae tu plataforma. password_hash() de PHP usa bcrypt por defecto (PASSWORD_DEFAULT), y PHP 8.4 subió el coste por defecto de 10 a 12. Django usa PBKDF2 por defecto; instala django[argon2] y pon Argon2 el primero en PASSWORD_HASHERS para cambiar. Django vuelve a aplicar el hash automáticamente a las contraseñas guardadas con un algoritmo antiguo cuando el usuario inicia sesión.

Cómo comprobar tu propio servicio

Normalmente puedes saber el esquema por el comienzo de la cadena del hash guardada. Mira unas pocas filas en un entorno que controles, sin copiar datos de producción a otro sitio.

Aspecto del hashEsquemaQué hacer
$argon2id$v=19$m=…,t=…,p=…$Argon2idComprueba que m, t y p cumplen el mínimo de OWASP
$2y$12$… / $2b$10$…bcrypt (el número es el coste)Súbelo si está por debajo de 10
pbkdf2_sha256$1000000$…PBKDF2 (el número es el número de iteraciones)Comprueba que es al menos 600.000
32 caracteres hexadecimalesMuy probablemente MD5Migra re-hasheando al iniciar sesión
40 / 64 caracteres hexadecimalesMuy probablemente SHA-1 / SHA-256Igual que arriba
La cadena de inicio de sesión tal cualTexto planoMigra primero. Evalúa el impacto si se filtrara

Comprueba también lo siguiente:

  • Las contraseñas introducidas no quedan registradas en los registros de la app o del servidor web, en los informes de errores ni en las herramientas de analítica
  • Si usas bcrypt, has decidido cómo tratar las entradas de más de 72 bytes
  • Las copias de seguridad y las copias de la base de datos hechas para pruebas solo contienen el mismo tipo de hashes

Ejemplo real: guardar en texto plano amplió el daño

En el incidente de Takufile-bin (2019), un acceso no autorizado a un servidor filtró unos 4,8 millones de registros, y las contraseñas de inicio de sesión que había entre ellos estaban guardadas en texto plano. Si se hubieran guardado con un hash lento con salt, el riesgo de que las contraseñas filtradas se usaran directamente en otros servicios habría sido mucho menor. Aunque no puedas evitar toda intrusión, cómo guardas las contraseñas cambia cuánto daño hace una filtración.

La visión de este sitio: para guardar contraseñas, usa la implementación estándar probada

El almacenamiento de contraseñas no es un sitio para ser creativo. Usar las implementaciones estándar de Argon2/bcrypt, ampliamente probadas, es lo más seguro y lo más fácil de operar. Nuestra postura: no innoves aquí. Gasta tu esfuerzo, en cambio, en reducir la dependencia de las contraseñas en general — una autenticación multifactor (MFA) exhaustiva, y un eventual paso a passkeys (sin contraseña). Ni siquiera el hash más fuerte puede salvar una contraseña débil y reutilizada.

Sigue leyendo

Fuentes

FAQ

Q¿No basta con cifrar las contraseñas para guardarlas?
A

No — el cifrado es la herramienta equivocada. El cifrado es reversible con una clave, así que si la clave se filtra, todas las contraseñas vuelven a texto plano. Una contraseña nunca necesita volver a leerse, ni siquiera por ti, así que un 'hash' irreversible es la respuesta correcta. Pero un hash simple no basta; combina un salt por usuario con un hash lento (bcrypt/Argon2).

Q¿bcrypt o Argon2 — cuál usar?
A

Para desarrollo nuevo, Argon2 (Argon2id en especial) es la primera opción: te deja ajustar tanto memoria como cómputo, lo que resiste bien la fuerza bruta por GPU. Si valoras una implementación consolidada y muy probada, bcrypt sigue siendo perfectamente práctico. El punto clave con cualquiera de los dos es usar la implementación de la biblioteca estándar tal cual y no inventarla tú.

QYa guardé contraseñas con MD5/SHA-256. ¿Cómo migro?
A

No puedes reconvertirlas en bloque a texto plano (esa es la idea), así que migra al iniciar sesión. En el momento en que un usuario inicia sesión correctamente, re-hashea la contraseña correcta que acaba de introducir con el nuevo esquema (p. ej. Argon2id) y guárdala. Para los usuarios que no han iniciado sesión, puedes reforzarlos mientras tanto volviendo a aplicar bcrypt sobre el hash existente (un hash en capas).

Q¿Qué diferencia hay entre un salt y un pepper?
A

Un salt es un valor aleatorio distinto para cada usuario que se guarda en la base de datos junto al hash. No necesita ser secreto. Un pepper es un único valor secreto compartido por todos los usuarios, guardado en un sitio distinto de la base de datos (un gestor de secretos o un HSM, por ejemplo). OWASP describe el pepper como una defensa adicional por si solo se filtra la base de datos. El salt es obligatorio; el pepper es un añadido opcional.

Q¿Qué es el límite de 72 bytes de bcrypt?
A

bcrypt solo usa los primeros 72 bytes de su entrada; todo lo que pase del byte 72 se ignora. Los caracteres multibyte (japonés, emojis) ocupan unos 3–4 bytes cada uno, así que el límite llega antes de lo que se piensa. Para sistemas nuevos, elige Argon2id, que no tiene ese límite; si usas bcrypt, fija una longitud máxima de entrada (OWASP).

Q¿Hay que mantener el salt en secreto?
A

No. La función del salt es que la misma contraseña genere un hash distinto para cada usuario, lo que frustra las tablas precalculadas y el descifrado masivo. No necesita ser secreto, y bcrypt y Argon2 lo guardan dentro de la cadena del hash. NIST SP 800-63B-4 exige salts de al menos 32 bits.