§2.2
Chapter 02 · QUIC Connection Establishment and Security Handshake

§2.2TLS 1.3 Integration and Key Derivation for QUIC

RFC 9001

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.

TLS 1.3 · provides secrets

QUIC · provides protection

'quic key' / 'quic iv'

'quic hp'

Initial secret
salt + client DCID

a level + direction secret

AEAD key + IV

header-protection key

packet: header + frames
(plaintext)

AEAD: encrypt + authenticate
nonce = IV XOR packet number

header protection:
mask packet number + flag bits

protected packet on the wire

handshake + 1-RTT
traffic secrets
(client + server)

Fig. 2.2-1The full path from secret to wire. TLS 1.3 — and, for Initial, QUIC's own bootstrap — supplies a per-level, per-direction secret; QUIC derives the AEAD key, IV, and header-protection key, then applies AEAD encryption (keyed by key + IV, with the packet number in the nonce) and header protection to turn a plaintext packet into the bytes on the wire.RFC 9001 §5; RFC 9000 §12.3

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.

'quic key'

'quic iv'

'quic hp'

From the TLS 1.3 key schedule — secret

handshake traffic secrets

application (1-RTT) traffic secrets

Initial bootstrap — computable by anyone

'client in'

'server in'

initial_salt (per QUIC version)
+ client's Destination CID

initial_secret
HKDF-Extract

client_initial_secret

server_initial_secret

a per-level, per-direction secret

AEAD key

AEAD IV

header-protection key

Fig. 2.2-2Zooming into the derivation step. Initial secrets are bootstrapped from a public salt and the client's DCID; Handshake and 1-RTT secrets come from TLS. Every secret yields the same key/IV/hp triple.RFC 9001 §5.1, §5.2

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):

  1. 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).
  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.

Note

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).

In practice

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.