Loading content...
The TLS handshake culminates in key derivation—the process of transforming the handshake's cryptographic outputs into the symmetric keys used for encrypted communication. This transformation is not merely technical housekeeping; it's a carefully designed process that ensures:
Key derivation functions (KDFs) are the unsung heroes of TLS security. They take the raw outputs of key exchange and generate the multiple keys needed for a secure session: client and server write keys, initialization vectors, and more.
This page examines how TLS transforms a shared secret into a complete set of session keys, the mathematical properties that make this secure, and the differences between TLS 1.2 and TLS 1.3 key derivation.
This page provides a comprehensive examination of TLS key derivation. You will understand the inputs to key derivation, the PRF in TLS 1.2, HKDF in TLS 1.3, how multiple keys are derived from a single shared secret, and the security properties this process provides.
After key exchange completes, both parties possess a shared secret. But this shared secret cannot be used directly as an encryption key. Several transformations are required:
Why Not Use the Shared Secret Directly?
Wrong size: ECDHE produces a point on an elliptic curve (32-65 bytes depending on curve). AES needs exactly 16, 24, or 32 bytes for the key.
Multiple keys needed: TLS requires separate keys for:
Biased output: DH shared secrets may have statistical properties (leading zeros, etc.) that make them poor direct keys.
Key separation: Using the same key for multiple purposes is cryptographically dangerous. Different keys for different functions provides defense in depth.
The Solution: Key Derivation Functions (KDFs)
A KDF takes input key material (IKM) and produces output key material (OKM) with the following properties:
Labels in key derivation provide 'domain separation'—they ensure keys derived for different purposes are cryptographically independent. Even with the same shared secret, the label 'client write key' produces a completely different key than 'server write key'. This is essential for security proofs.
TLS 1.2 uses a Pseudorandom Function (PRF) based on HMAC for key derivation. The PRF is defined in RFC 5246 and uses the hash algorithm from the cipher suite.
The Two-Stage Process:
TLS 1.2 key derivation occurs in two stages:
Stage 1: Master Secret Derivation
Combine the pre-master secret with the random values:
master_secret = PRF(pre_master_secret,
"master secret",
client_random + server_random)
[0..47] // 48 bytes
The pre-master secret depends on the key exchange:
Stage 2: Key Block Generation
Expand the master secret into all needed keys:
key_block = PRF(master_secret,
"key expansion",
server_random + client_random)
Note the order changes: server_random comes first in key expansion.
The key_block is sliced into individual keys:
key_block = client_write_MAC_key +
server_write_MAC_key +
client_write_key +
server_write_key +
client_write_IV +
server_write_IV
The PRF Construction (P_hash):
The TLS 1.2 PRF is built from HMAC in a iterative construction:
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
HMAC_hash(secret, A(2) + seed) +
HMAC_hash(secret, A(3) + seed) + ...
where:
A(0) = seed
A(i) = HMAC_hash(secret, A(i-1))
This construction, called HMAC-based Extract-and-Expand Key Derivation Function (HKDF) predecessor, iterates until enough key material is generated.
The PRF function:
PRF(secret, label, seed) = P_hash(secret, label + seed)
The hash function is determined by the cipher suite:
| Key Material | Length | Purpose |
|---|---|---|
| client_write_MAC_key | 0 (GCM is AEAD) | Not used with AEAD ciphers |
| server_write_MAC_key | 0 (GCM is AEAD) | Not used with AEAD ciphers |
| client_write_key | 32 bytes | AES-256 key for client→server |
| server_write_key | 32 bytes | AES-256 key for server→client |
| client_write_IV | 4 bytes | Implicit nonce for client→server |
| server_write_IV | 4 bytes | Implicit nonce for server→client |
With AEAD ciphers (AES-GCM, ChaCha20-Poly1305), the MAC keys are not needed—authentication is built into the cipher. This halves the key material size compared to CBC+HMAC cipher suites and eliminates MAC key management complexity.
TLS 1.3 completely redesigned key derivation using HKDF (HMAC-based Extract-and-Expand Key Derivation Function), defined in RFC 5869. The result is a more structured, provably secure key schedule.
HKDF Primitives:
HKDF consists of two operations:
1. HKDF-Extract(salt, IKM) → PRK
Extracts a pseudorandom key (PRK) from input key material:
HKDF-Extract(salt, IKM) = HMAC-Hash(salt, IKM)
2. HKDF-Expand-Label(Secret, Label, Context, Length) → OKM
Expands a PRK into output key material:
HKDF-Expand-Label(Secret, Label, Context, Length) =
HKDF-Expand(Secret,
HkdfLabel,
Length)
struct HkdfLabel {
uint16 length = Length;
opaque label<7..255> = "tls13 " + Label;
opaque context<0..255> = Context;
};
Derive-Secret:
TLS 1.3 defines a helper that binds keys to the handshake transcript:
Derive-Secret(Secret, Label, Messages) =
HKDF-Expand-Label(Secret, Label, Transcript-Hash(Messages), Hash.length)
Transcript-Hash is a running hash of all handshake messages up to that point.
The Key Schedule Progression:
TLS 1.3's key schedule progresses through three secrets:
1. Early Secret (0-RTT key material)
early_secret = HKDF-Extract(salt=0, IKM=PSK or 0)
Used for early data (0-RTT) when resuming with a PSK.
2. Handshake Secret (handshake encryption)
handshake_secret = HKDF-Extract(salt=Derive-Secret(early_secret, "derived", ""),
IKM=ECDHE_shared_secret)
Used to encrypt the handshake messages after ServerHello.
3. Master Secret (application data)
master_secret = HKDF-Extract(salt=Derive-Secret(handshake_secret, "derived", ""),
IKM=0)
Used to encrypt all application data after the handshake completes.
Traffic Secrets to Keys:
Traffic secrets are expanded into actual keys and IVs:
key = HKDF-Expand-Label(traffic_secret, "key", "", key_length)
iv = HKDF-Expand-Label(traffic_secret, "iv", "", iv_length)
For AES-256-GCM: key_length = 32, iv_length = 12
TLS 1.3 binds traffic secrets to the handshake transcript. The context in Derive-Secret includes a hash of all handshake messages up to that point. This means the keys used for application data cryptographically prove that a specific handshake occurred—if an attacker tampered with any handshake message, the keys won't match.
Key derivation functions must satisfy specific mathematical properties to provide security. These properties have been formally analyzed and proven for TLS's key derivation.
Pseudorandomness:
The output of the KDF must be indistinguishable from random to anyone who doesn't know the inputs. Formally:
For any efficient adversary A:
Pr[A distinguishes KDF(secret, label) from random] ≤ negligible
This is proven based on the pseudorandomness of HMAC, which relies on the underlying hash function's properties.
Key Independence:
Different keys derived from the same secret must be independent. Knowing client_write_key should tell you nothing about server_write_key. This is achieved through:
Why Random Values Matter:
The client and server randoms serve critical security functions:
1. Freshness: Even if the same key exchange happens (same ECDH shares, same certificate), different randoms produce different keys. This prevents caching attacks.
2. Replay Prevention: An attacker can't replay a recorded handshake—they'd need to predict the server's random to derive matching keys.
3. Entropy Mixing: If the ECDHE implementation has weaknesses (insufficient randomness), randoms provide additional entropy. If the random generator is weak but ECDHE is strong, the combined result is still secure.
The Downgrade Sentinel (TLS 1.3):
TLS 1.3 servers embed a special value in server_random when downgrading to TLS 1.2:
server_random[24..31] = 0x44 0x4F 0x57 0x4E 0x47 0x52 0x44 0x01 // "DOWNGRD" + 0x01
Clients check for this sentinel. If present in a TLS 1.2 connection when TLS 1.3 was offered, it indicates a downgrade attack. This cryptographic "tripwire" catches attackers who force protocol downgrades.
Predictable randoms break TLS completely. The Debian OpenSSL weak key bug (2006-2008) reduced key entropy to 15 bits due to a random number generator bug. All keys generated on affected systems were trivially breakable. Cryptographic random number generation is not optional—it's foundational.
Once derived, keys are used in the TLS Record Layer to encrypt application data. Understanding how keys are applied reveals why separate keys and IVs are necessary.
AEAD Construction (TLS 1.2 and 1.3):
Modern TLS uses Authenticated Encryption with Associated Data (AEAD). The key structure:
Ciphertext = AEAD-Encrypt(key, nonce, plaintext, additional_data)
Plaintext = AEAD-Decrypt(key, nonce, ciphertext, additional_data)
Components:
Nonce Construction:
The nonce must never repeat with the same key—doing so catastrophically breaks AES-GCM security. TLS uses a construction that guarantees uniqueness:
TLS 1.2 (explicit nonce):
nonce = write_IV (4 bytes, from key derivation) +
explicit_nonce (8 bytes, sent with each record)
The explicit nonce is typically the sequence number, sent in the clear.
TLS 1.3 (implicit nonce):
nonce = write_IV (12 bytes, from key derivation) XOR
padded_sequence_number (64-bit sequence, padded to 12 bytes)
No nonce transmitted—both sides track the sequence number.
Why This Works:
The sequence number is:
XORing with the IV provides privacy (sequence not revealed) while maintaining uniqueness.
Record Layer Encryption (TLS 1.3):
struct {
ContentType opaque_type = application_data; // Always 23
ProtocolVersion legacy_record_version = 0x0303; // Always TLS 1.2
uint16 length;
opaque encrypted_record[length];
} TLSCiphertext;
// encrypted_record contains:
AEAD-Encrypt(key, nonce,
plaintext + content_type + padding,
TLSCiphertext_header)
The actual content type (handshake, application_data, alert) is encrypted inside the record, not visible to observers.
| Traffic Direction | Key Used | IV/Nonce | Sequence Counter |
|---|---|---|---|
| Client → Server | client_write_key | client_write_IV XOR seq | Client tracks, increments per record |
| Server → Client | server_write_key | server_write_IV XOR seq | Server tracks, increments per record |
| Early Data (0-RTT) | client_early_traffic_key | From early_traffic_secret | Separate sequence space |
| After KeyUpdate | Updated keys via traffic_secret_N | Same IV, new sequence | Sequence resets to 0 |
Using different keys for each direction prevents reflection attacks where an attacker bounces a client's encrypted message back to them. With separate keys, server-encrypted data cannot be misinterpreted as client-encrypted data.
TLS 1.3 introduced the ability to update encryption keys during a connection without a full handshake. This provides post-compromise security—if keys are compromised, updating them restores security for future traffic.
The KeyUpdate Message:
Either party can send a KeyUpdate message:
enum { update_not_requested(0), update_requested(1) } KeyUpdateRequest;
struct {
KeyUpdateRequest request_update;
} KeyUpdate;
After sending KeyUpdate:
update_requested, the receiver must also update its sending keysDeriving Updated Traffic Secrets:
New keys are derived from the previous traffic secret:
application_traffic_secret_N+1 =
HKDF-Expand-Label(application_traffic_secret_N,
"traffic upd", "", Hash.length)
No new key exchange occurs—keys evolve mathematically from the previous state.
Security Properties:
When to Update Keys:
Key updates should occur:
AES-GCM Limits:
AES-GCM has cryptographic limits on usage with a single key:
After these limits, collision probability becomes non-negligible. Key updates reset the counters.
Implementation Considerations:
Key updates require careful implementation:
TLS 1.3 early data (0-RTT) uses keys derived from the PSK, before the new ECDHE key exchange. These keys provide forward secrecy relative to the PSK, but NOT against a PSK compromise. This is why 0-RTT data must be idempotent—replay is possible if the PSK is compromised. The handshake keys immediately after ServerHello incorporate fresh ECDHE, providing full forward secrecy for the main connection.
Beyond traffic encryption, TLS key derivation produces secrets for external use and session resumption.
Exporter Secrets:
Applications sometimes need keying material derived from the TLS session—for example, to authenticate channel bindings in higher-layer protocols.
exporter_master_secret = Derive-Secret(master_secret, "exp master", Transcript-Hash)
Applications derive keys using:
exported_key = HKDF-Expand-Label(exporter_master_secret,
label, context, length)
Use Cases:
Resumption Master Secret:
For session resumption without a full handshake:
resumption_master_secret = Derive-Secret(master_secret,
"res master",
Transcript-Hash)
This secret is used to derive PSKs for future connections:
PSK = HKDF-Expand-Label(resumption_master_secret,
"resumption",
ticket_nonce,
Hash.length)
The ticket_nonce ensures each ticket produces a different PSK, even from the same resumption_master_secret.
| Secret | Derived From | Purpose | Transcript Bound? |
|---|---|---|---|
| binder_key | Early Secret | PSK binder calculation | No |
| client_early_traffic_secret | Early Secret | 0-RTT data encryption | Partial (CH only) |
| client_handshake_traffic_secret | Handshake Secret | Client handshake messages | Yes (CH + SH) |
| server_handshake_traffic_secret | Handshake Secret | Server handshake messages | Yes (CH + SH) |
| client_application_traffic_secret | Master Secret | Client application data | Yes (full handshake) |
| server_application_traffic_secret | Master Secret | Server application data | Yes (full handshake) |
| exporter_master_secret | Master Secret | External key export | Yes (full handshake) |
| resumption_master_secret | Master Secret | Session resumption tickets | Yes (full handshake) |
Every secret after the Hello phase is bound to the transcript hash. This means an attacker cannot mix-and-match handshake messages—the keys simply won't match if any message differs. This is a powerful protection against transcript manipulation attacks that plagued earlier TLS versions.
Key derivation transforms the handshake's raw cryptographic outputs into the polished keys that protect every byte of TLS traffic. It's the mathematical machinery that ensures each connection is uniquely secured.
What's Next:
With key derivation understood, we explore the final handshake optimization: session resumption. The next page examines how TLS avoids repeating full handshakes for returning clients, the security properties of resumption, and the 0-RTT early data feature in TLS 1.3.
You now understand TLS key derivation — the cryptographic machinery that transforms handshake outputs into secure session keys. This knowledge enables you to understand TLS internals deeply, evaluate cryptographic library implementations, and appreciate why TLS provides the security guarantees it does.