Step 1 of 5 · Reading · ~2 min
Read
Cryptographic Primitives
HKDF-Expand-Label & Derive-Secret
The raw HMAC counter loop you just built (HKDF-Expand) is too easy to misuse — different parts of the protocol could accidentally derive the same key. RFC 8446 §7.1 wraps it in a labelled variant that every TLS 1.3 derivation goes through.
HKDF-Expand-Label
struct HkdfLabel {
uint16 length;
opaque label<7..255>; // ASCII bytes of "tls13 " || label
opaque context<0..255>; // 0..255 bytes
};
HKDF-Expand-Label(Secret, Label, Context, Length) =
HKDF-Expand(Secret, HkdfLabel, Length)
Three things to notice:
- The 6-byte ASCII prefix
tls13(literal, with the trailing space, no quotes) is part of the label every time. It cryptographically separates TLS 1.3 from any other HKDF user that might share the same secret. - The two opaque fields are length-prefixed (1 byte each) — classic TLS-style framing.
lengthis encoded big-endian in the first 2 bytes, and used as the HKDF-Expand output length.
Derive-Secret
The protocol uses one more helper, almost always with length=32 (SHA-256 output size):
Derive-Secret(Secret, Label, Messages) =
HKDF-Expand-Label(Secret, Label, Hash(Messages), Hash.length)
Hash(Messages) is the transcript hash of every handshake message so far. That ties every derived secret to the exact sequence of bytes both parties have observed — a Finished MAC over a hand-rolled transcript will not match.
The canonical labels
"derived" // step between branches of the key schedule
"c hs traffic" // client handshake traffic secret
"s hs traffic" // server handshake traffic secret
"c ap traffic" // client application traffic secret
"s ap traffic" // server application traffic secret
"finished" // input to the Finished MAC key
"key" // per-direction AEAD key
"iv" // per-direction static IV
"res master" // resumption master secret
"exp master" // exporter master secret
"client early traffic" // 0-RTT
"e exp master" // 0-RTT exporter
"resumption" // PSK from previous session
"res binder" // PSK binder key
Worked example (RFC 8448 §3)
Early Secret = HKDF-Extract(salt=0, IKM=0):
33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a
Then Derive-Secret(early, "derived", ""):
6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba
That second value is the salt that goes into HKDF-Extract for the Handshake Secret. The whole key schedule is just one HkdfLabel struct after another.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…