Understanding TLS 1.3 handshakes
TLS 1.3 (RFC 8446) is not just TLS 1.2 with a new version number. The handshake was redesigned to take one round trip instead of two, and almost everything after the first message is encrypted. Here is what happens, step by step.
The client guesses
In TLS 1.2 the client and server first agreed on a key exchange and only then exchanged key material. TLS 1.3 skips the negotiation: the client guesses which groups the server supports and sends public keys for them right away in the key_share extension of its ClientHello.
ClientHello
supported_versions: TLS 1.3, TLS 1.2
supported_groups: X25519MLKEM768, X25519, P-256, P-384
key_share: X25519MLKEM768 (1216 bytes), X25519 (32 bytes)
signature_algorithms, server_name, ALPN: h2, http/1.1 ...
The version field in the record header still says TLS 1.2 for compatibility with old middleboxes; the real version lives in supported_versions.
The server answers — and starts encrypting
The server picks one of the offered key shares, sends its own share in ServerHello, and both sides can now derive handshake keys. Everything after ServerHello is encrypted:
- EncryptedExtensions — ALPN result and other extensions that do not need to be public.
- Certificate — the server's certificate chain.
- CertificateVerify — a signature over the handshake so far, proving possession of the private key.
- Finished — a MAC over the whole transcript.
The client checks the certificate, sends its own Finished, and can send application data in the same flight. One round trip in total.
If none of the client's guesses was acceptable, the server sends a HelloRetryRequest naming the group it wants, and the client tries again. That costs an extra round trip, which is why clients send shares for the most common groups up front.
Session resumption and 0-RTT
After a handshake the server can send a session ticket. On the next connection the client presents it as a pre-shared key and may send early data before the handshake finishes. 0-RTT saves a round trip but early data can be replayed by an attacker, so servers should only accept it for idempotent requests like GETs — or not at all.
Hybrid post-quantum key exchange
Since late 2024 browsers offer X25519MLKEM768 by default: a hybrid that combines classical X25519 with ML-KEM-768 (the standardized form of Kyber). The shared secret is safe as long as either part holds, which protects today's traffic against "record now, decrypt later" attacks.
The cost is size. The ML-KEM public key makes the client's key share about 1.2 KB, so a ClientHello now often exceeds 1,500 bytes and spans two TCP segments. Poorly written middleboxes that expected the whole ClientHello in one packet broke when this rolled out; most have since been fixed.
Watching a handshake
openssl s_client -connect example.com:443 -servername example.com -tls1_3 -msg </dev/null
The -msg flag prints each handshake message. For a full picture, capture with tcpdump and open the file in Wireshark; set SSLKEYLOGFILE in a browser or curl to decrypt your own sessions.
Quick reference
| TLS 1.2 | TLS 1.3 | |
|---|---|---|
| Full handshake | 2 RTT | 1 RTT |
| Certificate visible on the wire | Yes | No |
| Static RSA key exchange | Allowed | Removed |
| Forward secrecy | Optional | Always |
| Resumption | Session IDs / tickets | PSK, optional 0-RTT |