Exercises
Six crypto exercises that test whether you can pick the right primitive AND apply it correctly. Try first; answers explain the WHY.
Six crypto drills
EXAMPLE
# ============================================================
# Drill 1 — Compare a token in constant time
# ============================================================
# A user submits a token. Compare against the stored one.
# Write code that does NOT leak via timing.
#
# ANSWER:
import hmac
def safe_eq(a: str, b: str) -> bool:
return hmac.compare_digest(a.encode(), b.encode())
# WHY: '==' compares character-by-character and returns early. An attacker
# can measure response time to deduce token bytes.
# ============================================================
# Drill 2 — Hash a password for storage
# ============================================================
# ANSWER:
import argon2
hasher = argon2.PasswordHasher(time_cost=3, memory_cost=64*1024, parallelism=4)
stored = hasher.hash(password)
# To verify:
try: hasher.verify(stored, candidate); ok = True
except argon2.exceptions.VerifyMismatchError: ok = False
# Plan a rehash if params changed
if hasher.check_needs_rehash(stored):
stored = hasher.hash(candidate)
# ============================================================
# Drill 3 — Encrypt + authenticate a cookie value
# ============================================================
# ANSWER: AES-256-GCM. Use a per-message random 96-bit nonce.
import secrets
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(256)
nonce = secrets.token_bytes(12)
aad = b'cookie-v1' # bind to context to prevent cross-version replay
ct = AESGCM(key).encrypt(nonce, b'session payload', aad)
# Wire format: base64(nonce || ct)
# Decrypt: pass same nonce + aad
# NEVER reuse a (key, nonce) pair. EVER. Catastrophic if you do.
# ============================================================
# Drill 4 — Sign a webhook payload
# ============================================================
# ANSWER: HMAC-SHA256 over body + timestamp.
import hmac, hashlib, time
# Sender
ts = str(int(time.time()))
body = b'{"order":"o1"}'
sig = hmac.new(secret, (ts + '.').encode() + body, hashlib.sha256).hexdigest()
# Headers: X-Signature: sig=<sig>, ts=<ts>
# Receiver
def verify(body: bytes, header_sig: str, header_ts: str, secret: bytes) -> bool:
if abs(int(time.time()) - int(header_ts)) > 300: return False # 5 min window
expected = hmac.new(secret, (header_ts + '.').encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header_sig)
# ============================================================
# Drill 5 — Generate a password reset token
# ============================================================
# ANSWER:
import secrets, hashlib
token = secrets.token_urlsafe(32) # send THIS in the email link
# Store ONLY the hash in the DB:
hash_ = hashlib.sha256(token.encode()).hexdigest()
# Expire in 15-30 minutes
# Why hash: a DB leak does not expose the live reset tokens.
# ============================================================
# Drill 6 — Choose a JWT algorithm for service-to-service
# ============================================================
# ANSWER: RS256 or EdDSA (Ed25519). Hard-code the expected algorithm at the
# verifier. Pin issuer + audience.
import jwt
claims = jwt.decode(token, public_key, algorithms=['RS256'],
audience='shop-api', issuer='https://api.example.com')
# NEVER: jwt.decode(token, key, algorithms=None) — alg-confusion attacks.
# ============================================================
# Bonus — encrypt a 1GB file
# ============================================================
# ANSWER: streaming AEAD via libsodium secret_stream or AES-GCM-SIV in chunks.
# Single AES-GCM call is fine up to ~2^32 bytes but allocates the whole
# ciphertext in memory; stream for big files.
# ============================================================
# Scoring
# ============================================================
# 6 / 6 -> safe to design crypto features
# 4 / 6 -> bookmark crypto/cheatsheet
# < 4 -> read 'Cryptography Engineering' before writing crypto in production
Why it matters
Pick the highest-level primitive available (AEAD, password hasher, HKDF) and let the library hide the misuse-prone knobs. Hand-rolled "I know what Im doing" CBC + HMAC constructions have shipped half of the cryptographic CVEs of the last decade; the high-level APIs make those bugs impossible by construction.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Fill in: const hash = await argon2.____('hunter2', { type: argon2.argon2id });
Try it Yourself »
Discussion
Loading…