TLS 1.2 used a custom PRF (Pseudorandom Function) to derive keys. TLS 1.3 replaced it with HKDF (HMAC-based Key Derivation Function), a well-studied, standardized construction. The key schedule is more structured and produces keys at different stages of the handshake.
Think of it like making a cocktail in stages. You start with a base spirit (the PSK, if you have one). Then you add a mixer (the ECDHE shared secret). Then you add a garnish (the handshake transcript). At each stage, the drink changes character, and you can pour off a glass for a specific purpose. The early stage gives you 0-RTT keys. The middle stage gives you handshake encryption keys. The final stage gives you the application data keys.
TLS 1.3 derives keys in three stages, each building on the previous:
graph TD
A[Early Secret] -->|derived from PSK or 0| B[Handshake Secret]
B -->|derived from ECDHE shared secret| C[Master Secret]
C -->|derived from handshake transcript| D[Application Traffic Keys]
A --> E[0-RTT keys - if using early data]
B --> F[Handshake traffic keys]
C --> D
Early Secret: Derived from a Pre-Shared Key (PSK) if one exists (for session resumption or 0-RTT). If there’s no PSK, a zero value is used. This stage produces the keys for 0-RTT early data.
Handshake Secret: Derived by combining the Early Secret with the ECDHE shared secret. This produces the keys used to encrypt the rest of the handshake (the server’s certificate, CertificateVerify, and Finished messages).
Master Secret: Derived from the Handshake Secret. This produces the application traffic keys used to encrypt all application data after the handshake.
Each stage incorporates more information. The Early Secret uses only the PSK (if any). The Handshake Secret adds the ECDHE shared secret. The Master Secret adds the full handshake transcript. This means the application traffic keys depend on everything: the PSK, the ECDHE exchange, and every handshake message. Tampering with any part changes the keys.
The staged approach also enables the encrypted handshake. Once the Handshake Secret is derived (after the key shares are exchanged), the handshake traffic keys are available, and the rest of the handshake can be encrypted.
HKDF has two operations:
HKDF-Extract: Takes raw input key material and a salt, and produces a clean, uniformly random pseudorandom key. Think of it like a juice press. You feed in raw fruit (the messy shared secret from the Diffie-Hellman exchange, which has mathematical structure and isn’t uniformly random) and get concentrated juice (a clean key that looks random). The salt adds extra randomness to the process.
HKDF-Expand: Takes the clean pseudorandom key and a label, and produces output key material of any length. Think of it like a dispenser. You take the concentrated juice and pour it into separate labeled glasses: one for the client write key, one for the server write key, one for the client IV, one for the server IV. Each glass gets a different portion derived from the same concentrate. The labels are literal strings (like “c hs traffic” for client handshake traffic) that ensure each derived key is unique, even though they all come from the same source material.
The key schedule alternates between Extract (to combine secrets at each stage) and Expand (to derive the specific keys needed at that stage). This structured approach means every key in the system is derived through a well-defined, auditable chain of operations.