A traffic secret (e.g. client_application_traffic_secret_0, a 32-byte pseudorandom value) isn’t yet usable by AES-GCM. AEAD needs two concrete things: a key and an IV. One more Expand each:


Two things to notice: the "key" and "iv" labels make the two outputs cryptographically independent (Expand clones by label), and the context is empty ("") — the transcript-binding already happened upstream when the secret was derived.

Now the per-record nonce. The IV is not the nonce; it’s the ingredient the nonce is built from:

where n is the record sequence number (0, 1, 2, …), a per-direction counter, left-padded to the IV length.

This is elegant for two reasons:

  • The sequence number is never sent. It’s implicit: both sides count records in order over the reliable TCP stream and independently compute the same nonce — zero bytes on the wire.
  • Every record gets a distinct nonce automatically, because n increments each time.

The counter resets to 0 each time the key changes (handshake keys, application keys, each KeyUpdate epoch each start fresh at 0). That’s safe because each epoch uses a different key, so (key, nonce) pairs still never collide.

See also

References

Questions

flashcards/software-engineering/tls

How is a TLS record’s per-record nonce constructed?::write_iv XOR the record sequence number (an implicit per-direction counter 0,1,2,…)

Why is the record sequence number never sent on the wire?::Both sides count records in order over the reliable TCP stream, so each independently computes the same nonce

How are write_key and write_iv derived from a traffic secret?::Two HKDF-Expand-Label calls with labels “key”/“iv” and an empty context (the transcript was already bound upstream)

The record sequence counter resets to 0 each time the key changes, which is safe because each epoch uses a different key.