Frontend Labs
⋯

Chapter 28 — The TLS 1.3 handshake, step by step

In one sentence

TLS 1.3 establishes an authenticated, forward-secret encrypted channel in one round trip, by having the client guess the key-exchange group and send its share immediately.

Why TLS 1.3 and not 1.2

TLS 1.2 (2008) TLS 1.3 (2018)
Handshake round trips 2 1 (0 with resumption)
Key exchange RSA transport or (EC)DHE (EC)DHE only — forward secrecy always
Cipher suites 300+, many broken (RC4, 3DES, CBC, export) 5, all AEAD
Negotiation in the clear, downgrade-prone mostly encrypted, downgrade-protected
Vulnerable to BEAST, CRIME, Lucky13, POODLE, ROBOT, FREAK, Logjam... none of that class by design
MD5/SHA-1 in handshake yes removed

Use TLS 1.3. Support 1.2 only for legacy clients. Disable 1.0 and 1.1 (dead since 2021) and SSLv3 (dead since 2015).

The five TLS 1.3 cipher suites (the whole list)

   TLS_AES_128_GCM_SHA256          <- mandatory to implement
   TLS_AES_256_GCM_SHA384         <- prefer this (Ch 23: 256-bit for PQ)
   TLS_CHACHA20_POLY1305_SHA256   <- prefer on devices without AES-NI
   TLS_AES_128_CCM_SHA256         <- constrained / IoT
   TLS_AES_128_CCM_8_SHA256       <- constrained, short tag (avoid if you can)

Notice a suite name no longer bundles a key exchange or a signature algorithm — those are negotiated separately in TLS 1.3. A suite is just AEAD + hash.

The handshake, message by message

   CLIENT                                                    SERVER
   ------                                                    ------

   ClientHello  ------------------------------------------->
     - TLS version(s) supported (pretends 1.2 for middleboxes,
       signals 1.3 via "supported_versions" extension)
     - client random (32 bytes)
     - list of cipher suites
     - "key_share": client's EPHEMERAL ECDHE public key(s),
        e.g. X25519 -- sent NOW, as a guess, to save a round trip
     - "supported_groups", "signature_algorithms"
     - SNI: the hostname it wants (server_name extension)
     - optional: PSK / session ticket for resumption, early_data

                <------------------------------------------- ServerHello
                  - chosen cipher suite
                  - server random (32 bytes)
                  - "key_share": server's ephemeral ECDHE public key

   *** Both sides now run ECDHE -> shared secret -> HKDF ->
       a set of keys. EVERYTHING BELOW THIS LINE IS ENCRYPTED. ***

                <------------------------------------------- {EncryptedExtensions}
                <------------------------------------------- {Certificate}
                  - the server's certificate chain (leaf + intermediates)
                <------------------------------------------- {CertificateVerify}
                  - a SIGNATURE, with the cert's private key, over a
                    transcript hash of the handshake so far.
                    THIS is what proves the server holds the private key
                    for the cert -- ties identity to this exact session.
                <------------------------------------------- {Finished}
                  - HMAC over the whole transcript (integrity of the
                    handshake itself; downgrade/tamper protection)

   {Finished}   ------------------------------------------->
                  - client's transcript HMAC

   [Application Data] <====================================> [Application Data]
     HTTP requests/responses, protected by the negotiated AEAD

{ } = encrypted with handshake keys. [ ] = encrypted with application keys. One round trip from ClientHello to sending real data.

What each cryptographic piece is doing

   client/server randoms  -> ensure each handshake is unique (anti-replay
                             into the key schedule)
   ECDHE key_share         -> forward-secret shared secret (Ch 18, 20)
   HKDF key schedule       -> derives distinct keys for handshake, application,
                             each direction, plus exporter/resumption secrets
   Certificate             -> "here is my identity" (Ch 29)
   CertificateVerify       -> "and I can prove I own it, RIGHT NOW, for THIS
                             handshake" (signature over the transcript)
   Finished (both sides)   -> "the handshake you saw is the handshake I saw"
                             -> defeats downgrade and tampering

The combination is what stops a man-in-the-middle: an attacker can relay bytes, but cannot produce a valid CertificateVerify for the real domain (no private key) and cannot forge the Finished MAC (no keys).

0-RTT resumption (and its one danger)

