Skip to content
Volver al blog
Seguridad

bcrypt vs Argon2 vs scrypt: hash de contraseñas en 2026

Compara bcrypt, Argon2id y scrypt con parámetros OWASP 2026, marco de decisión y ejemplos de código. Elige hoy el hash correcto para tu aplicación.

18 min de lectura

bcrypt vs Argon2 vs scrypt: hash de contraseñas en 2026

Respuesta corta: para cualquier proyecto nuevo en 2026, usa Argon2id con m=19456, t=2, p=1. Es el valor de referencia de la OWASP Password Storage Cheat Sheet y, hoy por hoy, el algoritmo de hash de contraseñas con la mayor resistencia frente a GPU y a ataques de canal lateral que puedes desplegar.

Si Argon2 no está disponible en tu stack (poco habitual, pero pasa en algunos runtimes embebidos o antiguos), elige scrypt con N=2^17, r=8, p=1. Usa bcrypt con cost=12 solo cuando estés atado a un sistema heredado que ya hable bcrypt y no puedas introducir una dependencia nueva. Quédate con PBKDF2-HMAC-SHA-256 con 600 000 iteraciones cuando el cumplimiento FIPS-140 sea obligatorio.

AlgoritmoParámetros OWASP 2026Cuándo elegirlo
Argon2idm=19456 KiB, t=2, p=1Predeterminado para proyectos nuevos
scryptN=2^17, r=8, p=1Argon2 no disponible
bcryptcost=12 (mínimo 10)Solo sistemas heredados
PBKDF2HMAC-SHA-256, 600k iteracionesCuando FIPS-140 es obligatorio

El resto del artículo explica de dónde salen esos números, cómo ajustarlos a tu hardware y cómo migrar sin obligar a un restablecimiento de contraseñas. ¿Necesitas generar contraseñas robustas de prueba para hacer benchmarks? Usa el Generador de Contraseñas Aleatorias. Para el panorama completo, consulta la guía de Seguridad Web Esencial.

Por qué el hash de contraseñas es distinto del hash general

Las funciones hash, vistas desde fuera, parecen todas iguales: entran datos, sale un resumen de longitud fija y no se puede invertir. Pero los objetivos de diseño para «hashear este ISO de 4 GB» y para «hashear esta contraseña de 12 caracteres» son exactamente opuestos. Uno debe ser tan rápido como el silicio lo permita. El otro debe ser tan lento como tu presupuesto de latencia de inicio de sesión tolere.

Confundir ambos casos es como las brechas de seguridad acaban convertidas en secuestros de cuenta.

Por qué MD5 y SHA-256 no bastan para contraseñas

Los hashes de propósito general como MD5, SHA-1 y SHA-256 se diseñaron pensando en el rendimiento. Procesan gigabytes por segundo en CPUs comunes y decenas de gigabytes por segundo en GPUs. Eso los hace excelentes para checksums de archivos y direccionamiento por contenido, y desastrosos para contraseñas.

Los benchmarks de Hashcat sobre una sola RTX 4090 muestran aproximadamente 164 GH/s para MD5 y 22 GH/s para SHA-256 en 2024. Una contraseña de ocho caracteres en minúsculas alfanuméricas (36^8 ≈ 2.8 × 10^12 candidatas) cae ante una sola GPU en menos de un minuto frente a MD5 y en un par de minutos frente a SHA-256. Una base de datos comprometida que almacene sha256(password) es prácticamente texto plano.

Y la sal tampoco te salva. La sal evita que las tablas arcoíris precomputadas funcionen, pero no hace nada para frenar un ataque por cuenta: el atacante hashea cada candidata concatenada con la sal filtrada y listo.

Para checksums sin fines de seguridad, MD5 y SHA-256 siguen siendo útiles; para eso están herramientas como el Generador de Hash MD5. Para una comparación a fondo de cuándo conviene cada algoritmo, lee MD5 vs SHA-256. Pero para contraseñas necesitas un hash deliberadamente lento.

