Understanding TLS 1.3 Handshakes
A deep dive into TLS 1.3's 1-RTT handshake, ECDHE key exchange, certificate chain validation, and 0-RTT session resumption with its security trade-offs.
Introduction
Transport Layer Security 1.3, finalized in RFC 8446 (August 2018), represents a fundamental redesign of the TLS handshake rather than an incremental update. The most impactful change is the reduction from TLS 1.2’s two round-trips (2-RTT) to a single round-trip (1-RTT) before application data can flow — achieved by eliminating legacy cipher suites, merging key exchange into the ClientHello, and removing the ChangeCipherSpec message. For latency-sensitive applications, this 1-RTT reduction can shave 100-300ms off connection establishment at typical internet round-trip times.
TLS 1.3 also removed every cipher suite that used RSA key exchange or static DH, making forward secrecy mandatory. In TLS 1.2, a passive adversary who later compromised the server’s private key could retroactively decrypt previously recorded sessions (if RSA key exchange was used). TLS 1.3’s mandatory Ephemeral Elliptic Curve Diffie-Hellman (ECDHE) means the session key is derived from ephemeral values that are discarded after the session, so recording encrypted traffic now yields nothing even if the server is later compromised.
Step-by-Step: The TLS 1.3 Handshake
flowchart TD
A["1. ClientHello"]
B["2. ServerHello + Key Agreement"]
C["3. Certificate Chain Validation"]
D["4. Client Finished (Completing Round 1)"]
E["5. 0-RTT Session Resumption (and Replay Risks)"]
F["6. Handshake Transcript MAC and Key Separation"]
A --> B
B --> C
C --> D
D --> E
E --> F
Step 1: ClientHello
The client initiates by sending a ClientHello message containing:
- TLS version:
0x0304(TLS 1.3) in thesupported_versionsextension (the legacy version field still reads 1.2 for middlebox compatibility) - Supported cipher suites: TLS 1.3 reduces the list to five:
TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256, and two others - Key share: The client proactively generates an ephemeral ECDHE key pair (typically X25519 or P-256) and includes the public key share directly in the ClientHello, without waiting for the server to negotiate the algorithm first
- Random: A 32-byte cryptographic random value
- Session ticket: If available, for 0-RTT resumption
# Observe the full ClientHello with openssl
openssl s_client -connect api.example.com:443 \
-tls1_3 \
-tlsextdebug \
-msg 2>&1 | head -60
# Show supported cipher suites in TLS 1.3
openssl ciphers -v 'TLSv1.3'
Step 2: ServerHello + Key Agreement (Server Side of Round 1)
The server responds immediately with a ServerHello containing:
- Its chosen cipher suite (e.g.,
TLS_AES_256_GCM_SHA384) - Its own ephemeral ECDHE public key share for the agreed curve (e.g., X25519)
- A
supported_versionsextension confirming TLS 1.3
At this point, both sides now have everything needed to derive the shared secret. Using the Diffie-Hellman operation: shared_secret = client_private_key × server_public_key (elliptic curve scalar multiplication). From this shared secret, TLS 1.3 derives a hierarchy of keys using HKDF (HMAC-based Key Derivation Function):
- Handshake traffic secrets: encrypt the rest of the handshake (EncryptedExtensions, Certificate, CertificateVerify, Finished)
- Application traffic secrets: encrypt application data after the handshake
The server then sends (all encrypted with the handshake traffic secret):
- EncryptedExtensions: additional extensions like ALPN protocol negotiation, server name
- Certificate: the server’s X.509 certificate chain
- CertificateVerify: a signature over the handshake transcript using the server’s private key
- Finished: an HMAC over the entire handshake transcript using the handshake traffic secret
Step 3: Certificate Chain Validation
The client validates the server’s Certificate message by walking the X.509 chain:
- Verify the leaf certificate’s signature using the intermediate CA’s public key
- Verify the intermediate CA certificate’s signature using the root CA’s public key
- Check that the root CA is in the client’s trust store
- Verify the certificate’s
CNorsubjectAltNamematches the hostname - Check certificate validity period (
notBefore,notAfter) - Check revocation status via OCSP (Online Certificate Status Protocol) or CRL
The CertificateVerify message is a digital signature over the hash of the complete handshake transcript up to this point, signed with the server’s private key. This proves the server actually possesses the private key corresponding to the certificate’s public key — critical because without it, an attacker could replay a legitimate certificate.
# Inspect a server's certificate chain
openssl s_client -connect api.example.com:443 -showcerts 2>/dev/null \
| openssl x509 -noout -text | grep -A2 "Subject:\|Issuer:\|Not After"
# Check OCSP stapling
openssl s_client -connect api.example.com:443 -status 2>/dev/null \
| grep -A10 "OCSP response"
Step 4: Client Finished (Completing Round 1)
The client sends its own Finished message — an HMAC over the full handshake transcript. Both sides now switch to using application traffic secrets derived from the master secret. Application data can now flow. This entire process completes in 1 RTT: one round trip consisting of ClientHello → (ServerHello + EncryptedExtensions + Certificate + CertificateVerify + Finished) → (Client Finished + application data).
# Measure TLS 1.3 handshake time
curl -w "time_connect: %{time_connect}\ntime_appconnect: %{time_appconnect}\n" \
-o /dev/null -s https://api.example.com
# Force TLS 1.2 for comparison
curl --tls-max 1.2 -w "time_appconnect: %{time_appconnect}\n" \
-o /dev/null -s https://api.example.com
Step 5: 0-RTT Session Resumption (and Replay Risks)
TLS 1.3 supports 0-RTT Early Data for session resumption. When a client reconnects to a server it previously spoke to:
- The server previously issued a session ticket (a PSK — Pre-Shared Key — encrypted with the server’s ticket key)
- On reconnect, the client sends its first HTTP request inside the ClientHello as Early Data, using a key derived from the PSK
- The server can process this data without waiting for the handshake to complete
This achieves 0 RTT for the application data on reconnection — the request reaches the server before any acknowledgment is received. The critical security caveat: 0-RTT data has no forward secrecy and is vulnerable to replay attacks. An adversary who records the ClientHello can replay the Early Data to the server multiple times. Servers must treat 0-RTT data as idempotent (safe to replay), making it appropriate only for safe HTTP methods like GET, never for POST with side effects.
# Test 0-RTT capability
openssl s_client -connect api.example.com:443 \
-tls1_3 \
-early_data /dev/null 2>&1 | grep "Early data"
Step 6: Handshake Transcript MAC and Key Separation
A subtle but critical feature: TLS 1.3 uses a transcript hash at every step. The Finished message’s HMAC is computed over the SHA-256 (or SHA-384) hash of all preceding handshake messages. This means the Finished message authenticates the entire negotiation — if any single byte of the ClientHello, ServerHello, Certificate, or any extension was tampered with in transit, the Finished verification will fail and the connection will be aborted. This eliminates the “FREAK” and “Logjam” class of downgrade attacks that plagued TLS 1.2.
# Full TLS 1.3 debug session showing all messages
openssl s_client -connect api.example.com:443 \
-tls1_3 -msg -debug 2>&1 | grep -E "<<< TLS|>>> TLS|Handshake"
Key Takeaways
- TLS 1.3 achieves 1-RTT by front-loading key material: The client sends its ECDHE public key share in the ClientHello, allowing both sides to derive the handshake secret before the server sends its Certificate.
- Forward secrecy is mandatory: All key exchange in TLS 1.3 uses ephemeral Diffie-Hellman (ECDHE with X25519 or P-256), ensuring that compromise of the server’s long-term private key cannot decrypt past sessions.
- Certificate validation involves chain walking, hostname matching, and revocation checks: The CertificateVerify signature over the handshake transcript proves key possession, preventing certificate replay attacks.
- The Finished message is a transcript MAC: It authenticates every byte of the negotiation, making downgrade attacks cryptographically impossible.
- 0-RTT Early Data sacrifices replay protection for latency: It should only be used for idempotent, read-only operations, and servers must implement replay protection mechanisms (nonce tracking or anti-replay windows).
Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.