The TLS Handshake
Topic 45

The TLS Handshake

Handshake

The handshake is the opening exchange where TLS does all the expensive work: the two sides agree on a protocol version and cipher, the server authenticates itself with its certificate, and both derive a shared symmetric session key — all before a single byte of application data flows. Once the handshake completes, the connection is cheap; until it does, nothing useful has moved.

The handshake's round trips are a real, measurable part of connection latency. On TLS 1.2 the setup costs two round trips on top of TCP's own; TLS 1.3 cut that to one by default, added a zero-round-trip resumption mode, and deleted a generation of insecure options outright. On a 100 ms transcontinental path, dropping one round trip is 100 ms a user feels on every fresh connection.

Handshake round trips on a 100 ms path (before the first byte)
TLS 1.2
2 RTT · 200 ms
TLS 1.3
1 RTT · 100 ms
1.3 resume (0-RTT)
0 RTT · ~0 ms
0 ms100 ms200 ms
1.3 halves setup by front-loading the key share; 0-RTT resumption sends data in the first flight — at the cost of replay safety.

The TLS 1.2 Handshake

A 1.2 handshake takes two round trips before application data. The client opens with a ClientHello listing its supported TLS versions, cipher suites, and a random nonce. The server replies with a ServerHello picking one cipher suite, its certificate, the key-exchange parameters, and a ServerHelloDone. The client verifies the certificate, completes the key exchange, and both sides switch to encrypted records and exchange Finished messages.

The weakness of 1.2 is everything it permitted. It allowed RSA key transport, where the client encrypts the session key to the server's public key — with no forward secrecy — and it carried a long tail of weak ciphers (RC4, CBC modes with padding-oracle bugs, export-grade suites) that a downgrade attack could coax both sides into using. Each option was a configuration mistake waiting to happen.

TLS 1.3 and the One-Round-Trip Handshake

TLS 1.3 redesigned the flow so the client guesses the key-exchange group and sends its key share in the ClientHello. The server responds with its own share, its certificate, and a Finished — and the client can send application data with its first reply. That is one round trip, half of 1.2's cost, with no negotiation penalty in the common case.

It is also safer because of what it removed. RSA key transport, static Diffie-Hellman, RC4, CBC-mode ciphers, compression, and renegotiation are all gone; the only key exchanges left are ephemeral, so forward secrecy is mandatory, not optional. The cipher-suite menu shrank from dozens of mostly-broken choices to five strong AEAD suites. TLS 1.3 is faster and safer at the same time, which is rare.

# inspect a live handshake: version, cipher, and cert chain
openssl s_client -connect example.com:443 -tls1_3 </dev/null
# New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
# Server certificate ... subject=CN=example.com
# Verify return code: 0 (ok)   <- chain validated to a trusted root

Key Exchange and Forward Secrecy

The session key is never sent on the wire. Instead both sides run an ephemeral Diffie-Hellman exchange (ECDHE over an elliptic curve like X25519): each generates a one-time key pair for this connection, swaps public values, and independently computes the same shared secret. An eavesdropper sees both public values and still cannot derive the secret, because that requires a private value neither side transmits.

Forward secrecy is the payoff of the word ephemeral. Because the key pair is discarded when the connection ends, an attacker who later steals the server's long-term private key cannot decrypt yesterday's recorded traffic — that traffic used a session key the stolen key never touched. The old RSA-key-transport mode lacked this: one stolen private key decrypted every past session captured under it, which is exactly the "harvest now, decrypt later" risk forward secrecy defeats.

Session Resumption and 0-RTT

Resuming a previous session skips the certificate and key-exchange work. TLS 1.3 hands the client a pre-shared-key ticket after the first connection; on reconnect the client presents it and the two sides are back to encrypted traffic in one round trip — or in 0-RTT, the client sends application data in the very first flight alongside the ticket, before any round trip completes.

0-RTT buys latency but reopens a door: that early data is replayable. An attacker who captures a 0-RTT request can resend it, and the server has no handshake context yet to reject the duplicate. Sending a 0-RTT POST /transfer twice could move money twice. The rule is firm — 0-RTT is for idempotent requests only (a GET, a cacheable read); anything that changes state must wait for the full one-round-trip handshake.

TLS 1.2 vs TLS 1.3

TLS 1.2 needs two round trips to set up, still permits RSA key transport with no forward secrecy, and carries a long menu of weak and legacy ciphers a downgrade attack can target. Keep it enabled only for clients that genuinely cannot do 1.3, and disable the broken suites explicitly.

TLS 1.3 sets up in one round trip (0-RTT on resumption), makes ephemeral key exchange and forward secrecy mandatory, and offers only five AEAD cipher suites. It is both faster and safer — the default for anything you control on both ends — with 0-RTT's replay caveat the one operational footgun.

Common Mistakes
  • Leaving TLS 1.0 and 1.1 enabled. They allow ciphers with practical attacks (BEAST, POODLE) and are out of PCI compliance; a downgrade can force a modern client onto them unless you disable them at the server.
  • Allowing RSA key-transport cipher suites. They provide no forward secrecy, so one future theft of the server private key decrypts every past session recorded under it — the harvest-now-decrypt-later scenario.
  • Enabling 0-RTT for non-idempotent requests. Early data is replayable, so a captured 0-RTT POST can be resent and executed twice, with no handshake context to detect the duplicate.
  • Ignoring handshake RTT in latency budgets. Each fresh TLS connection adds one to two round trips before the first byte; on a 100 ms path that is 100–200 ms users feel that connection pooling and 1.3 would remove.
  • Letting server and client clocks drift. Certificate validity is time-checked, so a host whose clock is days off rejects a valid certificate as not-yet-valid or expired, breaking every handshake until NTP corrects it.
Best Practices
  • Set TLS 1.2 as the floor and 1.3 as the preferred version, disabling 1.0 and 1.1 outright so no downgrade can land a client on a broken protocol.
  • Restrict cipher suites to ephemeral AEAD only — ECDHE with AES-GCM or ChaCha20-Poly1305 — so every session has forward secrecy and no padding-oracle exposure.
  • Reuse connections with keep-alive and HTTP/2 or HTTP/3 so the handshake cost is paid once and amortized across many requests instead of per request.
  • Restrict 0-RTT to idempotent, safe methods, or disable it, so a replayed early-data request cannot duplicate a state-changing operation.
  • Run NTP on every TLS endpoint and alert on clock skew over a few minutes, because validity-period checks turn a drifting clock into a total handshake outage.
Comparable conceptsQUIC (folds the handshake in, Topic 40)mTLS (adds client auth, Topic 48)SSH key exchange

Knowledge Check

What does forward secrecy from ephemeral Diffie-Hellman protect against that RSA key transport does not?

  • Decryption of old recorded sessions after the server's long-term key is later stolen
  • An attacker forging the server's certificate to impersonate it live during the current handshake
  • Tampering with the application data of the current live connection
  • A downgrade of the connection to an older, weaker TLS version

Why is enabling 0-RTT early data risky for a request that transfers money?

  • The early data is replayable, so a captured request can be resent and run twice
  • The early data is sent in plaintext before the session key exists
  • It adds an extra network round trip that delays the transfer past its configured timeout
  • The server is no longer authenticated, so the money could go anywhere

How does TLS 1.3 achieve a one-round-trip handshake where TLS 1.2 needs two?

  • The client sends its key share in the ClientHello, finishing key agreement a round trip sooner
  • It skips server authentication entirely to save the extra exchange
  • It opens fewer underlying TCP connections, which is what removes one full network round trip from setup
  • It sends the first application data unencrypted to avoid waiting on a key

You got correct