Las tres propiedades de un hash de contraseñas moderno

Un hash de contraseñas digno de desplegar en 2026 tiene tres propiedades:

  1. Lento por diseño, con un factor de trabajo ajustable. Iniciar sesión debería tardar entre 100 y 500 ms: lo bastante rápido para que el usuario no lo note y lo bastante lento para que un atacante offline queme días por cada millón de intentos. El factor de trabajo tiene que ser un parámetro, así puedes subirlo a medida que mejora el hardware.
  2. Sal por registro. Una sal aleatoria única por contraseña derrota a las tablas arcoíris y obliga al atacante a atacar cada cuenta por separado. Los algoritmos modernos generan e incrustan la sal en la cadena de salida por ti.
  3. Memory-hard (duro en memoria). Las GPUs y los ASICs son rápidos en cómputo pero caros en memoria de alto ancho de banda. Un algoritmo que exige decenas de MiB por hash obliga al atacante a aprovisionar RAM proporcional a su paralelismo, y eso destruye la rentabilidad de las granjas de GPU.

bcrypt cumple (1) y (2), pero no (3). scrypt fue el primer algoritmo en cumplir las tres. Argon2 refinó el diseño y ganó la Password Hashing Competition. La siguiente sección desgrana cada uno.

Los tres algoritmos: arquitectura y compromisos

bcrypt: basado en Blowfish, time-hard

bcrypt fue diseñado en 1999 por Niels Provos y David Mazières para OpenBSD. Está construido sobre el cifrado Blowfish, con una fase costosa de configuración de clave («EksBlowfish») repetida 2^cost veces. El único parámetro ajustable es el factor de coste (también llamado «log rounds»): cada incremento duplica el trabajo. Un hash con cost=10 ejecuta 1024 programaciones de clave; con cost=14, 16 384.

Un hash bcrypt se ve así:

$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
 │  │  │                      │
 │  │  │                      └─ hash base64 de 31 caracteres
 │  │  └─ sal base64 de 22 caracteres
 │  └─ factor de coste (12)
 └─ identificador de algoritmo ($2b$ = bcrypt v2)

El formato es autodescriptivo: verify() lee el coste y la sal de la cadena almacenada, sin necesidad de columnas separadas.

Los inconvenientes son reales. La huella en memoria de bcrypt ronda los 4 KiB, lo bastante pequeña como para que una GPU de gama alta ejecute miles de núcleos bcrypt en paralelo. Y bcrypt trunca silenciosamente la entrada a 72 bytes. Una passphrase de 100 caracteres tiene la misma seguridad que sus primeros 72 bytes. El coste máximo es 31, pero cualquier valor por encima de ~16 empieza a perjudicar la latencia de inicio de sesión en hardware estándar.

scrypt: el pionero memory-hard

scrypt fue publicado en 2009 por Colin Percival para el servicio de copias de seguridad Tarsnap y estandarizado como RFC 7914 en 2016. Introdujo la idea de memory-hardness: el algoritmo llena un búfer grande con datos pseudoaleatorios y luego lee desde posiciones aleatorias, así obliga a cualquier implementación a reservar la memoria de verdad.

scrypt acepta tres parámetros:

  • N — coste de CPU/memoria (debe ser potencia de 2)
  • r — tamaño de bloque en bytes (multiplicador sobre la memoria y las rondas de mezcla)
  • p — paralelismo (cómputos independientes, usados sobre todo para escalar el tiempo de CPU sin escalar la memoria)

El uso de memoria es aproximadamente 128 × N × r bytes. Con la recomendación OWASP de N=2^17, r=8, eso da 128 × 131072 × 8 = 134 217 728 bytes: exactamente 128 MiB por hash.

scrypt es además una función de derivación de claves, no solo un hash de contraseñas. Se usa en monederos de criptomonedas, cifrado de disco completo y la prueba de trabajo original de Litecoin. Ese doble papel resulta cómodo cuando necesitas almacenamiento de contraseñas y derivación de claves en una sola biblioteca.

