Day 5 of 5
⏱ ~60 minutes
Cryptography in 5 Days — Day 5

Practical Cryptography Applications

Raw cryptographic primitives must be combined correctly to build secure systems. Today covers crypto pitfalls, real-world applications (JWT, SSH, PGP, end-to-end encryption), and post-quantum cryptography.

JWT Security

JSON Web Tokens (JWT) encode claims in a Base64-signed format. Security pitfalls: the 'none' algorithm attack (set alg:none and remove the signature — broken libraries accept it), RS256 to HS256 confusion (using the public key as an HMAC secret), weak secrets, and long expiry times. Use RS256 or ES256 (asymmetric signing) for production. Validate algorithm explicitly, never trust the token's header.

End-to-End Encryption Design

E2E encryption means the server cannot read message content. Signal Protocol (used by WhatsApp, Signal, iMessage) uses: X3DH (Extended Triple Diffie-Hellman) for key agreement and the Double Ratchet algorithm for forward secrecy + break-in recovery. Each message uses a different key. Compromise of one message key reveals nothing about past or future messages.

Post-Quantum Cryptography

Quantum computers running Shor's algorithm would break RSA and ECC in polynomial time. NIST finalized its post-quantum standards in 2024: ML-KEM (CRYSTALS-Kyber) for key encapsulation and ML-DSA (CRYSTALS-Dilithium) for digital signatures. Both are based on hard lattice problems that quantum computers cannot efficiently solve. Migrate long-lived systems to hybrid classical+PQC schemes now.

python
import jwt  # PyJWT library
from datetime import datetime, timedelta, timezone
import secrets

# WRONG: using HS256 with a weak secret (never do this)
# token = jwt.encode({'sub': 'user123'}, 'secret', algorithm='HS256')

# RIGHT: RS256 with proper expiry
# Generate key: openssl genrsa -out private.pem 2048
# Extract public: openssl rsa -in private.pem -pubout -out public.pem

from cryptography.hazmat.primitives.serialization import load_pem_private_key
with open('private.pem', 'rb') as f:
    private_key = load_pem_private_key(f.read(), password=None)

payload = {
    'sub': '[email protected]',
    'iat': datetime.now(timezone.utc),
    'exp': datetime.now(timezone.utc) + timedelta(hours=1),
    'jti': secrets.token_hex(16)  # Unique token ID prevents replay
}
token = jwt.encode(payload, private_key, algorithm='RS256')
print(f'JWT: {token[:50]}...')

# Validate: always specify allowed algorithms
with open('public.pem', 'rb') as f:
    public_key = f.read()
claims = jwt.decode(token, public_key, algorithms=['RS256'])
print(f'Claims: {claims}')
💡
When decoding JWTs, always pass the algorithms parameter explicitly. Libraries that use the algorithm from the token header are vulnerable to algorithm confusion attacks.
📝 Day 5 Exercise
Build a Secure JWT Auth System
  1. Generate an RSA-2048 key pair with OpenSSL
  2. Write a JWT issuance function using RS256 with 1-hour expiry and jti claim
  3. Write a validation function that explicitly allows only RS256
  4. Test the 'none' algorithm attack — pass alg:none and verify your code rejects it
  5. Add token revocation by storing jti values in a Redis set and checking on each validation

Day 5 Summary

  • JWTs must use asymmetric signing (RS256/ES256) in production
  • Always specify allowed algorithms explicitly when validating JWTs
  • Signal Protocol's Double Ratchet provides forward secrecy per-message
  • NIST PQC standards (ML-KEM, ML-DSA) protect against quantum computer attacks
  • Migrate to hybrid classical+PQC schemes for long-lived cryptographic systems
Challenge

Audit a JWT implementation for the top 5 OWASP JWT security weaknesses. Write a report documenting each weakness, a test payload that exploits it, and the correct fix.

Finished this lesson?