🤝 The TLS handshake, step by step
Every time you see the browser padlock, this just happened in milliseconds: a handshake that agrees on a secret key without ever sending it and checks that the server is who it claims to be. Walk through it step by step (TLS 1.3).
The exchange, message by message
-
01 ClientHello
ClientHello — “here’s what I support”
The client proposes the TLS versions and cipher suites it understands, the curve groups and —crucially— its key share: an ephemeral public key (ECDHE). It includes the SNI: which domain it wants to reach.
Sent in the clear. The SNI reveals the destination domain (the ECH extension encrypts it). -
02 ServerHello
ServerHello — picks a cipher and shares its key
The server picks a cipher from the list and sends its own key share. With each other’s public key, both sides derive the same shared secret (ECDHE) — and that secret never travels across the wire.
From here on the whole handshake is encrypted with keys derived from that secret. -
03 {EncryptedExtensions} 🔒
EncryptedExtensions
Connection parameters —for example the application protocol (ALPN: HTTP/2, HTTP/3…)—, already encrypted.
In TLS 1.2 this traveled in the clear; in 1.3, it doesn’t. -
04 {Certificate} 🔒
Certificate — “this is my identity”
The server sends its certificate (an X.509 chain) signed by a Certificate Authority (CA) your system already trusts.
Encrypting without authenticating isn’t enough: without this, a man-in-the-middle could impersonate the server. -
05 {CertificateVerify} 🔒
CertificateVerify — proof of possession
The server signs the whole conversation so far with the certificate’s private key. Only whoever owns that key can produce that signature.
It binds the certificate to this specific connection: holding the certificate isn’t enough — you must own its private key. -
06 {Finished} 🔒
Finished (server) — integrity seal
An authentication code (MAC) over the whole handshake: it guarantees nothing was tampered with along the way.
If an attacker had altered an earlier message, this seal wouldn’t match and the connection aborts. -
07 {Finished} 🔒
Finished (client) — handshake complete
The client validates the certificate (the chain up to a trusted CA, and the signature) and replies with its own Finished. All in a single round trip (1-RTT).
If the certificate isn’t trusted or doesn’t match the domain, this is where the browser shows the warning. -
08 Application Data 🔒
Encrypted channel established 🔒
Client and server now exchange application data (your HTTP) encrypted with the derived session keys.
Forward secrecy: the ephemeral keys are discarded. If the server’s private key is stolen tomorrow, today’s traffic still can’t be decrypted.
Without JavaScript you get the full handshake as an article. With JS, step through it (arrow keys ← → work too).
🔑 The key that never travels
In ECDHE, each side sends its public key and keeps the private one. Combining the other’s public key with its own private key, both reach the same secret — which never crosses the wire. Because the keys are ephemeral (one per session), you get forward secrecy.
🪪 Encrypting isn’t authenticating
An encrypted channel with the attacker is useless. The certificate (signed by a CA) states who the server is, and CertificateVerify proves it owns the private key. Without that pair, a man-in-the-middle could slip in.
⚡ TLS 1.3 vs 1.2
TLS 1.3 closes the handshake in one round trip (1-RTT, previously two), encrypts the certificate and only allows ciphers with forward secrecy. Less latency and less surface: no more RSA key exchange or legacy ciphers.
Source: RFC 8446 (TLS 1.3). Want to see a real domain’s certificate and TLS posture? Try the security scorecard.