Argon2 (id/i/d): ganador de la Password Hashing Competition

La Password Hashing Competition se celebró entre 2013 y 2015, evaluando 24 algoritmos candidatos por su memory-hardness, su resistencia a canales laterales y la simplicidad de implementación. Ganó Argon2. Se estandarizó como RFC 9106 en 2021.

Argon2 tiene tres variantes. Las diferencias se reducen a cómo se direcciona la memoria durante la mezcla:

  • Argon2d usa direcciones de memoria dependientes de los datos. Maximiza la resistencia frente a ataques con GPU y ASIC, pero filtra información a través de canales laterales basados en tiempos de caché. Adecuado para prueba de trabajo de criptomonedas, no para autenticación.
  • Argon2i usa direcciones independientes de los datos. Seguro frente a canales laterales, pero un poco más débil ante ataques de tradeoff con GPU.
  • Argon2id es híbrido: la primera mitad de la primera pasada usa indexación Argon2i (segura ante canales laterales) y el resto usa indexación Argon2d (resistente a GPU). El RFC 9106 recomienda explícitamente Argon2id para hash de contraseñas, igual que OWASP.

Argon2 acepta tres parámetros:

  • m — memoria en KiB
  • t — coste de tiempo (número de pasadas sobre el búfer de memoria)
  • p — paralelismo (número de carriles procesados de forma concurrente)

Un hash Argon2id usa el formato de cadena PHC y se ve así:

$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG

Igual que con bcrypt, todos los parámetros van incrustados en la cadena, así que verify() no necesita una tabla aparte de parámetros.

Parámetros recomendados por OWASP 2026

La OWASP Password Storage Cheat Sheet es la referencia canónica. Los números de abajo coinciden con su guía actual. Son conservadores (dimensionados para un servidor web típico con un presupuesto de latencia de inicio de sesión de 100–500 ms) y aun así deberías hacer benchmark en tu propio hardware antes de pasar a producción.

Parámetros de Argon2id: primera elección

Recomendación de referencia de OWASP: m=19456 (19 MiB), t=2, p=1.

Si tu servidor tiene más holgura de RAM, puedes desplazar el trabajo entre memoria y tiempo. El RFC 9106 publica perfiles equivalentes; OWASP recomienda cualquiera de estos:

memoryCost (m)timeCost (t)parallelism (p)RAM por hash
471041146 MiB
194562119 MiB (referencia)
122883112 MiB
9216419 MiB
7168517 MiB

Regla práctica de ajuste. Elige primero m según tu presupuesto de RAM para inicios de sesión concurrentes en pico. Si esperas 100 inicios de sesión simultáneos y tienes 4 GiB disponibles, eso son 40 MiB por hash. Después sube t hasta que un solo verify tarde entre 100 y 500 ms en tu CPU de producción. Deja p=1 salvo que tengas una razón concreta multinúcleo para cambiarlo (la mayoría de los frameworks web ya asignan a cada petición su propio hilo).

Parámetros de scrypt: cuando Argon2 no está disponible

Recomendación de OWASP: N=2^17 (131072), r=8, p=1, que usa 128 MiB por hash.

Si 128 MiB por inicio de sesión concurrente es demasiado para tu servidor, OWASP permite perfiles más débiles:

NrpRAM por hash
2^1781128 MiB (preferido)
2^168164 MiB
2^158132 MiB

N debe ser potencia de dos. Subir r aumenta de forma proporcional la memoria y el trabajo de CPU; subir p aumenta el trabajo de CPU sin aumentar la memoria por instancia. Para hash de contraseñas, deja r y p en sus valores predeterminados y ajusta solo N.

bcrypt: factor de coste 10+ solo para sistemas heredados

OWASP ya no recomienda bcrypt para proyectos nuevos, pero sigue estando en todas partes: Devise, Spring Security, ASP.NET Identity y un sinnúmero de sistemas de autenticación caseros lo usan por defecto.

