TLS 1.3 Handshake
One RTT, key shares, encrypted extensions, Finished.
TLS 1.3 Handshake
Every HTTPS connection begins with a handshake that agrees on a key, proves the server's identity, and does it all in one round-trip. TLS 1.3 is what happens in the ~20ms between typing a URL and getting bytes back. Understanding the handshake means understanding how AES, ECDH, and certificate signatures compose in the real world.
Analogy
Two diplomats meet at a neutral cafe. Before they speak, they have to do three things: agree on a language only they understand, confirm each other's identity, and make sure nobody at the next table has quietly swapped in an impostor. In TLS 1.2 all of this took two rounds of greetings; TLS 1.3 folds everything into a single exchange — they show each other their passports, agree on a shared private language (the session key), and sign a transcript of the whole meeting all in one sitting. After that opening exchange, every sentence is spoken in the private language, and any interloper who whispers at the table is instantly exposed because the transcript signature won't match.
Why 1.3 is 1-RTT
TLS 1.2 took two round-trips to finish. The client sent a Hello, the server sent one back with a chosen suite, then they ran a separate key exchange. TLS 1.3 folds them together: the client speculatively sends a key_share with its ClientHello, the server picks a group it supports and replies with its own key_share. They derive the shared secret immediately. Everything after ServerHello is encrypted with handshake traffic keys.
Client Server
ClientHello
+ key_share (X25519 public)
+ supported_versions = [0x0304]
+ cipher_suites = [...]
+ signature_algorithms = [...] -------->
ServerHello
+ key_share (X25519 public)
+ cipher_suite
<-------- {EncryptedExtensions}
{Certificate}
{CertificateVerify}
{Finished}
{Finished} -------->
[Application data] <-------> [Application data]
Braces mean "encrypted under the handshake key". By the time the server sends its Certificate, the bytes are already encrypted; only a passive observer with the server's private key (which they won't have) can read them.
The five parts that actually matter
ClientHello. Lists supported cipher suites, named groups for key exchange, and signature algorithms. TLS 1.3 bakes in one of four AEAD ciphers: AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305, or AES-128-CCM. That's the whole menu.
ServerHello + key_share. The server picks one suite and one group. It sends its own ephemeral public key. At this moment both sides can compute the shared secret via DH — even though the server hasn't yet proven its identity.
Certificate + CertificateVerify. Now identity. The server sends its certificate chain. CertificateVerify is a signature made with the server's private key over the handshake transcript (everything sent so far). This is what binds the certificate to the session — a MITM who captured the handshake can't forge this because they don't have the private key.
Finished. Both sides send an HMAC over the full transcript, using a key derived from the shared secret. If either side computes a different transcript, the HMACs won't match and the connection aborts. This is tamper detection on the handshake itself.
What negotiation actually is
The server walks its own preference list and picks the first suite the client also offered. Same for named groups (for the key_share) and signature algorithms. Any one of those three missing a mutual option aborts the handshake. Modern browsers hedge against this by offering ChaCha20-Poly1305 (good on phones without AES hardware) plus AES-GCM (good on servers with AES-NI). Modern servers prefer ChaCha20 for mobile and AES for desktop — but the server has final say.
openssl s_client -connect example.com:443 -tls1_3 -servername example.com
# Look for:
# Protocol : TLSv1.3
# Cipher : TLS_AES_256_GCM_SHA384
# Server Temp Key: X25519, 253 bits
Forward secrecy, for free
TLS 1.3 removes all key-transport cipher suites. The only way to do key exchange is ephemeral (EC)DH. The server's long-term private key is used for authentication only (via CertificateVerify), never to encrypt session keys. Compromising the server's private key months later doesn't let an attacker decrypt yesterday's recorded traffic — the ephemeral DH keys are gone.
Sessions resumption and 0-RTT
For repeated connections the server can issue a session ticket encrypted under a key only it holds. Next visit, the client sends the ticket and skips most of the handshake (PSK resumption). 0-RTT lets the client send application data inside ClientHello, encrypted with a key derived from the previous session. It's fast but replay-vulnerable — a captured 0-RTT request can be replayed to the server. OpenSSL and Go both default 0-RTT off; Cloudflare famously turned it on with careful server-side deduplication.
What the playground tests
You'll be given a client's offered suites/groups/sigalgs and a server's. Work out what the server picks (or why it fails). This is exactly the logic in SSL_CTX_set_cipher_list plus SSL_CTX_set1_groups_list in OpenSSL, or tls.Config.CipherSuites in Go.
When it still goes wrong
Cipher suite downgrade (SSLv3 POODLE, TLS 1.2 BEAST/Lucky13) — killed by TLS 1.3 dropping every non-AEAD cipher. Protocol downgrade attacks — killed by the signature_algorithms extension being included in CertificateVerify. Record-layer flaws (RC4, CBC-MAC-then-encrypt) — killed by mandatory AEAD. TLS 1.3 is the most aggressive "remove, don't fix" revision the IETF has ever shipped. It's the reason we can finally stop saying "TLS is hard".