How the TLS 1.3 Handshake Works: 1-RTT, 0-RTT, and Removed Features
TLS 1.3 (RFC 8446, published August 2018) is a ground-up redesign of the Transport Layer Security protocol. It is not a minor revision of TLS 1.2 -- it removes entire categories of cryptographic primitives, cuts the handshake from two round trips to one, and encrypts far more of the negotiation than any previous version. Every cipher suite in TLS 1.3 provides forward secrecy. Every cipher is AEAD. There is no RSA key transport, no static Diffie-Hellman, no CBC mode, no compression, no renegotiation. The protocol was designed by learning from fifteen years of attacks on TLS 1.0 through 1.2 and eliminating every feature those attacks exploited. If you understand how TLS works in general, TLS 1.3 is where the protocol finally becomes what it always should have been.
As of 2025, TLS 1.3 handles the majority of HTTPS connections on the internet. Major CDN providers like Cloudflare (AS13335) and AWS (AS16509) default to TLS 1.3, and every modern browser has supported it since 2018-2019. QUIC/HTTP3 mandates TLS 1.3 exclusively -- you cannot use QUIC with an older TLS version. The question is no longer whether to support TLS 1.3 but when to drop TLS 1.2 entirely.
The 1-RTT Handshake: How It Works
The most visible improvement in TLS 1.3 is the handshake. TLS 1.2 required two round trips before the client could send application data: one for cipher suite negotiation and another for key exchange. TLS 1.3 collapses this to a single round trip by having the client speculatively send key shares in its first message, before the server has even responded.
Here is the full 1-RTT handshake sequence:
- ClientHello -- The client sends its supported cipher suites, a list of supported groups (the elliptic curves or DH groups it can use), and -- critically -- one or more
key_shareentries. Each key share contains the client's ephemeral public key for a specific group. The client is guessing which group the server will prefer, and precomputing its half of the key exchange for that group. The client also includes a random nonce and thesupported_versionsextension set to0x0304(TLS 1.3). - ServerHello -- The server selects a cipher suite and a key share group. If the client guessed correctly and included a key share for the server's preferred group, the server responds with its own ephemeral public key for that group. At this point, both sides can compute the shared secret via ECDHE (or DHE). The ServerHello also includes the server's random nonce.
- EncryptedExtensions -- Everything after ServerHello is encrypted. The server sends extensions that are not needed for cryptographic negotiation (like ALPN, server name acknowledgment, etc.) inside an encrypted message. In TLS 1.2, these were sent in plaintext.
- Certificate -- The server sends its certificate chain, encrypted. An eavesdropper cannot see which certificate the server presented.
- CertificateVerify -- The server signs a transcript hash of all handshake messages so far, proving it possesses the private key corresponding to the certificate. This is how TLS 1.3 authenticates the server.
- Finished -- The server sends a MAC over the entire handshake transcript, protecting against tampering.
- Client Finished -- The client verifies the server's messages, sends its own Finished MAC, and can immediately begin sending application data.
The entire negotiation -- cipher suite selection, key exchange, authentication, and the start of encrypted application data -- completes in a single round trip.
Compare this with TLS 1.2: the client sends ClientHello, waits for ServerHello plus the server's certificate and key exchange message, then sends its own key exchange and ChangeCipherSpec, and only then can application data flow. That is two full round trips. On a 100ms RTT connection, TLS 1.3 saves 100ms on every new connection -- a meaningful improvement for web performance.
HelloRetryRequest: When the Guess is Wrong
If the client's speculative key share does not match any group the server supports, the server responds with a HelloRetryRequest instead of ServerHello. This message tells the client which group to use. The client then sends a new ClientHello with the correct key share. This adds one round trip, making it a 2-RTT handshake -- the same as TLS 1.2. In practice, HelloRetryRequest is rare because clients typically offer key shares for X25519 and P-256, which cover essentially all servers. Browsers prioritize X25519 as the first key share since it is the most widely supported.
0-RTT Early Data: Speed at a Cost
TLS 1.3 introduces 0-RTT resumption, where a client that has previously connected to a server can send encrypted application data in its very first message, before the handshake completes. The client uses a pre-shared key (PSK) from a prior session to derive encryption keys immediately, then includes encrypted early data alongside its ClientHello.
This is possible because of the PSK resumption mechanism. At the end of a successful TLS 1.3 handshake, the server can send NewSessionTicket messages containing encrypted session state. On the next connection, the client includes a pre_shared_key extension referencing this ticket and an early_data extension containing application data encrypted under keys derived from the PSK.
The performance benefit is real: on a 150ms RTT mobile connection, 0-RTT saves 150ms on the initial request. For time-to-first-byte on repeat visits, this is significant. But 0-RTT comes with a critical security limitation: early data is not protected against replay attacks.
An attacker who captures the 0-RTT flight can retransmit it to the server. The server will process it as a legitimate request. This means 0-RTT data must be idempotent -- safe to repeat. A GET request for a web page is fine. A POST request that transfers money is not. Servers must implement application-layer replay protection (like single-use tokens) or restrict 0-RTT to safe HTTP methods.
Additional restrictions on 0-RTT early data:
- No forward secrecy -- Early data is encrypted under the PSK, not an ephemeral key exchange. If the PSK is compromised, all 0-RTT data encrypted with it is exposed.
- Limited to the first flight -- Only the client sends early data. The server's first application data still waits for the handshake to complete.
- Server can reject it -- The server may decline early data (via the
end_of_early_datamessage), forcing the client to retransmit after the full handshake. Well-configured servers reject 0-RTT for non-idempotent requests. - Time-limited -- Session tickets have a maximum lifetime of 7 days (the spec recommends shorter). Stale tickets are rejected.
The Key Schedule: HKDF and Key Derivation
TLS 1.3 uses a structured key schedule based on HKDF (HMAC-based Key Derivation Function, RFC 5869) to derive all keying material. Every key used in the protocol -- handshake encryption keys, application data keys, resumption secrets -- flows from a single derivation chain. This is a major improvement over TLS 1.2, where keys were derived through a less rigorous PRF (pseudo-random function) construction.
HKDF operates in two phases:
- HKDF-Extract -- Takes input keying material (IKM) and an optional salt, producing a fixed-length pseudorandom key (PRK). This step concentrates entropy from variable-length inputs into a uniformly distributed key.
- HKDF-Expand -- Takes the PRK and a context label, producing output keying material of any desired length. The label ensures different keys derived from the same PRK are cryptographically independent.
The TLS 1.3 key schedule feeds two inputs into this chain:
- PSK (Pre-Shared Key) -- Either from a previous session ticket (resumption) or an externally provisioned value. If no PSK is available, a zero-value key of the hash length is used.
- (EC)DHE shared secret -- The result of the ephemeral Diffie-Hellman key exchange from the handshake.
The derivation proceeds through a series of Extract-then-Expand steps:
PSK (or 0)
|
v
HKDF-Extract = Early Secret
|
Derive-Secret(., "ext binder" | "res binder", "")
| = binder_key
|
Derive-Secret(., "c e traffic", ClientHello)
| = client_early_traffic_secret [0-RTT keys]
v
(EC)DHE input
|
v
HKDF-Extract = Handshake Secret
|
Derive-Secret(., "c hs traffic", ClientHello...ServerHello)
| = client_handshake_traffic_secret
|
Derive-Secret(., "s hs traffic", ClientHello...ServerHello)
| = server_handshake_traffic_secret
v
0 (no input)
|
v
HKDF-Extract = Master Secret
|
Derive-Secret(., "c ap traffic", ClientHello...server Finished)
| = client_application_traffic_secret
|
Derive-Secret(., "s ap traffic", ClientHello...server Finished)
| = server_application_traffic_secret
|
Derive-Secret(., "res master", ClientHello...client Finished)
= resumption_master_secret
Each Derive-Secret call uses HKDF-Expand-Label with the transcript hash of all handshake messages up to that point. This means the key for encrypting application data incorporates the full handshake transcript -- any tampering with any handshake message results in different keys on each side, causing the connection to fail. The key schedule is the backbone of TLS 1.3's security: it provides key separation (handshake keys are independent of application keys), forward secrecy (compromising the long-term key does not reveal session keys), and transcript binding (keys are bound to the specific handshake that produced them).
Cipher Suites: Only AEAD Survives
TLS 1.3 radically simplifies cipher suite selection. In TLS 1.2, a cipher suite specified four components: key exchange algorithm, authentication algorithm, bulk cipher, and MAC algorithm. This produced a combinatorial explosion -- hundreds of possible combinations, many of them insecure. TLS 1.3 separates key exchange from the cipher suite entirely (it is negotiated via extensions) and mandates that all ciphers be AEAD (Authenticated Encryption with Associated Data), which combines encryption and integrity into a single primitive.
The complete list of TLS 1.3 cipher suites:
TLS_AES_128_GCM_SHA256-- AES-128 in Galois/Counter Mode with SHA-256 for HKDF. The most commonly negotiated suite, supported by hardware AES-NI on modern x86 and ARM CPUs.TLS_AES_256_GCM_SHA384-- AES-256 in GCM with SHA-384. Higher security margin, slightly more computational cost.TLS_CHACHA20_POLY1305_SHA256-- ChaCha20 stream cipher with Poly1305 MAC. Designed by Daniel Bernstein, this suite excels on devices without hardware AES support (older ARM phones, IoT devices). On hardware with AES-NI, performance is comparable.TLS_AES_128_CCM_SHA256-- AES-128 in Counter with CBC-MAC mode. Rarely used in practice, exists primarily for constrained IoT environments.TLS_AES_128_CCM_8_SHA256-- Same as above but with a truncated 8-byte authentication tag. Even more constrained use case.
That is five cipher suites total. TLS 1.2 had over 300. Every one of the five provides authenticated encryption -- there is no way to negotiate a cipher without integrity protection. There is no CBC mode, no RC4, no 3DES, no MD5, no SHA-1. The attack surface from cipher suite misconfiguration, which powered attacks like BEAST, POODLE, Lucky13, and Sweet32, is eliminated by construction.
What TLS 1.3 Removed (and Why)
The features removed from TLS 1.3 are not a list of minor deprecations -- they represent the protocol's most important security improvements. Each removed feature was the target of real-world attacks.
RSA Key Exchange
In TLS 1.2, the client could encrypt the pre-master secret directly with the server's RSA public key. This works, but it means the pre-master secret is protected only by the server's long-term RSA private key. If that key is ever compromised -- through a server breach, a compelled disclosure, or decades of future computational advances -- every past session encrypted using that key can be decrypted. This is the opposite of forward secrecy.
The Bleichenbacher attack (1998) and its descendants (DROWN, ROBOT) exploited subtle padding oracle leaks in RSA PKCS#1 v1.5 encryption to decrypt RSA key exchanges without even knowing the private key. These attacks were devastating: DROWN (2016) allowed an attacker with access to an SSLv2 server to decrypt TLS 1.2 sessions that used the same RSA key, even though the TLS 1.2 connection itself was properly configured.
TLS 1.3 removes RSA key exchange entirely. All key exchange uses ephemeral (EC)DHE, providing forward secrecy for every connection. The server's RSA key (if it uses RSA) is used only for signing -- never for encryption.
Static Diffie-Hellman
TLS 1.2 allowed static DH parameters, where the server reused the same DH key pair across connections. This defeated forward secrecy and enabled precomputation attacks. The Logjam attack (2015) showed that many servers used common 1024-bit DH groups, and a nation-state adversary who precomputed the discrete logarithm for a common group could passively decrypt any connection using that group. The researchers estimated that breaking the most common 1024-bit DH group would cost a few hundred million dollars -- well within the NSA's budget.
TLS 1.3 requires ephemeral key shares. There is no option for static DH.
Compression
TLS-level compression was exploited by the CRIME attack (2012). Because compression ratios depend on input content, an attacker who could inject known plaintext alongside secret data (like session cookies) could observe the compressed ciphertext length and iteratively guess the secret one byte at a time. CRIME was practical and devastating -- it could extract session cookies from HTTPS connections in under a minute.
TLS 1.3 removes compression entirely. Application-level compression (like HTTP gzip) still works because it operates on plaintext before encryption, but the TLS record layer no longer compresses.
Renegotiation
TLS renegotiation allowed either party to initiate a new handshake within an existing encrypted session. This was exploited in 2009 when Marsh Ray demonstrated a renegotiation attack where an attacker could inject arbitrary plaintext at the beginning of an HTTPS request during the renegotiation transition. Although RFC 5746 patched the most severe variant, renegotiation remained a source of complexity and potential vulnerabilities. TLS 1.3 removes it. If you need to update keys, the protocol provides a KeyUpdate message that triggers key rotation without renegotiation.
ChangeCipherSpec
In TLS 1.2, the ChangeCipherSpec message signaled the transition from unencrypted to encrypted communication. It was a source of implementation bugs (the CCS injection vulnerability, 2014) and an awkward protocol artifact. TLS 1.3 eliminates it -- the transition to encryption happens implicitly after the ServerHello, based on the key schedule. For compatibility, TLS 1.3 implementations may send a dummy ChangeCipherSpec message to avoid confusing middleboxes, but it is semantically meaningless and ignored by the receiver.
Arbitrary DH Groups and Non-ECDHE Key Exchange
TLS 1.2 allowed servers to specify arbitrary DH parameters, leading to weak configurations. TLS 1.3 defines a fixed set of named groups: x25519, x448, secp256r1, secp384r1, and secp521r1 for elliptic curves, plus ffdhe2048 through ffdhe8192 for finite-field DH. In practice, x25519 dominates -- it is fast, constant-time by design, and resistant to implementation pitfalls.
Encrypted Extensions: Hiding Negotiation Metadata
In TLS 1.2, the entire handshake was sent in plaintext. A passive eavesdropper could see the server's certificate (revealing the domain), the cipher suite negotiation, the ALPN selection, and other metadata. TLS 1.3 encrypts everything after ServerHello. The EncryptedExtensions message carries server extensions that are not required for cryptographic processing of the ServerHello:
- server_name (SNI acknowledgment) -- confirmation of which hostname the server is responding as
- ALPN -- which application protocol was selected (e.g., h2 for HTTP/2, h3 for HTTP/3)
- max_fragment_length -- negotiated maximum record size
- early_data -- whether the server accepted 0-RTT data
The certificate chain, CertificateVerify, and Finished messages are also encrypted. This means a network observer sees only the ClientHello and ServerHello in plaintext. The server's identity (its certificate) is hidden from passive surveillance. However, one critical piece of metadata remains visible: the Server Name Indication (SNI) in the ClientHello, which the client sends in plaintext so the server knows which certificate to present. Encrypting SNI requires a separate mechanism -- Encrypted Client Hello -- discussed below.
PSK Resumption and Session Tickets
TLS 1.3 replaces the TLS 1.2 session ID and session ticket mechanisms with a unified PSK (Pre-Shared Key) framework. After a full handshake completes, the server can send one or more NewSessionTicket messages. Each ticket contains an opaque blob (encrypted by the server with a key only it knows) that encapsulates enough state to resume the session later.
On a subsequent connection, the client includes the ticket in a pre_shared_key extension in the ClientHello. The server decrypts the ticket, recovers the PSK, and performs an abbreviated handshake. If the client also includes a key_share extension (which is recommended and default behavior in browsers), the resumed handshake uses both the PSK and a fresh ECDHE exchange. This PSK with (EC)DHE mode provides forward secrecy even for resumed connections -- something TLS 1.2 session resumption never offered.
PSK-only mode (without ECDHE) is permitted by the spec but lacks forward secrecy. If the PSK is compromised, all sessions resumed with that PSK are exposed. Browsers always use PSK+(EC)DHE, so in practice, resumed connections retain forward secrecy.
The ticket lifetime is capped at 604,800 seconds (7 days). Servers can issue multiple tickets per session, allowing the client to resume multiple times without reusing a ticket (each ticket should be used at most once to limit replay concerns). The ticket age is obfuscated by adding a random offset, preventing a network observer from correlating tickets to sessions based on timing.
Downgrade Protection: Sentinel Values
One of the subtler features in TLS 1.3 is its protection against version downgrade attacks. In a downgrade attack, a man-in-the-middle modifies the ClientHello to advertise support only for older protocol versions, forcing the server to negotiate TLS 1.2 or TLS 1.1. The attacker then exploits known vulnerabilities in the older protocol.
TLS 1.3 defends against this by embedding sentinel values in the ServerHello random field. When a TLS 1.3-capable server negotiates a lower protocol version (because the client only supports the lower version), the server sets the last 8 bytes of the ServerHello random to specific fixed values:
- If negotiating TLS 1.2: the last 8 bytes are set to
44 4F 57 4E 47 52 44 01(ASCII:DOWNGRD\x01) - If negotiating TLS 1.1 or below: the last 8 bytes are set to
44 4F 57 4E 47 52 44 00(ASCII:DOWNGRD\x00)
A TLS 1.3-capable client that receives a ServerHello for TLS 1.2 checks for this sentinel. If present, it knows the server actually supports TLS 1.3, meaning the downgrade was forced by an attacker, and it aborts the connection. If the sentinel is absent, the server genuinely only supports TLS 1.2.
This mechanism works because the ServerHello random is covered by the handshake transcript and authenticated by the Finished message. An attacker cannot modify the random value without being detected. The sentinel approach is elegant -- it requires no new messages or extensions, just a convention about a field that already exists.
Encrypted Client Hello (ECH)
Even with TLS 1.3 encrypting the certificate and most extensions, the Server Name Indication (SNI) in the ClientHello remains visible in plaintext. SNI tells the server which hostname the client wants to reach, which is necessary for virtual hosting (multiple domains on one IP). But it also tells any network observer exactly which website the client is connecting to -- even if the DNS query was encrypted via DNS over HTTPS.
Encrypted Client Hello (ECH), standardized in draft-ietf-tls-esni, solves this. ECH encrypts the entire ClientHello, including SNI, using a public key published in the server's DNS HTTPS record. The mechanism works as follows:
- The server (or CDN) publishes an ECH configuration in DNS, including a public key for encrypting the ClientHello. This is distributed via HTTPS DNS resource records.
- The client retrieves the ECH config (typically cached from a prior DoH query or the previous connection's retry config).
- The client constructs two ClientHello messages: the ClientHelloInner (the real one, containing the true SNI and other sensitive extensions) and the ClientHelloOuter (a cover, with the CDN's generic domain as SNI). The ClientHelloInner is encrypted with the server's ECH public key and embedded in the ClientHelloOuter as an extension.
- The server (which holds the ECH private key) decrypts the inner ClientHello and proceeds with the real handshake. A network observer sees only the outer ClientHello with the generic CDN domain.
ECH is deployed on Cloudflare and supported in Firefox and Chrome. The combination of TLS 1.3, ECH, and DoH creates a stack where the only metadata visible to a network observer is the IP address of the server -- and since CDN providers host millions of domains behind the same IP ranges, even that reveals little.
ECH has an elegant retry mechanism: if the server's ECH key has rotated since the client cached it, the server sends an updated ECH config in its HelloRetryRequest. The client then retries with the new key. This avoids a hard failure when DNS records are stale.
Comparing TLS 1.2 and TLS 1.3
The differences between TLS 1.2 and 1.3 are not incremental refinements -- they are architectural changes:
| Feature | TLS 1.2 | TLS 1.3 |
|---|---|---|
| Handshake RTTs | 2-RTT (full), 1-RTT (resumption) | 1-RTT (full), 0-RTT (resumption) |
| Key exchange | RSA, DHE, ECDHE, static DH | ECDHE or DHE only (ephemeral) |
| Forward secrecy | Optional (only with DHE/ECDHE) | Mandatory for all connections |
| Cipher suites | 300+ (many insecure) | 5 (all AEAD) |
| Handshake encryption | Plaintext (certificate visible) | Encrypted after ServerHello |
| Compression | Supported (CRIME attack vector) | Removed |
| Renegotiation | Supported (attack history) | Removed (KeyUpdate instead) |
| Hash algorithms | MD5, SHA-1, SHA-256, SHA-384 | SHA-256, SHA-384 only |
Post-Quantum TLS: What Comes Next
The next evolution of TLS 1.3 is already underway: integrating post-quantum key exchange to defend against future quantum computers. NIST finalized the ML-KEM (Module-Lattice Key Encapsulation Mechanism, formerly CRYSTALS-Kyber) standard in 2024, and browsers are deploying it today.
Chrome and Firefox ship X25519Kyber768 (a hybrid of classical X25519 and post-quantum ML-KEM-768) as a key share in their ClientHello. The hybrid approach ensures that even if ML-KEM is broken by future cryptanalysis, the classical X25519 component still provides security. The downside is size: a Kyber768 public key is 1,184 bytes, and the ciphertext is 1,088 bytes. Combined with X25519's 32-byte key share, the ClientHello grows significantly. Some middleboxes and network equipment that assumed TLS ClientHello messages would be small have broken when encountering these larger handshakes.
TLS 1.3's clean key schedule and extension framework made integrating post-quantum algorithms straightforward. The key share is just another named group in the supported_groups extension. No protocol changes were needed -- only new group identifiers. This extensibility was designed into TLS 1.3 from the start, and it validates the decision to separate key exchange from cipher suite negotiation.
TLS 1.3 in Practice: Implementation Considerations
Despite TLS 1.3's cleaner design, deploying it was not trivial. Several practical challenges emerged:
Middlebox Compatibility
The biggest obstacle to TLS 1.3 deployment was not cryptographic -- it was middleboxes. Firewalls, load balancers, intrusion detection systems, and corporate proxies had been parsing TLS handshakes for years, and many made assumptions about the handshake structure that TLS 1.3 violated. The version field in ClientHello could not simply be set to 0x0304 because many middleboxes would drop or reset connections with an unrecognized version.
The solution was a remarkable hack: TLS 1.3 disguises itself as TLS 1.2. The ClientHello and ServerHello use version 0x0303 (TLS 1.2) in the legacy version field. The actual version is negotiated via the supported_versions extension. A dummy ChangeCipherSpec message may be sent to satisfy middleboxes expecting it. The ServerHello's structure is kept compatible with TLS 1.2 parsing. This "greasing" approach was born from extensive testing showing that 8% of servers failed when presented with a version number they did not recognize.
QUIC and TLS 1.3
QUIC does not simply run TLS 1.3 over a UDP connection -- it integrates the TLS 1.3 handshake directly into the QUIC transport layer. QUIC uses the TLS 1.3 key schedule and handshake state machine but replaces the TLS record layer with QUIC's own packet protection. TLS handshake messages are carried in QUIC CRYPTO frames rather than TLS records. This tight integration is why QUIC can achieve a 1-RTT handshake that establishes both transport parameters and encryption simultaneously -- something impossible when TLS runs as a separate layer on top of TCP.
Certificate Compression
TLS 1.3 added support for certificate compression (RFC 8879) to reduce the size of the certificate chain sent during the handshake. Certificate chains are typically 3-5 KB and can exceed 10 KB with intermediate certificates. Brotli compression reduces this by 60-70%. Since TLS 1.3 encrypts the certificate, compression does not create a CRIME-style oracle -- the attacker cannot inject known plaintext into the certificate.
Delegated Credentials
For large-scale CDN deployments, TLS 1.3 supports delegated credentials (RFC 9345). A website owner can delegate a short-lived signing credential to a CDN provider without sharing their long-term private key. The delegated credential is valid for at most 7 days and is bound to a specific public key. This allows CDN edge servers to terminate TLS without possessing the origin server's private key -- a significant security improvement for the operational model where CDN providers like Cloudflare and Akamai (AS20940) terminate millions of TLS connections on behalf of their customers.
See It in Action
The infrastructure that powers TLS 1.3 connections -- certificate authorities, CDN edge nodes, DNS resolvers -- is all reachable via BGP routes you can inspect in the looking glass. Use the god.ad BGP Looking Glass to trace the network paths to the providers that terminate your TLS 1.3 connections:
- AS13335 -- Cloudflare, the largest deployer of TLS 1.3 and ECH
- AS15169 -- Google, which pioneered TLS 1.3 deployment in Chrome and on Google services
- AS8075 -- Microsoft, TLS 1.3 across Azure and Microsoft 365
- AS16509 -- AWS, serving TLS 1.3 on ALB and CloudFront
- AS396982 -- Google Trust Services, issuing certificates validated in TLS 1.3 handshakes
Every connection to this page used a TLS 1.3 handshake -- one round trip to negotiate an AEAD cipher suite, establish forward-secret keys via ECDHE, and begin encrypted data transfer. The protocol's clean design, mandatory forward secrecy, and extensibility for post-quantum cryptography make it the foundation of internet security for the foreseeable future.