Si estás atado a bcrypt, las reglas son:

  • Factor de coste mínimo de bcrypt: 10. Por debajo de 10, una sola GPU termina una base de datos filtrada en pocos días.
  • Recomendado: de 12 a 14, según el hardware. En un servidor x86 moderno, cost=12 tarda alrededor de 250 ms por hash; cost=13, 500 ms.
  • Apunta a 100–300 ms por verify en tu hardware de producción. Mide, no adivines.
  • Recuerda el límite de entrada de 72 bytes. Si los usuarios pueden elegir passphrases, prehashea con SHA-256 (consulta el FAQ).

La resistencia a GPU de bcrypt está limitada por su huella de 4 KiB. Ningún factor de coste de bcrypt va a igualar la memory-hardness de Argon2id: elige Argon2id cuando puedas.

Una referencia práctica: en un servidor EPYC de 2024, bcrypt(cost=12) se ejecuta en aproximadamente 250 ms; en una laptop de gama alta, más cerca de 350 ms. Si tus números se salen del rango 100–500 ms en un orden de magnitud, revisa si tu biblioteca está usando de verdad bcrypt nativo o si está cayendo en un polyfill lento de JavaScript (algunos bundlers descartan dependencias nativas en builds serverless).

PBKDF2: el camino para cumplir FIPS-140

PBKDF2 (RFC 8018) es el algoritmo de último recurso desde el punto de vista de las recomendaciones de seguridad. Es más antiguo que bcrypt, no es memory-hard y cae ante ataques con GPU más rápido que cualquiera de los tres anteriores. Pero es la única primitiva de hash de contraseñas validada FIPS-140, y eso pesa para el gobierno federal, HIPAA en sanidad y ciertos despliegues financieros.

Cuando necesites PBKDF2, usa:

  • HMAC-SHA-256 como PRF (no uses SHA-1; no uses SHA-256 simple sin HMAC)
  • 600 000 iteraciones como mínimo (referencia OWASP 2026)
  • Al menos 16 bytes de sal aleatoria por contraseña

Si FIPS no aplica en tu caso, prefiere Argon2id. El diseño de salida fija y memoria fija de PBKDF2 implica que cada dólar de silicio de GPU que compra un atacante se traduce directamente en más intentos de contraseña por segundo.

La SP 800-63B del NIST califica a PBKDF2-HMAC como «aprobado» para hash de contraseñas, pero no llega a recomendarlo por encima de las alternativas memory-hard. Léelo así: el NIST permite PBKDF2 porque retirarlo invalidaría todos los despliegues gubernamentales heredados, no porque sea la mejor opción para un proyecto desde cero.

Marco de decisión: ¿qué algoritmo deberías elegir?

Tabla comparativa

DimensiónbcryptscryptArgon2idPBKDF2
Memory-hardNoNo
Resistencia a GPUMediaAltaMuy altaBaja
Resistencia a canales lateralesMediaMediaAlta (id)Media
Complejidad de parámetros1 (cost)3 (N, r, p)3 (m, t, p)1 (iteraciones)
Madurez de la bibliotecaExcelenteBuenaBuenaExcelente
Límite de longitud de entrada72 bytesNingunoNingunoNinguno
Estandarizaciónde factoRFC 7914RFC 9106RFC 8018
Estado OWASP 2026Solo legadoAlternativaPrimera elecciónSolo FIPS

Usa Argon2id por defecto

Para un proyecto nuevo (aplicación web típica, stack moderno de Node/Python/Go/Rust/JVM, sin restricciones FIPS) usa Argon2id con m=19456, t=2, p=1. Obtienes la mayor resistencia disponible frente a GPU y canales laterales, un formato con parámetros incrustados que sobrevive a las actualizaciones de biblioteca y ninguna sorpresa con la longitud de entrada. El ecosistema de bibliotecas es maduro: argon2 en npm, argon2-cffi en PyPI, golang.org/x/crypto/argon2, el crate argon2 en crates.io; todos mantenidos y benchmarkeados.

