Lion Cipher Lab
Perform a TLS 1.3 handshake from scratch: hand-built ClientHello, X25519, the RFC 8446 key schedule, and decrypt the server's first encrypted record
- Foundations
- Advanced
- ~150 min
- Free · no account required
Why it matters
Almost everything you protect travels inside TLS, and almost nobody who relies on it can say what actually happens in the first two packets. You can read a handshake in Wireshark and still not be able to answer why the ServerHello claims to be TLS 1.2, where the encryption keys came from, or why the very next record is unreadable. That gap matters the moment something breaks: a handshake failure, a downgrade, a middlebox mangling records, a JA3 fingerprint you need to explain, a post-quantum group negotiation that silently falls back. The fastest way to close it permanently is to build the…
What you'll learn
- building a TLS 1.3 ClientHello byte by byte: TLSPlaintext record header, Handshake header, legacy_version, client random, the middlebox-compatibility legacy_session_id, cipher suite TLS_AES_128_GCM_SHA256
- the five extensions that make a TLS 1.3 hello legal: supported_versions, supported_groups, key_share, signature_algorithms and server_name (SNI)
- length-prefix arithmetic — the u8/u16/u24 nesting that kills almost every first attempt at hand-building a hello
- implementing X25519 with the RFC 7748 Montgomery ladder in pure Python, verified against a published key pair
- implementing HKDF-Extract and HKDF-Expand (RFC 5869) on hmac + hashlib, then HKDF-Expand-Label and Derive-Secret from RFC 8446
- running the full early -> handshake -> master key schedule down to client/server handshake traffic secrets and the per-direction key and iv
- the record-layer AEAD: additional_data is the 5-byte record header, and the per-record nonce is iv XOR the sequence number
- decrypting a real server's EncryptedExtensions with keys you derived yourself
- proving correctness offline against the byte-exact vectors in RFC 8448 rather than trusting a live connection
- named failure modes: the CH||SH transcript off-by-one, the missing 'tls13 ' label prefix, the nonce XOR, the fake change_cipher_spec record, HelloRetryRequest, and the inner content-type byte
Steps in this lab
- Where this sits: you already have the primitive, now build the protocol
- Open a terminal and set up (zero assumptions)
- Write the library: X25519, HKDF, the key schedule, the ClientHello builder
- Length prefixes: the thing that kills every first attempt
- Why the hello is full of lies: legacy_version, legacy_session_id, and supported_versions
- Write the RFC 8448 vector check — the offline, non-circular proof
- Run the vector check and read the real output
- The named failure modes, in the order they will bite you
- Now go live: send your hand-built ClientHello to a real server
- Run the live handshake and read what came back
- Why this, not that: what TLS 1.3 deleted, and why the deletions are the feature
- When NOT to do this, and how a pro does it differently
- Extend it: three things worth doing next