Por qué importa la memoria
Cuando un atacante roba hashes de contraseñas o una bóveda cifrada, prueba contraseñas sin conexión, tan rápido como le permita su hardware. Una GPU tiene miles de núcleos pequeños, así que una función que necesita poca memoria, como PBKDF2, corre en muchos de ellos a la vez.
Argon2 necesita un bloque de memoria por cada intento y no se puede saltar a bajo coste. La memoria es lo que escasea en una GPU. Como aritmética simple, no como benchmark: una tarjeta con 24 GiB admite unos 96 intentos a la vez con 256 MiB cada uno, y unos 1.290 con 19 MiB. PBKDF2 no impone ese límite.
Hay tres variantes. Argon2d es la que mejor resiste a las GPU, pero su acceso a memoria depende de la contraseña, lo que puede filtrarse por canales laterales. Argon2i evita eso, a costa de algo de resistencia frente a GPU. Argon2id empieza como Argon2i durante la primera mitad de la primera pasada y después funciona como Argon2d. RFC 9106 lo recomienda como opción por defecto.
Los parámetros
| Nombre | Significado | Efecto |
|---|---|---|
m | Memoria, en KiB | La defensa principal. Sube este primero. |
t | Pasadas sobre la memoria | Más tiempo por cada intento. |
p | Carriles, para hilos en paralelo | Permite que un dispositivo con más núcleos llene la memoria más rápido. No añade memoria. |
| Salt | Valor aleatorio para cada contraseña | La misma contraseña da un hash distinto. No es secreto, se guarda en claro. Con 16 bytes basta para cualquier uso, según el RFC. |
| Longitud de la etiqueta | Tamaño de la salida | 16 bytes bastan para la mayoría de usos. 32 es habitual cuando se convierte en una clave. |
Qué valores usar
| Fuente | m | t | p | Tiempo en nuestro Mac |
|---|---|---|---|---|
| RFC 9106, primera recomendación | 2 GiB | 1 | 4 | 1,43 s |
| RFC 9106, segunda recomendación, para menos memoria | 64 MiB | 3 | 4 | 0,11 s |
| OWASP, mínimo de su resumen | 19 MiB | 2 | 1 | 0,02 s |
OWASP enumera varias configuraciones que considera equivalentes en defensa, como 46 MiB con t=1 y 12 MiB con t=3. Todas son mínimos para un servidor que tiene que dejar entrar a muchos usuarios. Una app que deriva una sola clave en tu propio dispositivo puede permitirse mucho más, porque el usuario espera solo una vez por desbloqueo.
Una buena forma de elegir: toma la mayor cantidad de memoria que puedan ceder todos los dispositivos que soportas, mantén t en 3 o menos, y comprueba el tiempo en tu dispositivo más lento. Recuerda que si un teléfono se queda sin memoria, el desbloqueo falla.
Pruébalo
La herramienta de línea de comandos argon2 se instala con brew install argon2 en macOS, o sudo apt install argon2. La memoria se indica en KiB con -k. El salt debe tener al menos 8 caracteres. No uses contraseñas reales de esta forma, porque pueden quedar en el historial del shell:
$ printf 'correct horse battery staple' | argon2 examplesalt12345 -id -t 3 -k 262144 -p 1 -l 32
Type: Argon2id
Iterations: 3
Memory: 262144 KiB
Parallelism: 1
Hash: e2c39f45f62dd7bddc5b6eff815f32fae69748a05f4c6f9875dd0e06bb9b2934
Encoded: $argon2id$v=19$m=262144,t=3,p=1$ZXhhbXBsZXNhbHQxMjM0NQ$4sOfRfYt173cW27/gV8y+uaXSKBfTG+Ydd0OBrubKTQ
0.356 seconds
Verification ok
La cadena codificada contiene todo lo necesario para comprobar una contraseña más adelante: la variante, la versión (v=19 es la 1.3), los parámetros, el salt y el hash, estos dos últimos en Base64. Así puedes subir los parámetros después y los hashes antiguos seguirán verificándose.
En Python, con argon2-cffi en un entorno virtual:
from argon2 import PasswordHasher, Type
ph = PasswordHasher(time_cost=3, memory_cost=262144, parallelism=1,
hash_len=32, salt_len=16, type=Type.ID)
stored = ph.hash("correct horse battery staple")
ph.verify(stored, "correct horse battery staple") # True; a wrong password raises VerifyMismatchError
ph.check_needs_rehash(stored) # False until you raise the parameters
Crea un salt aleatorio por ti. No escribas tu propia gestión de salts.
Argon2id o PBKDF2
PBKDF2 repite un hash muchas veces y casi no necesita memoria. OWASP pide 600.000 iteraciones con HMAC-SHA256, que tardaron 0,04 segundos en nuestro Mac. Para ti es una espera razonable, pero cada intento es barato de ejecutar en paralelo. Usa PBKDF2 cuando un estándar como FIPS lo exija, y Argon2id en los demás casos.
Una contraseña débil cae igualmente
Argon2id hace más lento cada intento. No convierte una mala contraseña en una buena. Los atacantes prueban primero las contraseñas habituales, así que una contraseña cercana al principio de su lista necesita muy pocos intentos.
Cómo combina una bóveda la contraseña, Argon2id y una clave aparte se explica en qué significa el cifrado de extremo a extremo en un gestor de contraseñas.
Preguntas
¿Qué variante de Argon2 conviene usar?
Argon2id. La RFC 9106 la recomienda por defecto, porque resiste tanto los intentos con GPU como los ataques de canal lateral.
¿Qué significa $argon2id$v=19$m=65536,t=3,p=4?
Argon2id, versión 1.3, 65536 KiB (64 MiB) de memoria, 3 pasadas y 4 carriles. Después van la sal y el hash en Base64.
¿Es Argon2id mejor que bcrypt?
Para sistemas nuevos, sí. OWASP pone Argon2id en primer lugar y deja bcrypt para sistemas existentes. bcrypt usa poca memoria, siempre la misma, y la mayoría de implementaciones solo leen los primeros 72 bytes de la contraseña.
Fuentes
- RFC 9106: Argon2 Memory-Hard Function
- OWASP Password Storage Cheat Sheet
- RFC 8018: PKCS #5, incluido PBKDF2
- Documentación de argon2-cffi