Cuándo elegir scrypt o bcrypt en su lugar

Elige scrypt cuando Argon2 no esté disponible en tu runtime (algo realmente raro en 2026: hasta Cloudflare Workers y Deno ya lo tienen) o cuando ya tengas un sistema basado en scrypt en producción y el coste de migración supere al diferencial de seguridad. scrypt sigue siendo un algoritmo sólido; solo le falta el pulido de Argon2id frente a canales laterales.

Elige bcrypt cuando mantengas un sistema heredado, tengas un requisito estricto de minimizar dependencias (sin código nativo, sin paquetes adicionales) y el límite de 72 bytes en la entrada sea aceptable para tu base de usuarios. bcrypt lleva dos décadas desplegado a escala de internet; sus modos de fallo están bien estudiados.

Elige PBKDF2 cuando lo diga el regulador. Esa es la única razón. Si tu auditor acepta Argon2id (cosa que cada vez ocurre más para cargas de trabajo no-FIPS), usa Argon2id.

Errores comunes que hay que evitar

La mayoría de las brechas de almacenamiento de contraseñas de la última década se reducen a un conjunto pequeño y recurrente de errores de ingeniería. Ninguno es exótico; todos los caza una revisión del código de autenticación con esta lista delante.

  • Hashear contraseñas con SHA-256 o MD5 directos. Es el mayor fallo de almacenamiento de contraseñas. Mira MD5 vs SHA-256 para entender por qué no sirven para contraseñas.
  • Reutilizar una única sal global para todos los usuarios. La sal tiene que ser única por registro. Argon2 y bcrypt te la generan; no la sobrescribas.
  • Fijar el tiempo de hash por debajo de 50 ms. Cambiaste seguridad por una mejora de velocidad que ningún usuario puede percibir. Apunta a 100–500 ms.
  • Fijar el tiempo de hash por encima de 1 segundo. Creaste un vector de denegación de servicio contra tu propio endpoint de login. Tope a unos 500 ms.
  • Hashear contraseñas en el cliente y enviar el resumen al servidor. Ahora el hash es la contraseña. Cualquiera que robe la base de datos puede autenticarse sin necesidad de invertirla. Hashea siempre en el servidor.
  • Almacenar los parámetros del algoritmo en una columna aparte. El formato PHC los incrusta en el hash. Úsalo.
  • Loggear contraseñas o hashes durante el manejo de errores. Ambos pertenecen al usuario, no a tu agregador de logs. Sanéalos en la capa de parseo de la petición antes de que lleguen a cualquier logger.
  • Tratar las excepciones de verify() como fallos de autenticación. Una biblioteca que lanza una excepción ante un hash almacenado mal formado debería exponer el error, no caer en silencio en «contraseña incorrecta». Distingue entre «contraseña incorrecta» (devuelve 401) y «hash almacenado corrupto» (devuelve 500 y avisa al on-call).

Implementación en el mundo real

Argon2id en Node.js

El paquete argon2 (bindings nativos a la implementación de referencia) es la opción canónica en Node:

import argon2 from 'argon2';

// Hashear al registrarse o al cambiar la contraseña
const hash = await argon2.hash(password, {
  type: argon2.argon2id,
  memoryCost: 19456,  // 19 MiB
  timeCost: 2,
  parallelism: 1,
});
// → '$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>'

// Verificar al iniciar sesión
const ok = await argon2.verify(hash, candidate);
if (!ok) throw new Error('Invalid credentials');

// Detectar parámetros desactualizados y rehashear tras un login exitoso
if (argon2.needsRehash(hash, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1 })) {
  const upgraded = await argon2.hash(candidate, {
    type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
  });
  await db.users.update({ id: user.id }, { password_hash: upgraded });
}

El paso needsRehash es el truco para la migración a largo plazo: cada inicio de sesión exitoso se convierte en una oportunidad para actualizar el hash almacenado a los parámetros actuales sin molestar al usuario.

El mismo patrón en Python con argon2-cffi:

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)

# Hashear
stored = ph.hash(password)

