HKDF: How TLS 1.3 Grows One Shared Secret Into a Whole Key Tree
The problem: one secret, one tree of keys
When a TLS handshake completes, both sides hold a shared secret (the X25519 output, or the two secrets concatenated in the ML-KEM hybrid group). But a TLS session needs far more than one key:
- Client and server handshake-stage write keys and IVs (protecting the Finished messages)
- Client and server application-data write keys and IVs
- Per-stage Finished verification keys
- The exporter interface (key material for upper layers)
- Resumption PSKs and ticket keys
And these keys must be mutually independent — leaking an application-data key must not endanger handshake integrity, and old session keys must not help attack new ones. Growing one secret into a tree, safely, is HKDF's job.
Two steps: Extract converges, Expand branches
HKDF (RFC 5869) is built on HMAC, in two phases:
Extract: PRK = HMAC(salt, IKM) — mix the input keying material (whose distribution may be uneven) with a salt, converging it into a pseudorandom key of uniform cryptographic quality. The salt provides domain separation: different purposes, different salts, different PRKs even from identical IKM.
Expand: OKM = HKDF-Expand(PRK, info, L) — derive output keying material of any length, labeled by info. The info field says exactly what this key "is, for whom, for what".
Why not just hash(secret || label)? Three hard reasons: length-extension-style structural weaknesses (depending on the hash's construction), no safe way to produce long outputs, and the decisive one — no built-in domain separation. The info mechanism guarantees "client write key" and "server write key" can never collide into the same key.
The TLS 1.3 three-layer schedule
TLS 1.3 uses HKDF as a scheduling tree:
Early Secret ← PSK (resumption path)
↓ Derive-Secret
Handshake Secret ← ECDHE/ML-KEM shared secret injected
↓ "c hs traffic" / "s hs traffic" → handshake-stage keys
Master Secret
↓ "c ap traffic" / "s ap traffic" → application-data keys
↓ "exp master" → exporter
↓ "res master" → resumption PSK
Each layer is one HKDF-Extract (injecting a new secret) plus multiple labeled Expands. The elegant property: each handshake stage only unlocks its own layer's keys — if handshake keys are compromised, application-layer keys still stand independently; meanwhile the Finished messages, keyed by handshake keys, cover the entire transcript so a man in the middle cannot tamper with negotiation.
Fast KDF vs. slow KDF: two things sharing one name
A common confusion: HKDF and PBKDF2/argon2 are all called "key derivation", yet they are opposites in purpose —
| HKDF | PBKDF2 / bcrypt / argon2 | |
|---|---|---|
| Input | Already high-entropy secrets (DH outputs) | Low-entropy human passwords |
| Design goal | Fast (session setup must not stall) | Slow (punish brute force) |
| Brute-force resistance | Not its job | Its core job |
Deriving from a password with HKDF, or deriving TLS session keys with argon2, are both category errors — the first has no defense at all, the second adds hundreds of milliseconds for nothing.
Implementer's note
HKDF is a first-class citizen in browser WebCrypto (crypto.subtle.deriveBits with the HKDF algorithm) — something I felt directly while building this site's tools: the HMAC primitive (the HMAC tool) and HKDF differ by only two thin wrappers — extract is one HMAC call, expand is a counter-based chain of them. The other side of the contrast is just as real: the AES tool must take the slow PBKDF2 path when deriving keys from passphrases (browser-measured at only tens of thousands of iterations per second — a feature, not a performance bug). The two derivation regimes coexist in one tool site, turning this division-of-labor table into something touchable.
Related reading
- HMAC guide (the primitive underneath)
- ML-KEM, explained (where the input secret comes from)
- AES encryption, explained (where derived keys go)
- Harvest now, decrypt later
Related Tools
Related Articles
Post-Quantum Migration Self-Check: From Algorithm Inventory to Hybrid TLS — 12 Things to Do in 2026
A practical self-check framework for PQC migration: why three overlapping regulatory timelines make 2026 the starting line, how to build a cryptographic bill of materials (CBOM), ranking by HNDL exposure, enabling X25519+ML-KEM hybrid TLS, and what crypto-agility means in practice. With a 12-item checklist.
PIPL PIA Self-Check: The Five Triggering Scenarios, Three Assessment Elements, and a Working Framework
China's Personal Information Protection Law (Articles 55-56) requires a prior Personal Information Protection Impact Assessment (PIA) for five categories of processing. This post covers the triggers, the statutory three-element report structure, the three-year retention requirement, and a data-map → risk-matrix → controls framework. With the MLPS/miping/PIA three-pillar relationship.
Passkeys, Explained: Why the Server Can No Longer Hold a Copy of Your Password
How passkeys (WebAuthn/FIDO2) work: public-key challenge-response replacing shared secrets, why phishing resistance is structural (origin binding), what servers storing only public keys actually means, synced vs. device-bound passkeys, and the three enterprise adoption hurdles (enrollment, multi-device, recovery). With a capability table against passwords and TOTP.