Cryptography Used Correctly (and Misused)
Software Security · Nutthakorn Chalaemwongwan
Today
- Hashing vs encryption vs encoding
- Password storage done right
- Symmetric / asymmetric basics
- Common crypto failures (A04:2025)
- 🔓 Game: Capture the Hash
Recap — Week 2
- Tools find bug patterns; today we study one bug class deeply
- Crypto misuse is subtle: code "works" but isn't safe
Three things people confuse
- Hashing — one-way, fixed-size digest (integrity, passwords)
- Encryption — reversible with a key (confidentiality)
- Encoding — reversible without a key (Base64 ≠ security!)
Symmetric vs asymmetric
- Symmetric (AES, ChaCha20) — one shared key, fast
- Asymmetric (RSA, ECC) — public/private key pair
- TLS uses asymmetric to exchange a symmetric session key
Password storage
- Never store plaintext or plain hashes
- Use a slow, salted KDF, in order of preference: argon2id → scrypt (if argon2id unavailable) → bcrypt (legacy systems only) → PBKDF2 (FIPS-140 compliance only)
- Salt is per-user and random
from argon2 import PasswordHasher
ph = PasswordHasher(); hash = ph.hash("correct horse battery staple")
Worked example: why ECB leaks
- ECB encrypts each block independently → patterns survive
- The classic "ECB penguin": the image is still recognizable encrypted
- CBC isn't the fix either — no integrity check means an attacker can flip bits in the ciphertext and the corresponding plaintext bits flip predictably
- Fix: an authenticated mode (AES-GCM) with a random nonce — confidentiality and integrity together
ECB in one picture
Capture the Hash — how cracking works
# strip comments, then crack unsalted MD5 with a wordlist
hashcat -m 0 hashes.txt rockyou.txt
- Unsalted fast hash → attacker hashes wordlist once, matches millions
- Salt would force per-guess work; a slow KDF makes it infeasible
Common crypto failures (A04:2025)
- ECB mode → block patterns leak
- Hardcoded keys / keys in source (CWE-798)
- Weak RNG (
randominstead ofsecrets/CSPRNG) (CWE-330) - MD5 / SHA-1 for security; unauthenticated encryption (no integrity)
Do this instead
- Authenticated encryption: AES-GCM or ChaCha20-Poly1305
- Keys from a secrets manager / KMS — never in code
- CSPRNG:
secrets(Python),crypto.randomBytes(Node) - SHA-256+ for integrity; argon2id/bcrypt for passwords
CWE mapping
- CWE-327 — broken/risky crypto algorithm
- CWE-916 — weak password hash (no/weak KDF)
- CWE-330 — insufficiently random values
- CWE-798 — hardcoded key
Four fixes — in the code
# vulnerable_crypto.py -> solution_skeleton.py
md5(pw).hexdigest() # CWE-916 -> argon2.PasswordHasher().hash(pw)
AES.new(key, AES.MODE_ECB) # CWE-327 -> AES.new(key, MODE_GCM, nonce=os.urandom(12))
HARDCODED_KEY = b"0123..." # CWE-798 -> bytes.fromhex(os.environ["ENC_KEY_HEX"])
random.choice("0123456789") # CWE-330 -> secrets.token_urlsafe(16)
- Four separate decisions — KDF · cipher mode · key source · RNG — not one "use better crypto"
- "Use AES" answers none of them: AES-ECB leaks; AES-GCM under a hardcoded key is still CWE-798
- Each fix makes the value unguessable / authenticated / unique, not merely "encrypted"
Four decisions, four fixes
🔓 Game — Capture the Hash
- Round 1 (speedrun): crack unsalted/MD5 hashes — fastest team wins
- Round 2: exploit an ECB oracle (identical blocks)
- Round 3 (defend): run the fixed version (argon2id + AES-GCM + keys from env) and confirm it holds — then author two pieces yourself: migrate a legacy MD5 record to argon2id on next login, and write the decrypt+tamper-check the fixed version doesn't include for you
Lab 3 — deliverable
📋 Worksheet 3 —
labs/week03-cryptography/worksheet.md(Part 3) · kickoff:docker compose up(runs the crypto scripts)
- Cracked hashes + method · ECB-leak proof · predictable-token note
- Before/after code (misuse → fix → CWE closed) — incl. your rehash-on-login migration + GCM decrypt/tamper-check
- TLS: read a real cert's issuer/subject/validity + negotiated protocol version
- Crack NoteVault's own MD5 hashes for the term-project report
- Every screenshot needs your whoami + student ID + timestamp in-frame — this lab's raw output is identical for the whole cohort by design, so unstamped evidence isn't gradable
- + Audit the AI / EiPE / Prompt Problem (see worksheet)
Key takeaways
- Encoding ≠ encryption; hashing isn't reversible
- Use vetted KDFs + authenticated encryption — never roll your own
- Randomness & key handling matter as much as the algorithm
Questions?
Next week: Injection & input handling