Authentication

Hashing de Contraseñas

Las contraseñas son el único secreto que nunca deberías almacenar. Un hash lento y con salt convierte una base de datos robada en un montón de conjeturas que nunca terminan.

intermediate14 min readUpdated 20 sept 2026
passwords.js
js
// passwords.js
import { hash, verify } from "@node-rs/argon2";

export const hashPassword = (password) => hash(password);

export async function verifyPassword(password, stored) {
  try {
    return await verify(stored, password);
  } catch {
    return false;
  }
}
Regla
Haz hash, nunca cifres
Salt
Único por contraseña
Recomendado
Argon2id
Aún común
bcrypt
Comparación
Timing-safe
Al iniciar sesión
Rehash si es necesario

Por que importa

Por qué el hashing no es opcional

Unidireccional por diseño

Un hash de contraseña no se puede revertir, por lo que una tabla filtrada no revela texto plano y el atacante se ve obligado a realizar conjeturas offline.

Los salts derrotan la precomputación

Un salt aleatorio y único por contraseña hace que las rainbow tables sean inútiles y garantiza que dos contraseñas idénticas generen hashes diferentes.

El costo es el objetivo

Un factor de trabajo hace que cada intento sea lo suficientemente lento como para que la fuerza bruta sea impracticable, y puede aumentarse a medida que el hardware mejora.

La imagen completa

Las tres ideas detrás de las credenciales seguras

Haz hash, nunca cifres; usa salt en cada digest; y haz que cada intento sea deliberadamente costoso.

Hash

Transformar

Una función de derivación de claves convierte la contraseña en un digest de longitud fija que no se puede revertir.

Salt

Aleatorizar

Un valor aleatorio almacenado junto al digest hace que cada hash sea único y anula las tablas precomputadas.

Verificar

Comparar

Al iniciar sesión, se vuelve a derivar y se compara en tiempo constante, actualizando el hash si los parámetros están obsoletos.

HTML5 de un vistazo

Cómo es un almacenamiento de credenciales correcto

Argon2id

El estándar moderno, con memoria, iteraciones y paralelismo ajustables.

bcrypt

Probado en batalla e integrado en la mayoría de los stacks, con un factor de costo y un límite de 72 bytes.

scrypt

Memory-hard y estandarizado, una buena opción cuando Argon2 no está disponible.

Salt

Aleatorio por contraseña y almacenado en la misma cadena que el digest.

Comparación timing-safe

Compara digests sin filtrar información a través de una salida anticipada.

Rehash al iniciar sesión

Aumenta el costo o cambia el algoritmo mientras los usuarios inician sesión.

Una breve historia

De crypt a los hashes memory-hard

  1. 1979

    crypt y DES

    Los primeros sistemas Unix hacían hash de contraseñas con una variante de DES de 25 rondas y un salt de 12 bits.

    79
  2. 1999

    bcrypt

    Provos y Mazieres introducen un hash adaptativo basado en Blowfish con un costo ajustable.

    99
  3. 2012

    scrypt

    Se publica una función memory-hard para hacer que el cracking paralelo a gran escala sea costoso.

    12
  4. 2015

    Argon2 gana la PHC

    Argon2id es seleccionado como el ganador de la Password Hashing Competition.

    15
  5. Hoy

    Adaptativo por defecto

    Los frameworks incluyen Argon2 o bcrypt y esperan que ajustes el factor de trabajo.

    Hoy

La guia completa

Hashing de Contraseñas: Todo lo que necesitas saber

Por qué las contraseñas requieren un manejo especial

Cualquier otro secreto de tu sistema se puede rotar: API keys, tokens, certificados. Las contraseñas son diferentes. Nunca las posees en un formato que puedas regenerar, los usuarios las reutilizan en distintos sitios y, en el momento en que tu base de datos se filtra, el atacante tiene todo el tiempo del mundo para intentar adivinarlas offline.

El hashing de contraseñas es la práctica de almacenar una transformación unidireccional de la contraseña en lugar de la contraseña misma. Cuando un usuario inicia sesión, aplicas la misma transformación y comparas los resultados. Tú nunca conoces la contraseña, y tampoco lo hace quien robe la tabla.

Este no es lugar para intentar ser ingenioso. Las reglas son sencillas y están bien establecidas, y romperlas es lo que hace que las brechas de seguridad se vuelvan catastróficas.

El hashing no es cifrado

El cifrado es reversible. Si cifras las contraseñas, la clave reside en algún lugar de tu sistema, y cualquier persona que la obtenga —ya sea mediante una filtración, un backup, una variable de entorno mal configurada o alguien interno— podrá leer todas las contraseñas a la vez. No existe ningún escenario en el que necesites la contraseña original, por lo que la reversibilidad es un riesgo puro.

Una función de hash toma una entrada y produce un digest de longitud fija, y no hay una forma práctica de volver atrás. Para las contraseñas, un hash simple sigue sin ser suficiente: las funciones rápidas como SHA-256 están diseñadas para ser veloces, que es exactamente lo que busca un atacante. Una GPU puede probar miles de millones de candidatos por segundo.