# Verificar
try:
    ph.verify(stored, candidate)
except VerifyMismatchError:
    raise ValueError('Invalid credentials')

# Rehashear al actualizar parámetros
if ph.check_needs_rehash(stored):
    stored = ph.hash(candidate)

En Go con golang.org/x/crypto/argon2:

import (
    "crypto/rand"
    "golang.org/x/crypto/argon2"
)

func hashPassword(password string) ([]byte, []byte) {
    salt := make([]byte, 16)
    rand.Read(salt)
    hash := argon2.IDKey([]byte(password), salt, 2, 19456, 1, 32)
    return hash, salt
}

La biblioteca estándar de Go no incluye un codificador con formato PHC; si usas la primitiva argon2.IDKey directamente, te toca codificar los parámetros y la sal junto al hash. La mayoría de los proyectos en Go usan un wrapper como github.com/alexedwards/argon2id para eso.

Rust con el crate argon2 es igual de idiomático:

use argon2::{Argon2, PasswordHasher, PasswordVerifier, password_hash::{SaltString, rand_core::OsRng}};

let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();  // Argon2id, m=19456, t=2, p=1 por defecto
let hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string();

// Al verificar
let parsed = argon2::password_hash::PasswordHash::new(&hash)?;
argon2.verify_password(candidate.as_bytes(), &parsed)?;

En los tres runtimes, la cadena producida es intercambiable: un hash creado en Node se verifica sin problema en Python o Rust. Esa compatibilidad entre runtimes hace de Argon2 una apuesta más segura para arquitecturas políglotas que los wrappers específicos de cada algoritmo.

Patrón de migración de bcrypt a Argon2id

Casi nunca tienes el lujo de borrar la tabla de usuarios y empezar de cero. El patrón de migración correcto es el mismo que se usa en la sección MD5-a-bcrypt del FAQ de nuestro generador de hash: una actualización suave, dirigida por el inicio de sesión.

Añade una columna que registre el algoritmo:

ALTER TABLE users ADD COLUMN password_algo VARCHAR(16) NOT NULL DEFAULT 'bcrypt';

En el login, despacha hacia el verificador correcto:

async function verifyAndMaybeRehash(user, candidate) {
  let ok;
  if (user.password_algo === 'argon2id') {
    ok = await argon2.verify(user.password_hash, candidate);
  } else if (user.password_algo === 'bcrypt') {
    ok = await bcrypt.compare(candidate, user.password_hash);
    if (ok) {
      // Verificación legacy exitosa → rehashear con Argon2id
      const newHash = await argon2.hash(candidate, {
        type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
      });
      await db.users.update({ id: user.id }, {
        password_hash: newHash,
        password_algo: 'argon2id',
      });
    }
  }
  return ok;
}

Fija una ventana de retiro de 6 a 12 meses. Envía un correo del estilo «tu contraseña está almacenada con un método obsoleto, inicia sesión para actualizarla» a los 9 meses. Tras 12 meses, las cuentas que sigan en bcrypt requieren un restablecimiento forzoso de contraseña en el siguiente inicio de sesión. Los usuarios activos migran de forma transparente; las cuentas inactivas pasan por un único momento de fricción.

El mismo patrón funciona para migrar desde scrypt o PBKDF2. El único estado que necesitas es la columna password_algo.

Pepper, límites de longitud y trampas de codificación

Algunos bordes filosos que muerden en despliegues reales:

Pepper. Un pepper es un secreto de nivel de aplicación que se añade a cada contraseña antes de hashearla y se almacena por separado de la base de datos (en un KMS, una variable de entorno o Hashicorp Vault). Si tu base de datos se filtra pero tu secreto de aplicación no, los hashes filtrados son inatacables sin el pepper. Aplícalo como HMAC, no por concatenación:

import { createHmac } from 'crypto';
const peppered = createHmac('sha256', process.env.PEPPER).update(password).digest();
const hash = await argon2.hash(peppered, { type: argon2.argon2id, /* ... */ });

