User Authentication (challenge-response, PAKE, MFA)
Security & Cryptography · Nutthakorn Chalaemwongwan
Today
- Why sending a password to the server is a risk — even over TLS
- Challenge-response: proving you know a secret without saying it
- PAKE (SRP, OPAQUE) and what our demo does not give you
- Auth factors, TOTP vs FIDO2, credential stuffing, SSO/OAuth
- 🗝️ Game: Never Say the Password
Recap — Week 13
- E2E encryption: the server can't read message content, only the endpoints can
- Same server, same log line — plaintext vs. ciphertext, purely by where encryption happens
- This week: same question, different secret — can the server even avoid learning your password?
The premise: TLS protects the wire, not the endpoint
- TLS encrypts the network path between client and server
- The server terminates TLS — it decrypts the request and sees it in the clear
- "Log in by sending your password" hands the plaintext to the server itself
- A malicious, compromised, or merely over-logging server has it — TLS never touches this
server --[decrypts]--> plaintext in a Python dict. TLS solved the eavesdropper-on-the-wire problem decades ago; it says nothing about what the server does with the plaintext once it has it. This is the textbook-secure/real-system-failure split for this week: TLS is textbook-sound crypto; "send the password to the endpoint" is the real-system misuse. ~5 min. -->
Worked example: vulnerable mode
POST /login {"username":"alice","password":"correct-horse-battery"}
week14-server | SERVER SAW PASSWORD: correct-horse-battery
week14-client | LOGIN OK
- The plaintext password is now in the server's request handler — CWE-522 / CWE-319
- One
log.info(...)line away from sitting in application logs — CWE-532 - Login still "works" — that's what makes this easy to ship and easy to miss
The fix: challenge-response, in three steps
- Server sends a fresh random nonce (never reused)
- Client combines
nonce + passwordlocally → a proof - Server checks the proof against a stored verifier — never against the password
The server doesn't ask "what's your password?" — it asks "prove you know it."
How verification works without the plaintext
- Server stores a verifier
v = KDF(salt, password)— computed once, at signup, never the password itself - Client computes
proof = HMAC(v, nonce)locally, using the password it just typed - Server independently recomputes the same proof from its stored
vand the nonce it issued, and compares - Match →
LOGIN OK. The password never crossed the network, so there was nothing to log
Worked example: fixed mode
GET /challenge -> nonce=327decae...
POST /login {"username":"alice","nonce":"327decae...","proof":"40e70117..."}
week14-server | SERVER SAW: nonce=327decaec7e5... proof=40e701177990...
week14-client | LOGIN OK
docker compose -f docker-compose.fixed.yml logs | grep -c "correct-horse-battery" # -> 0
- Same successful login, same demo account — the password string appears nowhere
Honest scope — this is NOT a full PAKE
This demo proves exactly one property: the password is never transmitted.
A real PAKE (SRP, OPAQUE) also gives you two more:
- Offline-dictionary resistance on the stored verifier — ours is fast salted SHA-256; a DB leak lets an attacker brute-force weak passwords offline
- Mutual authentication — the client also verifies the server, so it can't be tricked into proving its password to an impostor
Audit the AI — a "professional-looking" login handler
log.info("login attempt: %s", body) # (A)
...
log.warning("login FAILED for %s (pw=%s)", body["username"],
body["password"]) # (B)
bcrypt.checkpw(...)against the stored hash is correct- But
(A)logs the full request body,(B)logs the raw password — CWE-532 - The bcrypt hash was never the leak. The log file is.
Authentication factors
| Factor | Example | Weakness |
|---|---|---|
| Know | password, PIN | phishable, reusable, guessable |
| Have | phone, FIDO2 key | can be lost/stolen; SIM-swap for SMS |
| Are | fingerprint, face | can't be rotated if compromised |
- MFA = combine factors from different rows — not two passwords
TOTP vs FIDO2/WebAuthn
- TOTP (authenticator app): a shared secret + current time → a 6-digit code you type
- Phishable — a fake login page can relay your code to the real site in real time
- FIDO2/WebAuthn: a private key on your device signs a challenge bound to the origin
- Not phishable — the signature is invalid for any domain but the real one
Credential stuffing
- Attacker takes passwords leaked from breach A, tries them against site B
- Works because people reuse passwords across sites
- Best single mitigation: MFA — a correct, reused password alone no longer logs the attacker in
Delegated authentication: SSO / OAuth
- User authenticates once with an identity provider (Google, institutional SSO…)
- Relying apps never see the password at all — only a token
- Fewer passwords to phish or reuse — but the IdP becomes a single point of failure
🗝️ Signature game — Never Say the Password
- Play server operator: run vulnerable mode, watch your own log hand you the plaintext password
- Flip to challenge-response mode, log in successfully, then grep your own logs for the password and find nothing
- Proving you know a secret without ever saying it out loud — the same trick every "Sign in with…" button quietly relies on
Lab today
📋 Worksheet 14 —
labs/week14-authentication/worksheet.md· run:docker compose -f docker-compose.vulnerable.yml up --build --abort-on-container-exit, then the.fixed.ymlvariant
- Capture both logs: the
SERVER SAW PASSWORDline (vuln) and its absence +grep -c 0(fixed) - Audit the AI on the planted logging flaw (Part 2b)
- EiPE: explain proof-without-revealing in plain English (Part 2c)
- Prompt Problem + viva prep (Part 2d–e) — plus 8 conventional essay questions (Part 1)
Key takeaways
- TLS protects the wire; it says nothing about what the endpoint does with your plaintext
- Challenge-response proves knowledge of a secret without ever transmitting it
- "Textbook-secure primitive, real-system failure": challenge-response math is sound — "just use HTTPS and send the password" is the real-system misuse
Questions?
Next week: Post-Quantum Cryptography