Necesitas una función que sea deliberadamente lenta y memory-hard, para que intentar adivinar las contraseñas sea costoso a gran escala.

Salt: la misma contraseña, un hash diferente

Sin un salt, dos usuarios con la contraseña hunter2 producen el mismo digest. Un atacante puede precomputar una tabla gigante de contraseñas comunes y sus hashes —una rainbow table— y buscar coincidencias al instante. También pueden ver de un vistazo qué cuentas comparten la misma contraseña.

Un salt es un valor aleatorio único generado por cada contraseña y almacenado junto al digest. No es secreto; su única función es hacer que cada hash sea único. Ahora, el atacante debe atacar cada cuenta por separado y la precomputación resulta inútil.

Anatomy of a stored Argon2 hash
$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$aBc...
algorithm + paramsalgorithm, version and work factors
saltunique per password, not secret
digestthe derived key you compare against

Las librerías modernas generan el salt por ti y lo codifican, junto con los parámetros, en una sola cadena de texto. Tú simplemente almacenas esa cadena tal cual.

Cómo elegir un algoritmo

Hay tres funciones que vale la pena conocer. Todas son adaptativas: el coste puede aumentarse a medida que el hardware mejora.

Argon2id

El ganador de la Password Hashing Competition de 2015 y la recomendación actual. Es memory-hard, lo que significa que un atacante debe invertir tanto en memoria como en cómputo, mitigando la ventaja de las GPUs y los ASICs. Argon2id equilibra la resistencia a los ataques de canal lateral y de GPU, siendo la opción predeterminada ideal para sistemas nuevos.

scrypt

Estandarizado en el RFC 7914 y también memory-hard, scrypt es una opción sólida cuando Argon2 no está disponible. Permite configurar el coste, el tamaño del bloque y el paralelismo para ajustarlo, aunque sus parámetros son más fáciles de configurar incorrectamente que los de Argon2.

bcrypt

Lanzado en 1999 y todavía presente en todas partes, bcrypt es bien conocido y ha sido probado en batalla. Su factor de coste duplica el trabajo con cada incremento. La principal advertencia es que trunca la entrada a 72 bytes, por lo que las frases de contraseña muy largas se cortan silenciosamente; realiza un pre-hash si esto es importante para ti.

Independientemente de lo que elijas, nunca utilices MD5, SHA-1 o SHA-256 simple. No son la herramienta adecuada, y apilar rondas sobre ellos es un esquema casero en el que nadie debería confiar.

Ajustando el factor de trabajo

El costo debe equilibrar dos presiones. Si es demasiado bajo, el hash es vulnerable a ataques de fuerza bruta; si es demasiado alto, una ráfaga de intentos de inicio de sesión puede convertirse en un vector de denegación de servicio contra tu propia CPU.

Ajústalo para que un solo hash tarde aproximadamente entre 100 y 500 milisegundos en tu hardware de producción. Mide el endpoint de login bajo una carga realista, no solo mediante un bucle de benchmark, y define el número basándote en los datos. Luego, revísalo periódicamente: los mismos parámetros se vuelven más débiles cada año a medida que el hardware mejora.

// Argon2id parameters (memory in KiB, iterations, parallelism)
await hash(password, { memoryCost: 19456, timeCost: 2, parallelism: 1 });

Almacenamiento y verificación

El registro y el inicio de sesión son los únicos dos lugares donde manipulas una contraseña.

import { hash, verify } from "@node-rs/argon2";

// Registration: hash before the password ever reaches the database.
const digest = await hash(password);
await db.users.insert({ email, passwordHash: digest });

// Login: verify against the stored digest.
const user = await db.users.findByEmail(email);
const ok = user && (await verify(user.passwordHash, password));
if (!ok) return res.status(401).json({ error: "invalid_credentials" });

Devuelve el mismo error independientemente de si el correo electrónico o la contraseña eran incorrectos. Un mensaje como “el usuario no existe” le da a un atacante una forma de enumerar cuentas.

Ataques de tiempo y comparación segura

Comparar dos hashes con === se detiene en el primer byte que sea diferente. Por lo tanto, el tiempo que tarda depende de qué parte de la suposición fue correcta, y un atacante paciente puede medir eso para reconstruir un valor. Es un canal estrecho, pero la comparación de contraseñas es precisamente donde esto importa.

Utiliza la función de verificación de la librería, la cual realiza la comparación en tiempo constante. Nunca compares los digests por tu cuenta y nunca compares la contraseña en texto plano.

Actualización de hashes con el tiempo

Es probable que cambies de opinión: el coste de computación aumenta o decides migrar de bcrypt a Argon2. Como nunca almacenaste el texto plano, solo puedes volver a generar el hash durante el login, que es cuando la contraseña reside brevemente en memoria.

Almacena el algoritmo y los parámetros con cada digest —la cadena codificada ya lo hace— y verifícalos en cada inicio de sesión exitoso:

