Skip to main content

All weeks · Worksheet · Overview

Week 14 · Lecture slides

Week 14

Contents18 sections

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

  1. Server sends a fresh random nonce (never reused)
  2. Client combines nonce + password locally → a proof
  3. 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 v and 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:

  1. Offline-dictionary resistance on the stored verifier — ours is fast salted SHA-256; a DB leak lets an attacker brute-force weak passwords offline
  2. 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

FactorExampleWeakness
Knowpassword, PINphishable, reusable, guessable
Havephone, FIDO2 keycan be lost/stolen; SIM-swap for SMS
Arefingerprint, facecan'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.yml variant

  • Capture both logs: the SERVER SAW PASSWORD line (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

All weeks in Security & Cryptography