Learning objective. Trace how TLS 1.3 secrets become the exact keys that protect a
QUIC packet: the key, IV, and header-protection key at each encryption level. Then the
cipher line and the four secrets in the §1.5 keylog make sense.
TLS provides secrets; QUIC provides protection #
QUIC uses the TLS 1.3 handshake but not the TLS record layer ([RFC 9001 §3]). TLS
runs its key schedule and hands QUIC a secret for each encryption level and
direction; QUIC then derives its own packet-protection keys from that secret and applies
them itself. The bridge is a single, repeated operation: HKDF-Expand-Label, the same
key-derivation function TLS 1.3 uses internally ([RFC 9001 §5.1]). From any level's
secret, QUIC derives three values with three fixed labels:
quic key→ the AEAD encryption key,quic iv→ the AEAD initialization vector,quic hp→ the header-protection key (§1.4).
Figure 2.2-1 puts the whole division of labour in one picture: where the secret comes from, the three keys QUIC derives from it, and the two operations that turn a plaintext packet into protected bytes on the wire. TLS's job ends at the secret; everything to the right of it is QUIC.
Bootstrapping the Initial keys #
The Initial level is special because there is no shared secret yet. Both sides compute
initial_secret = HKDF-Extract(initial_salt, client_DCID), where initial_salt is a
fixed constant per QUIC version (constants.md §1) and client_DCID is the Destination
Connection ID from the client's first packet — the unpredictable value the client itself
generates before it knows the server's CID, not a CID that names the client
(§1.4; [RFC 9001 §5.2]). From that they derive
client_initial_secret and server_initial_secret using the labels client in and
server in, and from each of those, the key/IV/hp triple. The hash is SHA-256 and
the AEAD is AEAD_AES_128_GCM for all Initial packets. Because the salt is public and
the DCID is on the wire in the clear, anyone can derive Initial keys. They provide
integrity and a consistent format, not confidentiality. That is exactly why the real
handshake content moves to Handshake packets as soon as TLS-derived keys exist
(§2.1). Figure 2.2-2 zooms into that derivation step (the same one that sits
in the middle of Figure 2.2-1), showing how the Initial bootstrap and the TLS secrets each
feed the identical key/IV/hp expansion.
Protecting a packet #
The two operations in the right half of Figure 2.2-1 (turning a plaintext packet into protected bytes) are, in detail ([RFC 9001 §5.3], §5.4):
- AEAD encryption. The nonce is the IV combined with the packet number: the packet number is left-padded with zeros to the IV's length and XORed with the IV ([RFC 9001 §5.3]). The AEAD encrypts the payload and authenticates the header as associated data. Because the nonce depends on the packet number, reusing a packet number with the same key would be catastrophic, one more reason packet numbers never repeat within a space (§1.2).
- Header protection. A sample of the ciphertext is run through the header-protection
key to produce a mask, and that mask is XORed over the packet-number bytes and a few
header bits:
protected_field = field XOR PRF(hp_key, sample)([RFC 9001 §5.4]). This hides the packet number even from an observer who can see the packet, and it is applied after AEAD on send and removed before AEAD on receive.
Key update #
After the handshake is confirmed, either endpoint may rotate 1-RTT keys. It derives the
next secret from the current one (secret_<n+1> = HKDF-Expand-Label(secret_<n>, "quic ku", "", 32)), re-derives key and IV, and flips the Key Phase bit in the short
header so the peer knows which generation protects the packet ([RFC 9001 §6]). The
header-protection key does not change on a key update. Updates let a connection stay
within an AEAD's usage limits, discussed next.
The four secrets written to the keylog in the §1.5 lab are precisely the per-level, per-direction traffic secrets: client/server for Handshake and 1-RTT. Wireshark feeds them back into this same derivation to reconstruct the keys and decrypt the capture; nothing else about the algorithm is secret (§11.3).
editorial An AEAD key cannot protect unlimited data.
QUIC caps the packets encrypted (confidentiality limit) and forged packets tolerated
(integrity limit) per key; exceeding them requires a key update or closing the connection
with AEAD_LIMIT_REACHED (0x0f) ([RFC 9001 §6.6]). For AES-128-GCM these limits are
enormous, so routine key updates are about hygiene and forward secrecy, not imminent
overflow, though very long-lived, very high-rate connections should update keys
periodically rather than never.
Takeaways #
QUIC's packet protection is TLS 1.3's key schedule with three extra labels and QUIC's own AEAD-plus-header-protection applied per packet. Initial keys bootstrap from a public salt for format, not secrecy; everything after comes from TLS and is genuinely confidential. With protected packets flowing, the next question is whether the client can skip a round trip entirely. §2.3 covers 0-RTT and the replay risk it carries.