Rota el pepper rara vez (rotarlo exige rehashear) pero soporta la rotación versionándolo: PEPPER_V2, con fallback a PEPPER_V1 al verificar.

Límite de 72 bytes en bcrypt. Si tienes que usar bcrypt y quieres soportar contraseñas de longitud arbitraria, prehashea con SHA-256 y codifica en base64 (evitando los bytes NUL incrustados, que bcrypt también maneja de forma inconsistente):

import { createHash } from 'crypto';
const prepped = createHash('sha256').update(password, 'utf8').digest('base64');
const hash = await bcrypt.hash(prepped, 12);

La misma transformación prepped debe correrse al verificar. Documéntalo en tu código de autenticación con un comentario gigante: tu yo del futuro le dará las gracias a tu yo del presente.

Normalización UTF-8. La cadena "café" puede codificarse como c-a-f-é (4 codepoints, NFC) o como c-a-f-e + agudo combinante (5 codepoints, NFD). Se ven idénticas pero producen hashes distintos. Normaliza siempre a NFC antes de hashear:

const normalized = password.normalize('NFC');

Esto pica con teclados móviles y al copiar y pegar desde PDFs más a menudo de lo que te imaginas.

Nunca prehashees en el cliente. Un hash calculado por el cliente y enviado al servidor es la nueva contraseña. Cualquiera que lea tu base de datos puede autenticarse. Hashea en el servidor, punto. Los JWT no cambian esto: lee Cómo decodificar un token JWT para entender qué autentican y qué no autentican los JWT.

Mide en el hardware de producción, no en tu laptop. Una laptop Intel de 13.ª generación que ejecuta Argon2id con m=19456, t=2, p=1 termina en aproximadamente 35 ms. Los mismos parámetros en una instancia EC2 t3.small se acercan a los 180 ms; en una Raspberry Pi 4, superan los 600 ms. Elige el hardware que va a correr en producción de verdad, cronometra 1000 verificaciones y ajusta a partir de la mediana. La varianza de latencia de inicio de sesión por arranque en frío de contenedores serverless también vale la pena medir: los arranques en frío de Lambda pueden añadir 200–800 ms ajenos al hashing.

FAQ

¿Cuál es la diferencia entre hash de contraseñas y cifrado?

El hash es unidireccional: calculas una huella digital de longitud fija que no se puede invertir para recuperar la entrada. El cifrado es bidireccional: con la clave correcta puedes descifrar y volver al original. Las contraseñas deben hashearse, no cifrarse. Un servidor no debería poder recuperar la contraseña de ningún usuario; así, una filtración de la base de datos no se convierte en una filtración de credenciales.

¿Por qué no puedo usar SHA-256 sin más para contraseñas?

SHA-256 está diseñado para velocidad. Una GPU moderna calcula 22 mil millones de hashes SHA-256 por segundo, así que una contraseña de 8 caracteres en minúsculas extraída de una base de datos filtrada cae en minutos. Los hashes de contraseñas necesitan tres propiedades que SHA-256 no tiene: ejecución deliberadamente lenta, sal por registro y memory-hardness. El compromiso es el mismo que se explica en la guía «No uses MD5 para seguridad» de nuestro generador de hash, y puedes leer más sobre cómo los atacantes convierten hashes débiles en texto plano en Entropía de Contraseñas.

¿bcrypt sigue siendo seguro en 2026?

bcrypt como tal no ha sido roto. La programación de claves basada en Blowfish sigue siendo criptográficamente sólida. Lo que cambió es el modelo de amenaza: las GPUs y los ASICs convierten la falta de memory-hardness de bcrypt en una debilidad apreciable frente a Argon2id. La postura de OWASP en 2026 es que bcrypt es aceptable para sistemas heredados con coste ≥ 10, pero los proyectos nuevos deben elegir Argon2id.

Argon2i vs Argon2d vs Argon2id, ¿cuál usar?

