Module 4 · Authentication and security

Lesson 20 — Password hashing and MFA

bcrypt/argon2, MFA and account recovery without opening the door to attackers.

Published
In this lesson
  1. Exercise 1 — The hash
  2. Exercise 2 — Enumeration
  3. Exercise 3 — TOTP
  4. Exercise 4 — Recovery codes
  5. Exercise 5 — Reset
  6. Professor's summary

Exercise 1 — The hash

  1. Argon2 format: argon2id$v=19$m=65536,t=3,p=4$<salt>$<digest> — 64MB of memory, 3 iterations, 4 threads. The salt is included (random per user).
  2. Typically 50-200ms per verification on a laptop → ~5-20 verifications/sec/thread. An offline attacker with 10,000 GPUs (each with its GBs): thousands of times faster, but argon2's memory cost limits the real parallelism per GPU — that is argon2id's design (CPU+memory together raise the cost of mass cracking).
  3. Doubling parameters roughly doubles your login time (100→200ms) and more than doubles the attacker's cost per attempt (theirs scales per attempt, yours per legitimate login). The balance: ~100-250ms of login is imperceptible; tune it on real hardware (benchmark on production-like machines).
  4. Pwned Passwords with k-anonymity: you send only the first 5 characters of the SHA-1 (the server never sees your password) and compare the rest locally — a validator that rejects known-breached passwords.

Exercise 2 — Enumeration

  1. Times differ if the flow cuts early (email doesn't exist → you don't compute the hash): an attacker measures and enumerates emails. Equalizing: do the same work (a dummy hash) or sleep: same response, roughly the same time. A single message: "Invalid credentials".
  2. The forgot endpoint's single response: "If an account exists, you will receive an email" whether or not it does. Timing filtering applies here too (the same work always).

Exercise 3 — TOTP

  1. QR with otpauth://totp/TicketFlow:{email}?secret={b32}&issuer=TicketFlow: the user's app stores the secret; your DB stores yours (encrypted). confirmed prevents activating MFA without proving the user scanned correctly.
  2. Flow: login ok → mfa_required with no tokens → POST /auth/mfa/verify with the code → tokens issued. Without this intermediate step, the password alone would be enough and the MFA decorative.
  3. valid_window=1 accepts the previous/next period's code (±30s): it covers mild clock drift without opening the door to codes from minutes ago (with replay: the same code could be verified twice inside the window — a Redis/DB "last used counter" per user prevents it; pyotp doesn't do it for you).

Exercise 4 — Recovery codes

  1. 10 codes xxxx-xxxx; in the DB ONLY the hash (like passwords): if the DB leaks, the codes are unusable.
  2. Single use under concurrency: UPDATE... SET used_at = now() WHERE id = X AND used_at IS NULL (one row, affects 0 or 1 — lesson 10 applied): two simultaneous uses, one wins. The hash check comes first; the atomic UPDATE of the flag decides.
  3. A visible counter + regenerate (invalidates the old ones) + a warning: the user doesn't discover they have no codes the day they lose their phone.

Exercise 5 — Reset

  1. The same response always; the real email only if it exists. Token: token_urlsafe(32), hashed in DB, TTL 1h, single-use (the same technique as Ex. 4).
  2. Second use of the token: rejected (used_at). Expired token: rejected (exp). After the reset: blacklist all the user's refresh tokens (18) + notification "your password changed" (if it wasn't you, contact us) — the attacker who already had a session loses access.
  3. With MFA: the email link is NOT enough — demand a TOTP or recovery code when using the token. Without this, the attacker controlling the victim's email resets and walks in: the reset is the spot that BYPASSES the hash, and that is why it cannot bypass the MFA.

Professor's summary

  • argon2id with memory: the slow hash turns a DB leak into a problem of years, not hours.
  • Don't enumerate users (identical timings and messages); let HIBP say which passwords are burned.
  • TOTP: encrypted secret, an honest window, hashed single-use recovery codes under a transaction.
  • The password reset is the door that bypasses everything: demand the second factor and revoke sessions.

Next: Lesson 21 — Authorization.