If the client has a PSK/ticket from a previous session, it can send application data in the very first flight ("early data", 0-RTT):

   + zero round trips before the first request -- great for latency

   - REPLAYABLE. An attacker who captures the 0-RTT data can re-send it.
     For a GET it's usually harmless; for "POST /transfer" it is not.

   Rule: only allow 0-RTT for idempotent, non-state-changing requests.
   Most servers restrict it to GET/HEAD or disable it. Know your config.

Where each Part's concepts show up

   Part 3 (hashing)      -> transcript hash, HKDF, Finished HMAC
   Part 4 (AEAD)         -> the record protection (AES-GCM / ChaCha20-Poly1305)
   Part 5 (asymmetric)   -> ECDHE key exchange + certificate signature
   Part 6 (PQC)          -> key_share can be X25519MLKEM768 (hybrid)
   Part 7 (this)         -> the choreography that assembles them safely

Practice (35 min)

cd ~/sec-lab/scratch

# 1. Watch a full handshake in detail
openssl s_client -connect wikipedia.org:443 -servername wikipedia.org \
        -tls1_3 -msg -state 2>&1 | sed -n '1,60p'

# 2. Force each version and see what negotiates / fails
for v in tls1_3 tls1_2 tls1_1; do
  echo "--- $v ---"
  echo | openssl s_client -connect wikipedia.org:443 -servername wikipedia.org \
        -$v 2>&1 | grep -E "Protocol|Cipher|handshake failure|no protocols"
done

# 3. See the key exchange group (forward secrecy in action)
echo | openssl s_client -connect wikipedia.org:443 -servername wikipedia.org \
        2>&1 | grep -E "Server Temp Key|Negotiated .* group"

# 4. Capture a handshake and read it in Wireshark
sudo tcpdump -i any -w /tmp/tls13.pcap 'tcp port 443' -c 60 &
curl -s https://wikipedia.org -o /dev/null; sleep 1; sudo pkill tcpdump
echo "open /tmp/tls13.pcap in Wireshark; filter: tls.handshake"
# In TLS 1.3 you'll see ClientHello and ServerHello in the clear,
# then everything else shows as 'Application Data' -- because the
# rest of the handshake is already encrypted.

# 5. TLS 1.3 with a keylog, so Wireshark CAN decrypt (your own traffic only)
SSLKEYLOGFILE=/tmp/keys.log curl -s https://wikipedia.org -o /dev/null
# In Wireshark: Preferences -> Protocols -> TLS -> (Pre)-Master-Secret log
#   filename = /tmp/keys.log   -> now the encrypted handshake decrypts

Common confusions

  • "TLS 1.3 handshake is encrypted." ClientHello and ServerHello are not (the keys don't exist yet). Everything after ServerHello is. SNI is in the clear unless ECH is used.
  • "The certificate proves the server is safe." It proves domain control. The CertificateVerify signature proves the server holds the matching private key for this handshake.
  • "0-RTT is just faster, use it everywhere." It's replayable. Restrict to idempotent requests.
  • "RSA key exchange is still a thing." Removed in TLS 1.3. If you see TLS_RSA_WITH_... it's TLS 1.2 or older, and it has no forward secrecy.
  • "More cipher suites = more secure." TLS 1.3 deliberately has five, all safe. Long suite lists were a TLS 1.2 liability.

Check yourself

  1. How many round trips does a full TLS 1.3 handshake take? What about resumption?
  2. What does CertificateVerify prove that the Certificate message alone does not?
  3. What is the Finished message for?
  4. Why is 0-RTT data dangerous, and what requests may safely use it?
  5. Which parts of a TLS 1.3 handshake are visible to a passive eavesdropper?
  6. Why does the client send a key_share in the ClientHello before knowing what the server supports?

Further reading

  • RFC 8446 — TLS 1.3. Section 2 ("Protocol Overview") is genuinely readable; skim the rest.
  • Site: tls13.xargs.org — "The Illustrated TLS 1.3 Connection", every byte of a real handshake annotated. Outstanding. (Also tls12.xargs.org.)
  • Video: Cloudflare's "TLS 1.3 explained" and Robert Heaton's "How does TLS work" write-up.
  • Book: Bulletproof TLS and PKI, Ristić — the handshake chapter.
  • Tool: Wireshark with SSLKEYLOGFILE — decrypt your own TLS to see inside.

Generated from real-life-guide.md · 8 Oct 2026 16:36