A Detailed Look At The Tls 1.3 Handshake Protocol: Key Schedule, 0 Rtt, And Forward Secrecy

A comprehensive technical exploration of a detailed look at the tls 1.3 handshake protocol: key schedule, 0 rtt, and forward secrecy, covering key concepts, practical implementations, and real-world applications.
Contents
The TLS 1.3 Handshake: A Complete Architectural Overhaul
The moment you load a webpage, send a text, or swipe your credit card, you are participating in an act of profound, invisible trust. You trust that the data you send will reach its destination unread by prying eyes. You trust that the server on the other end is who it claims to be, not a malicious imposter. You trust that the history of your session—your password, your bank balance, your private message—cannot be decrypted retroactively, even if an adversary collects the encrypted traffic today and steals the server’s keys tomorrow. This trust is not a matter of faith. It is a matter of mathematics, protocol design, and a beautifully choreographed sequence of bytes flying across the network. That sequence is the TLS handshake.
For decades, the Transport Layer Security (TLS) protocol has been the silent backbone of secure communication on the Internet. From its origins as SSL to the widely deployed TLS 1.2, it has evolved to patch vulnerabilities and adopt stronger cryptographic primitives. Yet, until recently, the handshake itself carried the legacy of a pre-mobile, pre-API, pre-latency-sensitive world. The process was relatively slow. It required multiple round trips. It permitted dangerously brittle negotiation dances where a determined downgrade attacker could force a client and server to agree on a weaker cipher suite. And in its most common configurations, it failed to provide a critical property for the modern era: forward secrecy for all sessions.
Then came TLS 1.3. Ratified by the IETF in August 2018, it was not a minor update. It was a complete architectural overhaul disguised as a version bump. The committee behind it did not simply add new cipher suites. They removed entire classes of cryptographic primitives. They rewired the handshake from the ground up, reducing the latency of a full connection from two round trips to one, and even enabling a zero-round-trip mode for returning users. They made forward secrecy not a negotiable feature, but a mandatory requirement. And in doing so, they fundamentally changed the security posture of every TLS connection forged thereafter.
In this blog post, we will peel back the layers of the TLS handshake—both old and new. We will walk through the TLS 1.2 handshake in excruciating detail, identify its shortcomings, and then examine every design decision behind TLS 1.3. We will explore cryptographic primitives, key derivation, 0-RTT, forward secrecy, performance implications, and real-world deployment. By the end, you will understand not only what changed, but why the changes matter for the future of the Internet.
1. The Pre-TLS 1.3 World: A Brief History of SSL/TLS
Before we can appreciate the revolution of TLS 1.3, we must understand the evolutionary baggage it was born to shed.
1.1 SSL: The Awkward Adolescence
Secure Sockets Layer (SSL) was developed by Netscape in the mid-1990s. SSL 1.0 was never publicly released due to serious security flaws. SSL 2.0 (1995) was deployed in Netscape Navigator but was quickly found to be vulnerable: it used weak MACs, allowed cipher suite downgrades, and had no protection against truncation attacks. SSL 3.0 (1996) addressed many of these issues, introducing a proper handshake protocol, support for more cipher suites, and the addition of a ChangeCipherSpec message. However, SSL 3.0 still suffered from the now-infamous POODLE attack (2014), where a padding oracle could decrypt ciphertexts when using CBC mode ciphers.
1.2 TLS 1.0, 1.1, and 1.2: Incremental Patches
The Internet Engineering Task Force (IETF) took over standardization and renamed the protocol to Transport Layer Security (TLS) with version 1.0 in 1999 (RFC 2246). TLS 1.0 was essentially SSL 3.0 with minor changes (e.g., different key derivation, use of HMAC instead of the old MAC construction). It still used RSA key exchange as the default, lacked forward secrecy, and relied on the flawed CBC-MAC combination.
TLS 1.1 (RFC 4346, 2006) addressed a few vulnerabilities: it added explicit IVs for CBC block ciphers to prevent BEAST-style attacks, and it introduced a warning about timing attacks on padding. But it was largely an incremental fix.
TLS 1.2 (RFC 5246, 2008) was the first major update. It removed the ability to negotiate a hash algorithm with the cipher suite by separating the signature hash from the cipher suite definition. It introduced Authenticated Encryption with Associated Data (AEAD) ciphers like AES-GCM and AES-CCM, which combine encryption and integrity in a single, secure primitive. It also defined the use of the SHA-256 hash for the PRF. However, it still allowed RSA key exchange, static DH, and many weak ciphers. It also retained the same multi-round-trip handshake structure that had existed since SSL 3.0.
1.3 The Vulnerability Parade (2009–2017)
The decade from 2009 to 2017 saw a relentless stream of attacks against TLS 1.2 and earlier versions. Here is a partial list, each attack highlighting a design flaw that TLS 1.3 would later eliminate:
- BEAST (2011): Exploited the use of CBC mode ciphers in TLS 1.0, allowing a man-in-the-middle to decrypt ciphertexts by predicting the IV.
- CRIME (2012): Used compression of the HTTP request body (TLS compression enabled) to perform a compression oracle attack, leaking session cookies.
- BREACH (2013): Similar to CRIME, but exploited HTTP-level compression (gzip) instead of TLS compression.
- Heartbleed (2014): A buffer over-read in OpenSSL’s implementation of the TLS heartbeat extension—allowed attackers to read memory contents, including private keys.
- POODLE (2014): Exploited the padding oracle in CBC mode when using SSL 3.0; later extended to TLS 1.0–1.2 (POODLE-TLS).
- FREAK (2015): Exploited export-grade RSA cipher suites that were left in TLS 1.2 for backward compatibility; downgrade attack forced a weak 512-bit RSA key.
- Logjam (2015): Similar downgrade attack against DHE key exchange, forcing servers to accept export-grade 512-bit Diffie-Hellman groups.
- SLOTH (2015): Attacked MD5-based signatures used in certificate verification.
Each attack was a painful lesson. Designers realized that backward compatibility came at a terrible cost. The TLS handshake was too complex, too negotiable, and too forgiving of legacy. A fundamental reset was needed.
2. The TLS 1.2 Handshake: A Detailed Walkthrough
To understand what TLS 1.3 fixed, we must first walk through the TLS 1.2 handshake in detail. We will focus on the most common configuration: a full handshake using ECDHE (Elliptic Curve Diffie-Hellman Ephemeral) with an RSA-signed certificate.
2.1 Handshake Messages and Round Trips
A full TLS 1.2 handshake requires two round trips (2-RTT) between client and server before the client can send protected application data. The message flow is as follows:
Client Server
------ ------
ClientHello (contains:
- TLS version (e.g., 1.2)
- random (32 bytes)
- session ID (for resumption)
- cipher suites list
- compression methods
- extensions: supported_groups, signature_algorithms, etc.)
-------->
ServerHello (contains:
- chosen version
- random
- session ID
- chosen cipher suite
- chosen compression)
Certificate (server's X.509 chain)
ServerKeyExchange (for ECDHE: curve, public key, signature)
ServerHelloDone
<--------
ClientKeyExchange (for ECDHE: client's public key)
ChangeCipherSpec (client signals encryption start)
Finished (encrypted with session keys)
-------->
ChangeCipherSpec
Finished (encrypted)
<--------
Application Data (protected)
Round Trip 1 (Client → Server → Client):
- Client sends a ClientHello.
- Server replies with ServerHello, Certificate, ServerKeyExchange, and ServerHelloDone.
Round Trip 2 (Client → Server → Client):
- Client sends ClientKeyExchange, ChangeCipherSpec, and Finished.
- Server sends the same (ChangeCipherSpec and Finished).
Only after the second Finished message does either side have verified that the other holds the correct keys. Application data (e.g., HTTP request) can then be sent.
2.2 Key Exchange in Depth
In TLS 1.2, the ephemeral Diffie-Hellman key exchange works as follows:
- The client sends its supported elliptic curves (e.g., secp256r1) in the
supported_groupsextension. - The server chooses a curve and generates an ephemeral key pair:
(d_s, Q_s)whereQ_s = d_s * G(G is the curve generator). It sends the curve identifier, the public pointQ_s, and a signature over the entire handshake transcript so far (including the client’s random) using the server’s long-term private key (from its certificate). This signature binds the ephemeral key to the server’s identity. - The client verifies the signature, generates its own ephemeral key pair
(d_c, Q_c), and sendsQ_cto the server. - Both sides compute the shared secret:
Z = d_c * Q_s = d_s * Q_c = d_c * d_s * G. ThisZis then input into a PRF (based on SHA-256 for TLS 1.2) to derive the master secret and subsequently the session keys.
If the server chooses static RSA key exchange (no longer recommended), the client generates a pre-master secret, encrypts it with the server’s RSA public key, and sends it. No forward secrecy.
2.3 Certificate Validation and Signature Algorithms
The server’s certificate is sent in the Certificate message. The client must validate the certificate chain (root CA, intermediate CAs, leaf). The ServerKeyExchange message contains a signature that covers the server’s ephemeral public key along with the client and server randoms. The signature algorithm must be one of those advertised by the client in the signature_algorithms extension (e.g., RSA-PKCS1-SHA256, ECDSA-SHA256). The signature proves that the entity holding the long-term private key (the server) is actively participating in this session.
2.4 Finished Messages and Key Derivation
After the key exchange, both sides compute the master secret:
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)[0..47]
Then they compute the client write key, server write key, and MAC keys (for non-AEAD ciphers) from the master secret using additional PRF calls.
The Finished message is a hash of all previous handshake messages, encrypted with the negotiated keys. It proves that both sides derived the same keys and saw the same transcript.
2.5 Abbreviated Handshake (Session Resumption)
To reduce latency on subsequent connections, TLS 1.2 supports session resumption. The client includes a session ID from a previous session; if the server recognizes it, they can skip the Certificate and KeyExchange messages and derive keys from the previous master secret. This still requires one round trip (1-RTT) because the client must send a ClientHello and wait for a ServerHello before sending application data.
2.6 Weaknesses of the TLS 1.2 Handshake
Despite its widespread use, the TLS 1.2 handshake suffers from several critical weaknesses:
- Latency: The mandatory two round trips (or one for resumption) add significant delay, especially on high-latency networks (e.g., mobile, satellite).
- Downgrade Attacks: The client offers a list of cipher suites in the ClientHello; the server chooses one. If an attacker can modify the ClientHello to remove strong ciphers (or modify the ServerHello to select a weak one), the handshake might proceed with a vulnerable suite. While some downgrade protection exists via the Finished message, it is not universally enforced, and attacks like FREAK and Logjam exploited export-grade suites.
- Lack of Mandatory Forward Secrecy: Many servers were (and still are) configured with RSA key exchange as a fallback. An attacker who records encrypted traffic and later obtains the server’s RSA private key can decrypt all past sessions. Forward secrecy via ephemeral Diffie-Hellman is optional and many deployments omitted it due to performance concerns (though ECDHE is fast).
- Insecure Cryptographic Primitives: TLS 1.2 still allows CBC mode ciphers (vulnerable to padding oracles), RC4 (broken), and export-grade ciphers (512-bit keys). The handshake does not enforce the use of AEAD ciphers.
- No Encryption of Server Parameters: The ServerKeyExchange and Certificate messages are sent in plaintext, revealing the server’s certificate chain and the ephemeral public key. While this is not a direct security risk, it leaks information (e.g., server identity) and allows fingerprinting.
TLS 1.3 was designed to eliminate every one of these problems.
3. The Problem: Why TLS 1.2 Needed a Revolution
The vulnerabilities of TLS 1.2 were not just theoretical; they were actively exploited. But beyond security, the performance gap became a major pain point. The modern Internet is dominated by mobile devices, real-time APIs, and microservices. A 2-RTT handshake adds at least 40–80 ms on a 20 ms network, and hundreds of milliseconds on mobile. For a small API call, the handshake overhead could dominate the transaction time. Moreover, the arms race between attackers and cryptographers had become unsustainable: every few months a new attack on some legacy cipher or negotiation step forced a frantic patch cycle.
The IETF TLS working group, led by Eric Rescorla, decided that incremental patches were no longer sufficient. In 2014, they began work on what would become TLS 1.3. Their guiding principles were:
- Simplify: Remove all obsolete primitives and negotiation steps.
- Make Forward Secrecy Mandatory: Every session must use ephemeral Diffie-Hellman.
- Reduce Latency: Aim for 1-RTT for new connections and 0-RTT for returning users.
- Encrypt the Handshake: Protect as much of the handshake as possible from passive observation.
- Prevent Downgrade: Use hard-coded sentinel values to block protocol downgrade attacks.
The result was a protocol that is not just a new version of TLS, but a fundamentally different design.
4. The Solution: TLS 1.3 Design Principles
Before diving into the mechanics of the TLS 1.3 handshake, let’s outline the key design decisions:
4.1 Removal of Obsolete Algorithms
TLS 1.3 completely eliminates the following:
- All RSA key exchange (static and ephemeral)
- Static Diffie-Hellman (non-ephemeral)
- All CBC mode ciphers (AES-CBC, 3DES)
- All stream ciphers (RC4)
- All export-grade cipher suites
- Compression (which enabled CRIME)
- Renegotiation (a source of many attacks)
- Custom Diffie-Hellman groups (only standard named groups are allowed)
The only allowed key exchange methods are (EC)DHE (ephemeral Diffie-Hellman) and PSK (pre-shared key, used for resumption). The only allowed encryption modes are AEAD: AES-GCM, AES-CCM, and ChaCha20-Poly1305.
4.2 Mandatory Forward Secrecy
By removing static RSA and DH, every TLS 1.3 connection uses ephemeral keys. Even if an attacker obtains the server’s long-term private key (e.g., through Heartbleed), they cannot decrypt past sessions because the ephemeral keys have been discarded. This is forward secrecy.
4.3 1-RTT and 0-RTT Handshakes
The full TLS 1.3 handshake requires only one round trip (1-RTT). The client can send its key share (ephemeral public key) in the ClientHello itself, so the server can compute the shared secret immediately. In the 0-RTT mode (used for resumed sessions), the client can send application data along with its first message, eliminating the round trip entirely. This is a game-changer for latency-sensitive applications.
4.4 Encrypted Handshake
In TLS 1.3, the server’s Certificate and CertificateVerify messages are encrypted (once the handshake secret is derived). This protects the server’s identity from passive observers and reduces fingerprinting. Also, the server can optionally send encrypted extensions.
4.5 Downgrade Protection
TLS 1.3 uses two specific markers in the ServerHello to prevent downgrade attacks:
- If the server negotiates TLS 1.3, it sets the
server_randomfield to a special 32-byte value (e.g.,44 4F 57 4E...meaning “DOWNGRD”) if it detects a downgrade attempt. - The client checks these markers; if the server falsely claims a lower version, the client aborts.
Additionally, the client uses a “supported_versions” extension to explicitly list the TLS versions it supports, rather than relying on the ClientHello.version field (which is always set to TLS 1.2 for backward compatibility with middleboxes).
5. The TLS 1.3 Handshake: The Core
Now we dive into the actual messages of a TLS 1.3 handshake. We will describe the full handshake (1-RTT) using ECDHE with an RSA-signed certificate, and then explain the 0-RTT mode.
5.1 The 1-RTT Handshake
The message flow for a full TLS 1.3 handshake is:
Client Server
------ ------
ClientHello (contains:
- supported_versions: {1.3, 1.2}
- random (32 bytes)
- cipher suites (only AEAD: e.g., TLS_AES_128_GCM_SHA256)
- key_share extension: client's ephemeral (EC)DHE public key (e.g., for secp256r1)
- signature_algorithms: for authentication
- pre_shared_key: if resuming)
-------->
ServerHello (contains:
- chosen version (1.3)
- random (includes downgrade marker)
- cipher suite
- key_share extension: server's ephemeral public key)
EncryptedExtensions (contains: server extensions like SNI, ALPN, etc.)
Certificate (encrypted)
CertificateVerify (encrypted, signature of transcript)
Finished (encrypted, MAC of handshake)
<--------
ClientFinished (encrypted, MAC of handshake)
Application Data (protected)
-------->
Note: The server sends its Finished before receiving the client’s Finished. This is possible because the server already knows the handshake secret from the key exchange. The client’s Finished is just a confirmation.
Step-by-Step:
ClientHello: The client sends its list of supported versions, but only advertises TLS 1.3 and maybe TLS 1.2 for backward compatibility. It sends a random nonce (32 bytes), a list of AEAD-only cipher suites (e.g.,
TLS_AES_128_GCM_SHA256), and akey_shareextension containing the client’s ephemeral public key for one or more supported groups (e.g., secp256r1, X25519). This is crucial: the server can immediately compute the shared secret upon receiving the ClientHello.ServerHello: The server chooses TLS 1.3 and a matching cipher suite. It sends its own random (including a specific byte string to protect against downgrade), and its ephemeral public key in the
key_shareextension. At this point, both sides can compute the handshake secret using the ECDHE shared secret.EncryptedExtensions: The server sends any non-critical extensions (e.g., ALPN negotiation, supported versions) under the freshly derived handshake traffic keys.
Certificate: The server sends its certificate chain, encrypted with the handshake keys. The client cannot yet verify the certificate because it has not received the server’s signature yet, but the encryption prevents eavesdropping.
CertificateVerify: The server signs the entire handshake transcript (ClientHello, ServerHello, EncryptedExtensions, Certificate) using its private key. The client uses this to authenticate the server.
ServerFinished: The server sends a MAC of all handshake messages computed with the handshake traffic keys. This proves that the server possesses the correct key.
ClientFinished: The client sends a MAC of all handshake messages to the server, also encrypted. The server can now be sure the client is the same entity that participated in the key exchange.
Application data can be sent after the client’s Finished, i.e., after one round trip.
5.2 Key Schedule
TLS 1.3 uses a cryptographic key schedule based on HKDF (HMAC-based Extract-and-Expand Key Derivation Function). The sequence is:
- Early Secret: Derived from a pre-shared key (PSK) or from the all-zero string for a full handshake.
- Handshake Secret: Derived from the (EC)DHE shared secret and the early secret.
- Master Secret: Derived from the handshake secret and the transcript hash of the handshake messages up to the server’s Finished.
From these, traffic keys are derived for the handshake (client and server) and for application data. The application traffic keys are computed after the handshake is complete.
This key schedule ensures that:
- The application keys depend on the full transcript (preventing renegotiation attacks).
- The handshake keys are separate and ephemeral.
- Forward secrecy is guaranteed because the (EC)DHE shared secret is destroyed after the handshake.
5.3 Signature Algorithms in TLS 1.3
The signature used in CertificateVerify must be one of a small set of strong algorithms: RSA-PSS (with SHA-256/384/512), ECDSA (with SHA-256/384/512), or EdDSA (Ed25519/Ed448). The client advertises its supported signature algorithms in the signature_algorithms extension. Note that TLS 1.3 does not allow RSA-PKCS1v1.5 signatures, which are vulnerable to Bleichenbacher attacks. This is a significant security improvement.
5.4 Supported Groups (Curves)
TLS 1.3 requires support for at least one ephemeral Diffie-Hellman group. The standard defines several named groups:
- Elliptic curves: secp256r1, secp384r1, secp521r1, X25519, X448.
- Finite-field DH: ffdhe2048, ffdhe3072, ffdhe4096.
X25519 (Curve25519) is now widely deployed because of its high speed and security.
5.5 0-RTT (Early Data)
The 0-RTT mode allows a client that has previously communicated with a server to send application data (e.g., an HTTP request) immediately with its first ClientHello, eliminating the round trip. This is enabled by a Pre-Shared Key (PSK) established during an earlier session.
Establishing the PSK: During a full handshake, the server can send a new_session_ticket message after the handshake, which contains a PSK identity and a ticket that encodes the PSK itself (encrypted with a server-held key). The client caches this ticket.
Using 0-RTT: On a subsequent connection, the client includes:
- A
pre_shared_keyextension containing the PSK identity and an obfuscated ticket age. - Early data (application data) in the
early_dataextension. - The
key_shareextension for optional (EC)DHE to achieve forward secrecy for the 0-RTT data.
The server can accept or reject 0-RTT. If accepted, the server derives the early traffic keys from the PSK and the client’s early data extension. It processes the early data immediately, then proceeds with the handshake (which may still include an optional (EC)DHE exchange for forward secrecy of the rest of the session).
Risks and Anti-Replay: 0-RTT data is vulnerable to replay attacks because an attacker could capture the ClientHello and early data and replay them to the server. TLS 1.3 implementations must include anti-replay mechanisms:
- The server maintains a ticket database with replay counters and timestamps.
- The ticket includes an
obfuscated_ticket_agethat the client reports; the server can reject replays if the age is inconsistent. - The server can limit 0-RTT to idempotent (safe) operations, e.g., GET requests in HTTP.
Many large deployments (e.g., Google, Cloudflare) use 0-RTT for HTTP/2 connection reuse, greatly reducing latency.
6. Forward Secrecy: The Non-Negotiable
Forward secrecy (FS) ensures that if the server’s long-term private key is compromised, past sessions remain secure. In TLS 1.2, FS was optional; many sites used RSA key exchange for simplicity. In TLS 1.3, FS is mandatory because every handshake uses ephemeral Diffie-Hellman. The long-term key is only used for signing the ephemeral key (via the CertificateVerify message), not for encryption.
Why FS matters:
- Governments and adversaries often record large volumes of encrypted traffic with the hope of decrypting it later.
- Large-scale key leaks (e.g., Heartbleed) can expose private keys.
- Without FS, a single key compromise retroactively exposes all past sessions.
TLS 1.3 ensures that even if the server’s private signing key is stolen, the attacker cannot decrypt any past session because the ephemeral (EC)DHE keys are discarded. Only forward-secure data (the application traffic) remains protected; the handshake transcript (certificates signatures) could be verified, but the session content is safe.
7. Security Improvements
Beyond forward secrecy, TLS 1.3 introduces several other security enhancements:
7.1 Encryption of Handshake Messages
As mentioned, the server sends its Certificate and CertificateVerify messages encrypted with the handshake secret. This hides the server’s identity (its certificate chain) from passive adversaries. It also prevents traffic analysis attacks that might target specific domains.
7.2 Removal of Compression and Renegotiation
TLS compression was removed because of the CRIME attack. Renegotiation (the ability to re-negotiate parameters mid-session) was removed because it was a vector for insecure renegotiation attacks (e.g., the client could change identity). TLS 1.3 does not support any form of renegotiation.
7.3 Downgrade Protection via Random Values
TLS 1.3 introduces two special 8-byte strings that the server places in its server_random:
- If the server negotiates TLS 1.3, but the client’s initial message indicated a lower version, the server sets the first 8 bytes of its random to
44 4F 57 4E 47 52 44 01(ASCII “DOWNGRD\x01”) for TLS 1.2 downgrade, or44 4F 57 4E 47 52 44 00for TLS 1.1. - The client checks for these markers; if present but the server is not claiming that version, the client aborts.
This makes downgrade attacks extremely difficult because the attacker must either remove the marker (which would break the handshake hash) or provide valid random values (impossible without the server’s secret).
7.4 Key Derivation with HKDF
TLS 1.3 uses HKDF, a two-step process that is more robust than the old PRF. The extract-then-expand paradigm ensures that even if the (EC)DHE shared secret is partial (e.g., when using PSK), the output is still strong.
7.5 Perfect Forward Secrecy for All Sessions
Because static key exchange is banned, every session has FS. Even 0-RTT sessions can have FS if the client includes an (EC)DHE key share along with the PSK. The server can then derive the handshake secret using both the PSK and the ephemeral key, so the early data is forward secret (if the ephemeral key is used). If the server only uses the PSK (for maximum speed), the 0-RTT data is not forward secret, but the rest of the session can be upgraded to FS.
8. Performance Implications
8.1 Latency Reduction
The most visible performance impact is the reduction from 2-RTT to 1-RTT for a full handshake. On a network with 50 ms round-trip time, that saves 50 ms. For mobile networks (100–200 ms RTT), the savings are even more dramatic.
But the real win is 0-RTT. For returning users, the handshake overhead is completely eliminated. This has a huge impact on page load times for repeat visits. Google reported that with TLS 1.3 and QUIC (which uses a similar handshake), page load times decreased by 5–10%.
8.2 CPU Overhead
Switching from RSA key exchange (which involves expensive modular exponentiation) to ECDHE is computationally lighter (especially on the server side). Elliptic curve Diffie-Hellman is fast, and AEAD ciphers (AES-GCM) can be accelerated with hardware instructions (AES-NI, PCLMULQDQ). The removal of CBC and HMAC also simplifies the implementation.
However, TLS 1.3 does require more computation per handshake because the server must generate an ephemeral key pair. But this is negligible compared to the overall cost of a TLS session.
8.3 Handshake Size
TLS 1.3 handshake messages are slightly larger due to the key share extension (which includes a public key). However, the overall number of messages is reduced (e.g., no separate ServerKeyExchange and ServerHelloDone). This results in fewer packets on the wire.
8.4 Real-World Measurements
Cloudflare published a study showing that TLS 1.3 reduced the median handshake time by 33% compared to TLS 1.2. Google reported similar improvements. The ability to use 0-RTT for APIs and single-round-trip connections is a major enabler for the modern Web.
9. Deployment and Adoption
9.1 Browser and Client Support
All major browsers support TLS 1.3:
- Chrome (since version 70, 2018)
- Firefox (since version 63, 2018)
- Safari (since macOS 10.15, iOS 13, 2019)
- Edge (since version 79, based on Chromium)
Most clients use the BoringSSL or NSS implementations.
9.2 Server Software
- OpenSSL 1.1.1 (released 2018) supports TLS 1.3.
- BoringSSL (Google) was the first implementation and used in Google services.
- LibreSSL and NSS also support it.
Major web servers (Nginx, Apache with mod_ssl, IIS with SChannel) have supported TLS 1.3 for years. Cloudflare enabled it early via their own fork.
9.3 Backward Compatibility and Middlebox Interference
One of the biggest challenges during TLS 1.3 deployment was middleboxes (firewalls, load balancers, proxies) that inspect or modify TLS handshakes. Many of these boxes were built to understand TLS 1.2 and would drop or corrupt TLS 1.3 handshakes. To mitigate this, the TLS working group introduced a technique called GREASE (Generate Random Extensions And Sustain Extensibility). Client implementations send random, unknown extensions to force middleboxes to ignore extensions they don’t recognize. This ensures that future protocol extensions can be deployed without breaking the network.
Additionally, the ClientHello version field is set to TLS 1.0 (or 1.2) to avoid alarming middleboxes that check the version field; the true version is advertised in the supported_versions extension.
9.4 Transition from TLS 1.2
Most servers now support both TLS 1.2 and TLS 1.3. Clients will negotiate the highest mutual version. Because TLS 1.3 has been stable since 2018 and all major software has adopted it, the majority of web traffic (over 95%) is now encrypted with TLS 1.3. However, some legacy servers (e.g., embedded systems) may still only support TLS 1.2.
10. Practical Examples and Code
Let’s get our hands dirty with some practical examples.
10.1 Wireshark Capture of TLS 1.3 Handshake
Below is a description of a TLS 1.3 handshake captured with Wireshark. We will interpret the key fields.
Frame 1: Client → Server (ClientHello)
- TLSv1.3 Record Layer: Handshake Protocol: ClientHello
- Handshake Protocol: ClientHello
- Version: TLS 1.0 (0x0301) // backward compat
- Random: 32 bytes
- Session ID Length: 0
- Cipher Suites (2 suites)
- TLS_AES_128_GCM_SHA256 (0x1301)
- TLS_CHACHA20_POLY1305_SHA256 (0x1303)
- Compression Methods: null
- Extensions:
- supported_versions: TLS 1.3 (0x0304), TLS 1.2 (0x0303)
- key_share: group: x25519, key_exchange: 32 bytes
- signature_algorithms: ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256…
- supported_groups: x25519, secp256r1…
Frame 2: Server → Client (ServerHello)
- TLSv1.3 Record Layer: Handshake Protocol: ServerHello
- Handshake Protocol: ServerHello
- Version: TLS 1.2 (0x0303) // compat
- Random: includes DOWNGRD marker? Actually server sets first 8 bytes to
CF AD BA DA...- no, it’s not a downgrade; this is a real TLS 1.3 handshake. The random contains a hard-coded 8-byte string44 4F 57 4E 47 52 44 01if it’s a downgrade from TLS 1.3 to 1.2. Otherwise it’s random. - Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
- Extensions:
- key_share: group: x25519, key_exchange: 32 bytes
- supported_versions: TLS 1.3 (0x0304)
Frame 3: Server → Client (EncryptedExtensions)
- Encrypted with handshake keys. Frame 4: Server → Client (Certificate)
- Encrypted, contains certificate chain. Frame 5: Server → Client (CertificateVerify)
- Encrypted signature. Frame 6: Server → Client (Finished)
- Encrypted handshake MAC.
Frame 7: Client → Server (Finished)
- Encrypted.
Frame 8: Client → Server (Application Data)
- HTTP request (e.g., GET /).
10.2 Using OpenSSL s_client to Verify TLS 1.3
On a system with OpenSSL 1.1.1+, run:
openssl s_client -connect example.com:443 -tls1_3
Output will show:
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
...
To see the handshake messages in detail, add -msg flag.
10.3 Python Code with SSL Module
Python’s ssl module (since 3.7) supports TLS 1.3 by default. Example client:
import ssl
import socket
context = ssl.create_default_context()
context.set_ciphers('TLS_AES_128_GCM_SHA256') # TLS 1.3 ciphers
sock = context.wrap_socket(socket.socket(), server_hostname='example.com')
sock.connect(('example.com', 443))
print("TLS version:", sock.version())
sock.sendall(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
data = sock.recv(4096)
print(data)
sock.close()
Running this will output TLS version: TLSv1.3 if the server supports it.
10.4 Key Exchange Calculation (Illustration)
To illustrate the Diffie-Hellman key exchange, we can use Python with the cryptography library. Here’s a simplified simulation of the client and server computing the shared secret using X25519:
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives import serialization
# Server generates its long-term key pair (not used for encryption, just for signing)
# But for the handshake, each side generates an ephemeral key pair.
# Client side
client_private = x25519.X25519PrivateKey.generate()
client_public = client_private.public_key()
# Server side
server_private = x25519.X25519PrivateKey.generate()
server_public = server_private.public_key()
# Each computes shared secret
client_shared = client_private.exchange(server_public)
server_shared = server_private.exchange(client_public)
assert client_shared == server_shared
print("Shared secret (hex):", client_shared.hex())
The resulting shared secret is then used in the HKDF-based key schedule.
11. Comparison Table: TLS 1.2 vs TLS 1.3
| Feature | TLS 1.2 | TLS 1.3 |
|---|---|---|
| Full handshake round trips | 2-RTT | 1-RTT |
| 0-RTT support | No | Yes (with PSK) |
| Forward secrecy | Optional (RSA key exchange allowed) | Mandatory (only (EC)DHE) |
| Allowed key exchange | RSA, DH, ECDHE, PSK | (EC)DHE, PSK |
| Allowed cipher modes | CBC, GCM, CCM, RC4 | Only AEAD (GCM, CCM, ChaCha20-Poly1305) |
| RSA signature scheme | PKCS1v1.5, PSS | Only PSS (RSASSA-PSS) |
| Encryption of handshake | No (plaintext Certificate) | Yes (Certificate, CertificateVerify encrypted) |
| Downgrade protection | Weak (Finished only) | Strong (via random markers and supported_versions) |
| Compression | Allowed | Removed |
| Renegotiation | Supported | Removed |
| Signature algorithms | Flexible, including MD5 | Fixed set (RSA-PSS, ECDSA, EdDSA) |
| Key derivation | PRF (SHA-1, SHA-256) | HKDF (Extract-then-Expand) |
| Mid-compat for middleboxes | None | Uses version fields and GREASE |
12. Conclusion
TLS 1.3 is not merely a version increment—it is a fundamental rethinking of how we establish secure channels on the Internet. By eliminating legacy cruft, mandating forward secrecy, reducing latency, and encrypting more of the handshake, the protocol has become leaner, faster, and more secure. The TLS working group showed remarkable discipline in resisting the temptation to maintain backward compatibility with every broken cipher suite. The result is a protocol that will serve as the foundation for Internet security for decades to come.
The next time you load a webpage, send a text, or swipe your credit card, remember that the TLS 1.3 handshake has, in a single round trip, negotiated a secure channel with perfect forward secrecy, verified the server’s identity, and (if you are a returning user) already delivered your data to the server. That trust is no longer a matter of hope—it is a matter of well-engineered mathematics.
For those building secure applications, the message is clear: upgrade to TLS 1.3, use 0-RTT where appropriate, and never look back at the slow, brittle handshakes of the past. The future of secure communication is here, and it’s one round trip away.