iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Bootcamp

A 60-minute crypto bootcamp: build a small token-signing + verification service that ships every modern best practice in one repo.

A 60-minute crypto bootcamp

EXAMPLE
# ===== Objectives =====
# 1. Hash passwords with argon2id
# 2. Sign + verify JWTs with RS256
# 3. Encrypt sensitive cookie values with AES-GCM
# 4. Sign webhook bodies with HMAC + timestamp
# 5. Constant-time compare for all secret comparisons
# 6. Add unit tests that prove the misuse path is closed

# ===== Stack =====
# Python + cryptography + argon2-cffi + pyjwt
# pip install cryptography argon2-cffi pyjwt fastapi uvicorn pytest

# ===== 0-10 min: password hashing =====
# auth.py
import argon2

_hasher = argon2.PasswordHasher(time_cost=3, memory_cost=64*1024, parallelism=4)

def hash_password(p: str) -> str:
    return _hasher.hash(p)

def verify_password(stored: str, candidate: str) -> bool:
    try:
        _hasher.verify(stored, candidate)
        return True
    except argon2.exceptions.VerifyMismatchError:
        return False

# Tests
def test_password_round_trip():
    h = hash_password('hunter2')
    assert verify_password(h, 'hunter2')
    assert not verify_password(h, 'wrong')

# ===== 10-25 min: JWT signing + verification =====
# tokens.py
import jwt, time
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# In production these come from a vault; here we just generate at import time.
_priv = rsa.generate_private_key(public_exponent=65537, key_size=2048)
_pub  = _priv.public_key()

PRIVATE_KEY = _priv.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption(),
)
PUBLIC_KEY = _pub.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

ISSUER   = 'https://api.example.com'
AUDIENCE = 'shop-api'

def issue_access_token(sub: str) -> str:
    now = int(time.time())
    return jwt.encode(
        { 'sub': sub, 'iss': ISSUER, 'aud': AUDIENCE, 'iat': now, 'exp': now + 900 },
        PRIVATE_KEY, algorithm='RS256',
    )

def verify_access_token(token: str) -> dict:
    return jwt.decode(
        token, PUBLIC_KEY,
        algorithms=['RS256'],                # HARDCODED
        audience=AUDIENCE,
        issuer=ISSUER,
    )

# Tests
def test_token_round_trip():
    tok = issue_access_token('u1')
    claims = verify_access_token(tok)
    assert claims['sub'] == 'u1'

def test_token_rejects_alg_none():
    tok = jwt.encode({ 'sub': 'u1', 'aud': AUDIENCE, 'iss': ISSUER }, '', algorithm='none')
    try:
        verify_access_token(tok)
        assert False, 'should have raised'
    except jwt.InvalidAlgorithmError:
        pass

# ===== 25-40 min: AES-GCM cookie encryption =====
# cookies.py
import os, base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

COOKIE_KEY = AESGCM.generate_key(256)        # production: from env / KMS

def encrypt_cookie(plain: str, aad: bytes = b'cookie-v1') -> str:
    nonce = os.urandom(12)
    ct = AESGCM(COOKIE_KEY).encrypt(nonce, plain.encode(), aad)
    return base64.urlsafe_b64encode(nonce + ct).decode()

def decrypt_cookie(token: str, aad: bytes = b'cookie-v1') -> str:
    raw = base64.urlsafe_b64decode(token.encode())
    nonce, ct = raw[:12], raw[12:]
    return AESGCM(COOKIE_KEY).decrypt(nonce, ct, aad).decode()

def test_cookie_round_trip():
    enc = encrypt_cookie('session=abc')
    assert decrypt_cookie(enc) == 'session=abc'

# ===== 40-50 min: HMAC webhook signature =====
# webhook.py
import hmac, hashlib, time

WEBHOOK_SECRET = b'shared-with-the-receiver'

def sign_webhook(body: bytes, ts: int) -> str:
    return hmac.new(WEBHOOK_SECRET, f'{ts}.'.encode() + body, hashlib.sha256).hexdigest()

def verify_webhook(body: bytes, sig: str, ts: int) -> bool:
    if abs(int(time.time()) - ts) > 300:     # 5 min window
        return False
    expected = sign_webhook(body, ts)
    return hmac.compare_digest(expected, sig)

def test_webhook_signature():
    body = b'{"order":"o1"}'
    ts = int(time.time())
    sig = sign_webhook(body, ts)
    assert verify_webhook(body, sig, ts)
    # tampered body
    assert not verify_webhook(b'{"order":"o2"}', sig, ts)

# ===== 50-60 min: wire it up =====
# main.py — FastAPI app exposing the recipes
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel

app = FastAPI()

class LoginReq(BaseModel):
    username: str
    password: str

# In production look the hash up from a database.
_user_hash = hash_password('hunter2')

@app.post('/login')
def login(req: LoginReq):
    if req.username != 'alice' or not verify_password(_user_hash, req.password):
        raise HTTPException(401)
    return { 'access_token': issue_access_token('u1') }

@app.get('/me')
def me(authorization: str = Header(default='')):
    if not authorization.startswith('Bearer '):
        raise HTTPException(401)
    try:
        claims = verify_access_token(authorization[7:])
    except jwt.PyJWTError:
        raise HTTPException(401)
    return { 'sub': claims['sub'] }

# uvicorn main:app

# ===== Post-bootcamp checklist =====
# - password hashing: argon2id, not sha
# - JWT: algorithm pinned at decode
# - encryption: AEAD with random nonce per message
# - HMAC: constant-time compare; timestamp window
# - tests for the misuse path (alg=none, tampered body, wrong password)
# - secrets loaded from env / vault, not committed
# - kms / hsm for production keys

# ===== Pitfalls =====
# - hmac compare with == instead of hmac.compare_digest
# - issue_access_token without 'iat' / 'exp' / 'aud'
# - reusing the same nonce for AES-GCM (catastrophic)
# - rolling your own protocols when libsodium would do

Why it matters

A small repo that demonstrates argon2id + RS256 JWT + AES-GCM + HMAC + constant-time compare in 200 lines of code IS the cryptographic best-practice posture. Most production breaches stem from missing exactly one of these; ship them all once and the rest of your codebase has a clean reference to copy from.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// 30-day crypto-for-devs bootcamp in the lesson body.
Try it Yourself »

Discussion

Loading…