Módulo 4 · Autenticación y seguridad

Lección 20 — Hash de contraseñas y MFA

bcrypt/argon2, MFA y recuperación de cuenta sin abrir la puerta al atacante.

Publicada
En esta lección
  1. Ejercicio 1 — El hash
  2. Ejercicio 2 — Enumeración
  3. Ejercicio 3 — TOTP
  4. Ejercicio 4 — Códigos de recuperación
  5. Ejercicio 5 — Reset
  6. Resumen del profesor

Ejercicio 1 — El hash

  1. Formato argon2: argon2id$v=19$m=65536,t=3,p=4$<salt>$<digest> — 64MB de memoria, 3 iteraciones, 4 hilos. El salt va incluido (aleatorio por usuario).
  2. Típicamente 50-200ms por verificación en una portátil → ~5-20 verificas/seg/hilo. Un atacante offline con 10.000 GPUs (cada GPU con sus GB): miles de veces más rápido, pero el coste de memoria de argon2 limita el paralelismo real por GPU — ese es el diseño de argon2id (CPU+memoria juntos suben el coste del crack masivo).
  3. Duplicar parámetros ~duplica tu tiempo de login (100→200ms) y más que duplica el coste del atacante por intento (el suyo escala por intento, el tuyo por login legítimo). El equilibrio: ~100-250ms de login es imperceptible; ajustas según hardware real (benchmark en producción-like).
  4. Pwned Passwords con k-anonymity: envías solo los primeros 5 caracteres del SHA-1 (el servidor nunca ve tu contraseña) y compara localmente el resto — validador que rechaza contraseñas filtradas conocidas.

Ejercicio 2 — Enumeración

  1. Los tiempos difieren si el flujo corta antes (email no existe → no calculas hash): un atacante mide y enumera emails. Igualar: hacer el mismo trabajo (hash dummy) o dormir: misma respuesta, mismo tiempo aproximado. Mensaje único "Credenciales inválidas".
  2. Respuesta única del forgot: "Si existe una cuenta, recibirás un email" exista o no. El filtrado por tiempo también aplica aquí (mismo trabajo siempre).

Ejercicio 3 — TOTP

  1. QR con otpauth://totp/TicketFlow:{email}?secret={b32}&issuer=TicketFlow: la app del usuario guarda el secreto; tu BD guarda el suyo (cifrado). confirmed evita activar MFA sin demostrar que el usuario escaneó bien.
  2. Flujo: login ok → mfa_required sin tokens → POST /auth/mfa/verify con el código → tokens emitidos. Sin este paso intermedio, el password solo sería suficiente y el MFA decorativo.
  3. valid_window=1 acepta el código del periodo anterior/siguiente (±30s): cubre desincronización leve de relojes sin abrir la puerta a códigos de minutos atrás (con replay: el mismo código puede verificarse dos veces dentro de la ventana — Redis/bd "last used counter" por usuario lo impide; pyotp no lo hace por ti).

Ejercicio 4 — Códigos de recuperación

  1. 10 códigos xxxx-xxxx; en BD SOLO el hash (como contraseñas): si filtra la BD, los códigos no son usables.
  2. Uso único bajo concurrencia: UPDATE... SET used_at = now() WHERE id = X AND used_at IS NULL (una fila, afecta 0 o 1 — la 10 aplicada): dos usos simultáneos, uno gana. El check del hash antes, el UPDATE atómico del flag decide.
  3. Contador visible + regenerar (invalida los viejos) + aviso: el usuario no descubre que no tiene códigos el día que pierde el móvil.

Ejercicio 5 — Reset

  1. Respuesta única siempre; el email real solo si existe. Token: token_urlsafe(32), hash en BD, TTL 1h, single-use (misma técnica del Ej. 4).
  2. Segundo uso del token: rechazo (used_at). Token vencido: rechazo (exp). Tras reset: blacklist de todos los refresh del usuario (18) + notificación "tu contraseña cambió" (si no fuiste tú, contacta) — el atacante que ya tenía sesión pierde el acceso.
  3. Con MFA: el link del email NO basta — exige TOTP o código de recuperación al usar el token. Sin esto, el atacante que controla el email de la víctima resetea y entra: el reset es el punto que por BYPASEA el hash, y por eso no puede bypasea el MFA.

Resumen del profesor

  • argon2id con memoria: el hash lento convierte un leak de BD en un problema de años, no de horas.
  • No enumeres usuarios (tiempos y mensajes idénticos); deja que HIBP diga qué contraseñas están quemadas.
  • TOTP: secreto cifrado, ventana honesta, códigos de recuperación hasheados y de un solo uso bajo transacción.
  • El reset de contraseña es la puerta que bypasea todo: exige el segundo factor y revoca sesiones.

Después: Lección 21 — Autorización.