Usa Argon2id. El RFC 9106 lo especifica como la variante recomendada para hash de contraseñas. Argon2i es independiente de los datos (seguro frente a canales laterales pero más débil ante ataques de tradeoff con GPU). Argon2d depende de los datos (fuerte frente a GPU pero vulnerable a canales laterales por tiempos de caché). Argon2id es un híbrido que obtiene ambas propiedades por el precio de una.

¿Cómo elijo los parámetros de Argon2id para mi aplicación?

Empieza con la referencia de OWASP: m=19456, t=2, p=1. Después haz benchmark en tu CPU de producción y ajusta:

  1. Decide tu presupuesto de RAM por inicio de sesión (por ejemplo, 50 MiB en concurrencia pico).
  2. Fija m en ese valor o por debajo.
  3. Ejecuta argon2.hash() en bucle y mide el tiempo de pared.
  4. Sube t hasta que la mediana esté entre 100 y 500 ms.

Deja p=1 salvo que hayas perfilado y sepas que el paralelismo multi-carril ayuda en tu runtime. Para servidores de autenticación con mucho tráfico, sesgar hacia un t mayor y un m menor suele dar mejor holgura de RAM.

¿Qué es el límite de 72 bytes de bcrypt y cómo manejo passphrases largas?

bcrypt alimenta su entrada al schedule de claves de Blowfish, que trunca a 72 bytes. Una passphrase de 150 caracteres tiene la misma seguridad que sus primeros 72 bytes; el resto se ignora. La solución es prehashear con SHA-256 (32 bytes) o SHA-512 (64 bytes), codificar el resumen en base64 para evitar bytes NUL, y pasar eso a bcrypt. Argon2id y scrypt no tienen ese límite; aceptan entradas arbitrariamente largas de forma directa.

¿Puedo migrar de bcrypt a Argon2 sin forzar restablecimientos de contraseña?

Sí. El patrón es: almacena ambos algoritmos detrás de una columna password_algo, despacha la verificación a la biblioteca correcta y, ante cada verificación bcrypt exitosa, rehashea inmediatamente con Argon2id y actualiza la fila. Los usuarios activos migran en silencio dentro de su cadencia normal de inicio de sesión. Define una ventana de retiro de 6 a 12 meses para cuentas inactivas y, después, fuerza el restablecimiento de contraseña a cualquier registro que siga en bcrypt. El mismo patrón sirve para cualquier migración de algoritmo a algoritmo.

¿PBKDF2 sigue siendo una buena opción en 2026?

Solo cuando el cumplimiento FIPS-140 te obliga: típico en el gobierno federal, sanidad regulada (HIPAA) y ciertos sistemas financieros. Usa HMAC-SHA-256 como PRF con al menos 600 000 iteraciones. PBKDF2 no es memory-hard, así que cae ante ataques con GPU más rápido que Argon2id en presupuestos de latencia equivalentes. Si FIPS no aplica, elige Argon2id y ahórrate la gimnasia regulatoria.


La respuesta sobre hash de contraseñas en 2026 es corta: por defecto, Argon2id con los parámetros de referencia de OWASP; como respaldo, scrypt si Argon2 no está disponible; bcrypt solo donde el legado lo exija; y reserva PBKDF2 para sistemas obligados por FIPS. Acompaña el hash con una sal por registro (toda biblioteca moderna la maneja sola), un pepper de nivel de aplicación almacenado fuera de la base de datos y un bucle de rehasheo dirigido por el inicio de sesión que te permita subir los factores de trabajo a medida que mejora el hardware.

Genera un conjunto representativo de contraseñas con el Generador de Contraseñas Aleatorias, mide tu ruta de verificación contra la CPU de producción y escribe los parámetros en un archivo de constantes para que el siguiente ingeniero sepa exactamente qué tocar en 2028. El contexto completo de seguridad (TLS, gestión de sesiones, rate limiting, MFA) vive en nuestra guía de Seguridad Web Esencial. Elige hoy el hash correcto para tu aplicación.

Artículos relacionados

Ver todos los artículos