if (await verify(stored, password)) {
  if (needsRehash(stored)) {
    await db.users.update(user.id, { passwordHash: await hash(password) });
  }
  // continue the login
}

Los usuarios que nunca vuelvan a iniciar sesión conservarán su hash antiguo, lo cual está bien; sigue protegiéndolos. Con el tiempo, las cuentas activas migrarán de forma natural.

La política de contraseñas en la práctica

El algoritmo te protege después de una brecha de seguridad. La política reduce las probabilidades de que ocurra una.

  • Prioriza la longitud sobre la complejidad. Una frase de contraseña es mejor que P@ssw0rd!.
  • Verifica las nuevas contraseñas comparándolas con listas de brechas conocidas, como la API de k-anonymity de Have I Been Pwned.
  • No impongas rotaciones arbitrarias. La expiración forzada conduce a patrones de Summer2026!.
  • Aplica rate-limit a los intentos de inicio de sesión y restablecimiento, y añade un backoff exponencial o un bloqueo de cuenta.
  • Utiliza un flujo de restablecimiento genérico con tokens de un solo uso que expiren, y nunca envíes una contraseña por email.

Mejores prácticas

  • Usa Argon2id, scrypt o bcrypt para el hashing; nunca utilices un hash rápido de propósito general.
  • Deja que la librería genere y almacene el salt.
  • Ajusta el costo para que el proceso tarde entre 100 y 500 ms y auméntalo con el tiempo.
  • Verifica las contraseñas utilizando la función de tiempo constante de la librería.
  • Vuelve a aplicar el hash tras un inicio de sesión exitoso si los parámetros han cambiado.
  • Devuelve errores genéricos y aplica rate-limit a los intentos.
  • Realiza el hashing siempre en el servidor; nunca en el cliente.

Errores comunes

  • Encriptar contraseñas o almacenarlas en texto plano “temporalmente”.
  • Usar SHA-256 o MD5 con unas pocas rondas y llamarlo hashing.
  • Usar un único salt global o reutilizar el mismo salt en varias cuentas.
  • Comparar hashes con == o ===.
  • Registrar contraseñas en los logs o incluirlas en reportes de errores.
  • Limitar la longitud de la contraseña a un valor bajo, lo que perjudica el uso de passphrases.
  • Olvidar actualizar el factor de costo durante años.

Próximos pasos

Las credenciales son solo la primera barrera. Una vez que el usuario ha sido verificado, necesitas un lugar donde mantener ese estado: lee la guía de Session Auth para sesiones basadas en cookies, o la guía de JWT para tokens stateless. Cuando prefieras no gestionar contraseñas en absoluto, OAuth 2.0 y OpenID Connect delegan el inicio de sesión a un proveedor de identidad.

En la practica

Hash, almacenar, verificar

Los mismos tres pasos en cualquier stack. La librería gestiona el salt y lo codifica junto al digest.

hash.js
import { hash } from "@node-rs/argon2";

const digest = await hash(password);
// $argon2id$v=19$m=19456,t=2,p=1$...$...

Hacer hash de una contraseña

Usa un hash de contraseñas diseñado para tal fin con un factor de trabajo. Un hash rápido de propósito general es el regalo favorito de un cracker.

Preferir
import { hash } from "@node-rs/argon2";

const digest = await hash(password);
Evitar
import { createHash } from "node:crypto";

const digest = createHash("sha256")
  .update(password)
  .digest("hex");

Comparar una contraseña

Compara a través de la verificación de tiempo constante de la librería. La igualdad de strings simple puede filtrar contenido y longitud mediante el tiempo de respuesta.

Preferir
const ok = await verify(stored, password);
Evitar
const ok = stored === (await hash(password));

Compromisos

¿Argon2 o bcrypt?

Ambos son seguros si se ajustan correctamente. Argon2 es el estándar moderno; bcrypt sigue siendo una opción perfectamente válida con años de uso en producción.

Strengths

  • Argon2id es memory-hard

    Resiste el cracking mediante GPU y ASIC al requerir memoria además de tiempo, y es la recomendación actual.

  • bcrypt está en todas partes

    Está integrado en la mayoría de los frameworks, tiene un largo historial y no requiere dependencias nativas adicionales.

  • Ambos son ajustables

    El factor de trabajo puede aumentarse con el tiempo, por lo que los hashes almacenados pueden fortalecerse sin cambiar las contraseñas de los usuarios.

Trade-offs

  • bcrypt trunca a los 72 bytes

    Las frases de contraseña largas se cortan silenciosamente, así que haz un pre-hash o prefiere Argon2 cuando esto sea importante.

  • El ajuste requiere cuidado

    Si es demasiado bajo, es vulnerable a cracking; si es demasiado alto, el inicio de sesión se convierte en un vector de denegación de servicio bajo carga.

  • El hashing no es toda la historia

    El rate limiting, las comprobaciones de filtraciones y un flujo de restablecimiento seguro importan tanto como el algoritmo.

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender Password Hashing & Credentials?

Nuestro tutorial interactivo te guia a traves de Password Hashing & Credentials paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.