Skip to content

🀝 09-02: TLS Handshake Walkthrough


πŸ“Œ Why a Handshake Is Needed at All

Before a client and server can exchange a single byte of protected application data, they need to agree on several things: which encryption algorithm to use, which key to use, which integrity (MAC) algorithm to use, and how to exchange the keying material safely over a network an attacker might be watching. Settling all of this is the entire job of the TLS handshake protocol β€” it's the negotiation phase that happens before the "real" data ever flows.

Since TLS runs on top of TCP, a full three-way TCP handshake has already completed before any of this TLS negotiation even begins. The TLS handshake is a separate, additional negotiation layered on top of that established TCP connection.

πŸ’‘ Think of the TLS handshake as two strangers meeting to agree on a private language and a shared secret codeword before they'll say anything sensitive out loud β€” and doing so in a room where someone else might be listening the entire time.


πŸͺœ The TLS 1.2-Style Handshake, Step by Step

This walkthrough follows the classic (TLS 1.2 and earlier) handshake flow, which is more spread out and easier to learn from than TLS 1.3's compressed version.

CLIENT                                                    SERVER
  |------------------- ClientHello ----------------------->|
  |<---------- ServerHello, Certificate, --------------------|
  |<---------- ServerKeyExchange, ServerHelloDone -----------|
  |------------------- ClientKeyExchange ------------------->|
  |------------------- ChangeCipherSpec -------------------->|
  |------------------- Finished ----------------------------->|
  |<---------- ChangeCipherSpec ------------------------------|
  |<---------- Finished ---------------------------------------|
  |<==================  Application Data  ===================>|

Step 1: ClientHello

The client kicks things off by sending a ClientHello message, which includes:

  • The highest TLS version it supports
  • A random number (the "client random") β€” used later as an input when deriving session keys, ensuring the final keys aren't purely dependent on secrets that might repeat
  • A list of cipher suites it's willing to use, in preference order (what a cipher suite name actually encodes is covered in 09-03: Cipher Suites)

Step 2: ServerHello

The server picks one cipher suite from the client's offered list (the one it prefers most, among ones it supports) and replies with a ServerHello, including its own random number (the "server random") and the chosen cipher suite. At this point, both sides know which algorithms will be used for the rest of the connection.

Step 3: Certificate

The server sends its digital certificate (and typically the chain of intermediate certificates up toward a trusted root). This certificate contains the server's public key and is what lets the client verify it's really talking to the server it intended to reach.

The client now performs certificate validation:

  • Checking the certificate hasn't expired
  • Verifying the certificate's digital signature is valid and chains up to a Certificate Authority (CA) the client already trusts
  • Checking that the hostname the client is trying to reach matches the certificate's subject/SAN fields

The full mechanics of how certificates are issued, signed, and validated up a trust chain are covered in 07-08: Digital Certificates & PKI β€” the handshake step here is just "receive the certificate and run that validation process."

πŸ’‘ Certificate verification and hostname verification are two separate checks. A certificate can be perfectly valid β€” correctly signed by a trusted CA, not expired β€” while still belonging to the wrong hostname. Many real-world TLS bugs have come from applications that verified the certificate's signature chain but forgot to separately verify that the hostname matches, which opens the door to man-in-the-middle attacks even when "the certificate checks out." See 09-05: Common TLS Attacks for how this failure is actually exploited.

Step 4: ServerKeyExchange and ServerHelloDone

Depending on the chosen cipher suite, the server may send additional key exchange parameters (for example, Diffie-Hellman parameters if an ephemeral Diffie-Hellman key exchange is being used). ServerHelloDone simply signals that the server has finished this part of the handshake and is waiting on the client.

Step 5: ClientKeyExchange β€” Generating the Keys

