TLS Handshake
TLS Key Derivation
Read aloud is unavailable in this browser.
From Handshake to Encrypted Communication
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 uniqueness: Every connection has different encryption keys
- Key independence: Compromise of one key doesn't reveal others
- Key binding: Keys are bound to the specific handshake that occurred
- Forward secrecy: Session keys cannot be derived retrospectively
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.
The Key Derivation Problem
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:
- Client-to-server encryption
- Server-to-client encryption
- (In TLS 1.2 with non-AEAD) Client MAC key
- (In TLS 1.2 with non-AEAD) Server MAC key
- Initialization vectors for both directions
-
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:
- Deterministic: Same inputs always produce same outputs
- Pseudorandom: Output is indistinguishable from random to anyone without the inputs
- Arbitrary output length: Can produce as many bytes as needed
- One-way: Cannot recover inputs from outputs
- •Shared secret — From key exchange (ECDHE point, DHE value, or RSA pre-master secret)
- •Client random — 32 bytes from ClientHello (freshness, replay prevention)
- •Server random — 32 bytes from ServerHello (freshness, replay prevention)
- •Handshake transcript — Hash of handshake messages (TLS 1.3 only, binds keys to specific handshake)
- •Labels — ASCII strings identifying key purpose (domain separation)
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 Key Derivation: The PRF
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:
- RSA: 48-byte random value generated by client, encrypted to server
- DHE/ECDHE: Result of Diffie-Hellman computation
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:
- Cipher suites ending in SHA256 use SHA-256
- Cipher suites ending in SHA384 use SHA-384
| 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 Key Derivation: HKDF and Key Schedule
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)
- IKM: Input Key Material (the raw shared secret)
- salt: Optional randomization (often previous PRK in TLS 1.3)
- PRK: Pseudorandom Key (concentrated randomness)
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.
Mathematical Security Properties
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:
- Different labels: "client write key" vs "server write key"
- Provable security: Under the random oracle model, different labels produce independent outputs
- •IKM Extraction: Random salt + weak IKM → strong PRK
- •Key Expansion: PRK + labels → unlimited pseudorandom keys
- •Domain Separation: Different labels → independent keys
- •Forward Secrecy: Derived keys reveal nothing about IKM
- •Standard-Model Proof: Security reduces to HMAC assumptions
- •Staged Secrets: Early → Handshake → Master progression
- •Transcript Binding: Keys bound to specific handshake
- •Post-Compromise Security: Key updates limit breach impact
- •PSK Mixing: Pre-shared key properly combined with DHE
- •Exportable Keys: External applications can derive keys safely
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.
Key Material Usage in the Record Layer
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:
- Key: The derived write key (AES-256 = 32 bytes)
- Nonce: Unique per-record value
- Plaintext: The data to encrypt (plus content type in TLS 1.3)
- Additional Data: Authenticated but not encrypted (sequence number, record type)
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:
- Incremented for each record
- Never repeated (resets only on key update)
- Implicitly synchronized between parties
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.
Key Updates and Traffic Secret Evolution
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:
- Sender immediately switches to new keys (cannot be retracted)
- If
update_requested, the receiver must also update its sending keys
Deriving 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:
- Forward secrecy within connection: Old keys cannot be derived from new keys
- Post-compromise recovery: If an attacker learns key_N, a key update produces key_N+1 they don't know
- Efficient: No asymmetric operations required, just HKDF expand
When to Update Keys:
Key updates should occur:
- Periodically: After a certain number of records or bytes (prevents sequence number overflow)
- After sensitive operations: After transmitting particularly sensitive data
- Proactively: In response to potential compromise indicators
AES-GCM Limits:
AES-GCM has cryptographic limits on usage with a single key:
- Safety limit: 2^24.5 encryptions (~23 million records)
- Confidentiality limit: 2^32.5 blocks of plaintext
After these limits, collision probability becomes non-negligible. Key updates reset the counters.
Implementation Considerations:
Key updates require careful implementation:
- Both sides must agree on when the transition occurs
- Out-of-order delivery (if buffering occurs) must be handled
- Old keys should be securely deleted after transition
- Sequence numbers reset to 0 after key update
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.
Exporter and Resumption Secrets
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:
- EAP-TLS: Deriving session keys for network authentication
- Token binding: Binding tokens to the TLS channel
- Application-layer authentication: Proving a message came through a specific TLS session
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.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.