La contraseña de 1MB: Cómo los ataques por agotamiento de hashing derriban servidores 🏎️💥

En el mundo de la ciberseguridad, a menudo nos enseñan que más lento es mejor. Cuando se trata de hashing de contraseñas, elegimos intencionadamente algoritmos como Argon2id o Bcrypt porque son “costosos”. Están diseñados para consumir ciclos de CPU y memoria, dificultando los ataques de fuerza bruta para los hackers.
Pero, ¿qué pasa cuando ese “coste” se vuelve en tu contra?
Entra el Ataque de Contraseña de 1MB. Es un vector de DoS (Denegación de Servicio) silencioso, elegante y sorprendentemente simple. Enviando una cadena sobredimensionada—digamos, 1 megabyte de caracteres aleatorios—en tu campo de login o registro, un atacante puede forzar a tu servidor a gastar segundos, o incluso minutos, en hashear una sola solicitud.
Unas cuantas de estas solicitudes pueden poner tu CPU al 100%, derribando efectivamente todo tu backend. En este artículo, analizaremos la mecánica del agotamiento de hashing, por qué los frameworks modernos te dejan vulnerable y cómo solucionarlo sin comprometer la seguridad.
1. La paradoja del hashing “lento”
Para entender esta vulnerabilidad, hay que mirar cómo funcionan las funciones de hashing. Los algoritmos modernos como Bcrypt, Scrypt y Argon2 son “adaptativos”. Utilizan un Factor de Trabajo (o costo) para determinar cuántas iteraciones de la función de hashing deben ejecutarse.
$O(n)$ vs. Factor de Trabajo
La mayoría de los desarrolladores se concentran en el factor de trabajo. Si configuras Bcrypt con un costo de 12, tarda aproximadamente 250ms en hashear una contraseña. Si subes a 13, tarda 500ms. Esto es constante independientemente de si la contraseña es “password123” o “correct-horse-battery-staple”.
Sin embargo, hay una variable oculta: la longitud de la entrada ($n$).
Mientras el factor de trabajo ofrece protección exponencial contra fuerza bruta, la función de hashing aún debe procesar cada byte de la string de entrada. La complejidad es aproximadamente $O(n \times \text{factor de trabajo})$.
Cuando $n$ es de 15 caracteres, el impacto es insignificante. Cuando $n$ es de 1,000,000 de caracteres (1MB), la CPU debe calcular el hash sobre una cantidad enorme de datos, ejecutando miles de iteraciones simultáneamente.
Por qué esto es un “asesino silencioso”
La mayoría de los ataques DoS son volumétricos (sobre carga de red) o basados en protocolos (SYN floods). Los firewalls y los WAFs (Web Application Firewalls) son eficaces bloqueando estos ataques. Pero una solicitud POST de 1MB parece un dato de usuario válido (aunque algo grande). Como apunta a la capa de aplicación, evita muchas defensas tradicionales de red.
2. Anatomía del ataque: la carga útil de 1MB
Imagina un endpoint de login estándar: POST /api/v1/login.
Una solicitud típica sería así:
{
"username": "victim@example.com",
"password": "pA$$w0rd123!"
}
Ahora, imagina que el atacante envía esto:
{
"username": "victim@example.com",
"password": "a...[999,990 'a's'...a]"
}
El pico de CPU
Cuando tu servidor recibe esto, pasa la cadena de 1MB a la librería de hashing.
- El impacto en memoria: El servidor debe reservar al menos 1MB de RAM solo para mantener la cadena en el cuerpo de la solicitud.
- El impacto en CPU: La CPU empieza a trabajar a toda máquina. Para Argon2id, que es resistente a la memoria, puede intentar asignar 64MB de RAM por hash y ejecutar 3 iteraciones sobre esa entrada de 1MB.
- El bloqueo del hilo: En muchos entornos (como Node.js o Python/Django), el hashing se realiza en el hilo principal o en un pool limitado de workers. Mientras la CPU está ocupada hasheando esa cadena de 1MB, no puede procesar otras solicitudes.
3. Comparación de vulnerabilidades: Bcrypt vs. Argon2 vs. PBKDF2
No todos los algoritmos de hashing manejan cadenas largas igual. Irónicamente, algunas “fallas” antiguas ofrecen una capa de protección.
Bcrypt: La “escudo” de 72 bytes
Bcrypt tiene una peculiaridad famosa: solo considera los primeros 72 bytes de la contraseña. Los caracteres después del 72º byte son ignorados.
- La desventaja de seguridad: usuarios con contraseñas de 100 caracteres no son tan seguros como creen.
- La ventaja en DoS: como trunca la entrada casi de inmediato, enviar una contraseña de 1MB a una función Bcrypt generalmente no causa un pico en CPU. La librería solo mira los primeros 72 bytes y descarta el resto.
Argon2id: La vulnerabilidad moderna
Argon2id (el estándar actual en 2026) fue diseñado para solucionar las limitaciones de Bcrypt. Puede manejar entradas arbitrariamente largas. Aunque esto es bueno para la seguridad y las frases de paso, significa que intentará hashear fielmente los 1MB que envíes. Sin un límite a nivel de aplicación, Argon2 se vuelve un objetivo principal para ataques de agotamiento.
PBKDF2: La pesadilla de las iteraciones
PBKDF2, a menudo usado con SHA-256, no tiene límite de 72 bytes, pero es puramente CPU-bound. Si un atacante envía una cadena enorme, la carga de CPU escala linealmente con la longitud. En frameworks como Django (que usa PBKDF2 por defecto), esto puede generar latencias significativas incluso si no derriba el servidor inmediatamente.
4. Por qué los frameworks a menudo fallan en esto
En 2025 y 2026, muchos frameworks han adoptado configuraciones “seguras por defecto”, pero se concentran en la longitud mínima, no en la máxima.
- Django: Tiene
MinimumLengthValidator(por defecto 8 caracteres). No incluye unMaximumLengthValidatorpara contraseñas. - Laravel: valida
min:8, pero rara vezmax:128. - Node.js (bibliotecas Bcrypt/Argon2): La mayoría simplemente aceptan un String o Buffer. Si la aplicación pasa el cuerpo crudo de la solicitud a la función, la librería intentará procesarlo.
La filosofía siempre ha sido: “¿Por qué detener a un usuario de tener una contraseña más segura y larga?” Olvidamos que una “contraseña” de 1MB no es una contraseña—es una granada.
5. Mitigaciones estratégicas: Protege tu backend
Protegerse contra el agotamiento de hashing requiere un enfoque en capas. Quieres detener la “granada” lo más lejos posible de tu CPU.
Mitigación 1: La regla de oro (límites de longitud máxima)
La defensa más simple y efectiva es establecer un límite rígido en el campo de contraseña.
Recomendación: Establece un máximo de 128 o 256 caracteres.
Incluso el entusiasta de seguridad más extremo no necesita una contraseña de más de 256 caracteres. Es suficiente para cualquier frase de paso razonable y mantiene el hashing computacionalmente trivial para el servidor.
Ejemplo (Django):
# settings.py
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 12},
},
# Añade un MaxLengthValidator personalizado o incorporado si está disponible
]
Mitigación 2: La técnica de pre-hash SHA-256
Si necesitas permitir contraseñas de longitud infinita (por alguna razón criptográfica específica), usa un Pre-Hash.
Antes de pasar la contraseña a Bcrypt o Argon2, pásala por un hash rápido y no adaptativo como SHA-256.
- El usuario envía una contraseña de 1MB.
- El servidor calcula
temp_hash = SHA-256(password). - La salida de SHA-256 siempre es una cadena fija de 32 bytes.
- El servidor calcula
final_hash = Bcrypt(temp_hash).
Esto asegura que el algoritmo “costoso” (Bcrypt/Argon2) solo vea una entrada pequeña y fija.
Nota: Si implementas esto, debes ser consistente. No puedes cambiar a pre-hash para usuarios existentes sin un plan de migración, ya que los hashes dejarán de coincidir.
Mitigación 3: Limitación de tasa por IP y usuario
Los ataques de agotamiento dependen del volumen. Aunque una contraseña de 1MB tarda 2 segundos en hash, una sola solicitud no derriba el servidor. Diez solicitudes concurrentes sí.
- Implementa limitación de tasa estricta en los endpoints de login y registro.
- Usa un algoritmo de “bucket con fugas” para ralentizar intentos repetidos desde la misma IP.
Mitigación 4: WAF y límites en la carga útil
Configura tu Load Balancer (Nginx, AWS ALB) o WAF (Cloudflare, Akamai) para rechazar cargas útiles sospechosas de ser demasiado grandes.
Si tu formulario de login solo tiene usuario y contraseña, el cuerpo POST completo no debería superar los 10KB. Rechaza cualquier cosa mayor en el borde.
6. Ejemplos de implementación en el mundo real
Node.js (Express + Joi)
Usar una librería de validación como Joi facilita aplicar estos límites antes de que los datos lleguen a tu lógica de hashing.
const loginSchema = Joi.object({
username: Joi.string().email().required(),
password: Joi.string().min(12).max(128).required() // La línea crítica
});
app.post('/login', (req, res) => {
const { error } = loginSchema.validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
// Solo ahora llamamos al hash costoso
const isValid = await bcrypt.compare(req.body.password, user.hash);
});
PHP (Laravel)
El motor de validación de Laravel hace esto en una línea.
$request->validate([
'email' => 'required|email',
'password' => 'required|string|min:12|max:255', // Limite superior
]);
7. Tabla comparativa: Algoritmos de hashing y vulnerabilidad DoS
| Algoritmo | Máximo por defecto | Vulnerabilidad DoS (sin límite) | Recomendación 2026 |
|---|---|---|---|
| Bcrypt | 72 bytes | Baja (por truncamiento) | Bueno para legado / RAM limitada |
| Argon2id | Ninguno | Alta | Mejor (con límite de 128 caracteres) |
| Scrypt | Ninguno | Media/Alta | Bueno, pero Argon2id preferido |
| PBKDF2 | Ninguno | Media | Solo si Argon2 no está disponible |
Conclusión: La disponibilidad es una característica de seguridad
A menudo tratamos Seguridad y Disponibilidad como silos separados. Endurecemos nuestros hashes para evitar brechas de datos (Seguridad), pero olvidamos que un servidor no responsivo es un servidor fallido (Disponibilidad).
La “Contraseña de 1MB” nos recuerda que cada byte de entrada del usuario tiene un costo. Añadiendo simplemente una restricción max_length: 128 en tus campos de contraseña, cierras un enorme agujero de DoS sin comprometer los más altos estándares de protección criptográfica.
No permitas que tu “seguridad fuerte” sea lo que te derriba.
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.