This is where the actual shared secret gets established. TLS uses a three-step key derivation process:

  1. Pre-master secret β€” a value whose exact generation depends on the negotiated key exchange algorithm. In older/weaker cipher suites, the client generates this value and encrypts it directly with the server's RSA public key (from the certificate) before sending it. In modern, forward-secret cipher suites, it's instead derived via a Diffie-Hellman-style exchange β€” see 07-05: Diffie-Hellman Key Exchange for exactly how two parties can derive a shared secret this way without ever transmitting it directly.
  2. Master secret β€” both client and server independently compute this from the pre-master secret combined with the client random and server random exchanged earlier in the handshake. Mixing in those random values ensures the master secret is unique to this specific session, even if the same pre-master secret were somehow reused.
  3. Session keys — the master secret is expanded into several separate keys: keys for encrypting client→server data, keys for encrypting server→client data, and separate keys for the integrity (MAC) checks in each direction. Communication is bidirectional, so each direction gets its own dedicated keys rather than sharing one.

Once both sides have independently derived the same session keys, they're ready to switch to fast, efficient symmetric encryption (like AES) for all the actual application data β€” see 07-03: AES & Block Cipher Modes for how that symmetric encryption actually works. Public-key cryptography is only used briefly during the handshake because it's computationally expensive; once a shared secret exists, both sides switch to much cheaper symmetric-key operations for the bulk of the conversation.

Step 6: ChangeCipherSpec and Finished (both directions)

Each side sends a ChangeCipherSpec message, signaling "from this point forward, I'm switching to the negotiated encryption." Immediately after, each side sends a Finished message β€” the very first message actually protected with the new session keys. This serves as a built-in integrity check on the handshake itself: if anything in the negotiation was tampered with along the way, the Finished messages won't validate correctly, and the connection is aborted rather than silently continuing on corrupted parameters.

Step 7: Application Data

Once both Finished messages are exchanged and verified, the handshake is complete, and the client and server begin exchanging actual application data (e.g., HTTP requests/responses), now protected by the negotiated symmetric encryption and integrity checks.


⚑ How TLS 1.3 Compresses This

TLS 1.3 collapses this multi-step negotiation into far fewer round trips:

  • The client, in its very first message, guesses which key exchange method the server will want and includes its own key exchange parameters (e.g., its Diffie-Hellman public value) right there in the ClientHello, instead of waiting for the server to ask.
  • The server responds with its certificate, its own key exchange value, and its Finished message essentially all at once.
  • In the common case, application data can start flowing after just one round trip total, compared to TLS 1.2's two full round trips before any data could be sent.
  • TLS 1.3 also removed the option to derive keys via static RSA encryption entirely β€” every TLS 1.3 handshake uses a Diffie-Hellman-style exchange, guaranteeing forward secrecy on every connection (see 09-01: TLS History & Versions for why that matters).

πŸ’‘ The conceptual steps (agree on parameters, exchange/verify a certificate, derive a shared secret, confirm the handshake wasn't tampered with, switch to symmetric encryption) are all still present in TLS 1.3 β€” they're just packed more efficiently into fewer trips across the network, and several legacy options that used to require extra negotiation rounds were simply deleted from the protocol.


πŸ“Œ Key Takeaways

  • The TLS handshake negotiates cryptographic parameters (cipher suite, keys) before any application data is exchanged, layered on top of an already-established TCP connection.
  • ClientHello/ServerHello agree on the TLS version and cipher suite; both sides also exchange random values used later in key derivation.
  • The server's certificate is used for authentication β€” but certificate validity and hostname matching are two distinct checks, and skipping the hostname check is a common, dangerous mistake.
  • Key generation is a three-step process: pre-master secret β†’ master secret β†’ session keys, with separate keys for each direction of traffic.
  • Modern cipher suites derive the pre-master secret via Diffie-Hellman-style exchange (see 07-05: Diffie-Hellman Key Exchange) rather than encrypting it with the server's public key, which provides forward secrecy.
  • Finished messages, sent under the newly negotiated encryption, act as an integrity check on the entire handshake.
  • After the handshake, both sides switch to symmetric encryption (e.g., AES β€” see 07-03: AES & Block Cipher Modes) for actual data, because it's far cheaper than public-key operations.
  • TLS 1.3 compresses this same conceptual flow into roughly one round trip and removes legacy, non-forward-secret key exchange options entirely.