Skip to content

Handshake in TLS 1.3

This blog writes the takeaway notes of the TLS 1.3 handshake. To understand it, several basic crypto algorithms are required, such as SHA(security hash algorithm), DHE(Diffie-Hellman Exchange), KDF(key derivation function), DSA(digital signature algorithm) and AES. Moreover, understanding the PKI(public key infrastructure) is needed as well.

In this blog, the fundamental math formulas are not the focused parts. Instead, we focus on the whole workflow. The full details are stipulated by the rfc8446.

Overview

Procedures in TLS handshake could be classified into three parts

  • key exchange
  • server parameters
  • authentication

TLS handshake aims to establish the secure communicating channel and encrypt data along with the integrity through the unsecure transportation.

The typical phases in TLS 1.3 are listed below. Note that TLS supports different extensions and different modes(for example, the 0-RTT handshake named PSK), but we focus on the typical phases only.

tls_overall_phases.png

The rfc8446#overview

       Client                                           Server

Key  ^ ClientHello
Exch | + key_share*
     | + signature_algorithms*
     | + psk_key_exchange_modes*
     v + pre_shared_key*       -------->
                                                  ServerHello  ^ Key
                                                 + key_share*  | Exch
                                            + pre_shared_key*  v
                                        {EncryptedExtensions}  ^  Server
                                        {CertificateRequest*}  v  Params
                                               {Certificate*}  ^
                                         {CertificateVerify*}  | Auth
                                                   {Finished}  v
                               <--------  [Application Data*]
     ^ {Certificate*}
Auth | {CertificateVerify*}
     v {Finished}              -------->
       [Application Data]      <------->  [Application Data]

Key Exchange and Deriving

A common misunderstanding is after ClientHello and ServerHello, the final symmetric key is negotiated by the (EC)DHE. However, the truth is the TLS handshake exchanges secret and derives many intermediate secrets on the fly, and finally derives the symmetric key consisted by [sender]_write_key and [sender]_write_iv. The [sender] follows the mark in rfc8446#sender to refer client or server.

There are several keys generated during the whole TLS handshake, they are:

  1. handshake secret
  2. [sender]_handshake_traffic_secret
  3. [sender]_application_traffic_secret
  4. [sender]_write_key and [sender]_write_iv

These secrets are used in different phases during the whole TLS handshake. The most common known symmetric key is indeed consisted by the [sender]_write_key and [sender]_write_iv. Each of them differs between client and server, but they perform as a symmetric key by working together.

You may get confused why we need both [sender]_application_traffic_secret and [sender]_write_key, [sender]_write_iv, and why not use the application traffic secret directly? Indeed, the write key and iv are derived from the application traffic secret, and the application traffic secret could be updated. This reduces the efforts to establish a TLS handshake and ensures the write key and iv change every time.

tls_key_exchange.png

The handshake secret is negotiated though the Diffie–Hellman key exchange algorithm, whose principle is the advisory cannot calculate the handshake key given public keys of each side. This is guaranteed by the assumption that the integer factorization problem or discrete logarithm problem is hard to solve, see my another blog ECC and RSA to learn more the math basis.

Then, the HKDF is used to derive multiple secrets for different usages as shown below, according to diagram of the rfc8446#Key-Schedule.

# handshake_secret is the shared secret after DHE
server_handshake_traffic_secret = HKDF-Expand(
    Handshake Secret,
    "s hs traffic" || Hash(ClientHello),
    Hash.length)
client_handshake_traffic_secret = HKDF-Expand(
    Handshake Secret,
    "c hs traffic" || Hash(ClientHello),
    Hash.length)

master_secret = HKDF-Expand-Label(
    handshake_secret, 
    "derived", 
    "", 
    Hash.length)    

finished_key = HKDF-Expand-Label(
    [sender]_handshake_traffic_secret,
    "finished",
    "",
    Hash.length)

client_application_traffic_secret_0 = HKDF-Expand-Label(
    master_secret,
    "c ap traffic", 
    client_finished_hash, 
    Hash.length)

client_application_traffic_secret_0 = HKDF-Expand-Label(
    master_secret,
    "s ap traffic", 
    server_finished_hash, 
    Hash.length)

[sender]_write_key = HKDF-Expand-Label(
    [sender]_application_traffic_secret_0,
    "key",
    "", 
    key_length)

[sender]_write_key = HKDF-Expand-Label(
    [sender]_application_traffic_secret_0,
    "iv",
    "", 
    iv_length)

Encryption Used of Each Message

In the overall topic, several stages are listed to illustrate the phases. After learning the key exchange and derivation, we start to focus on the encrypting status of each message.

tls_overall_with_encryption.png

The ClientHello definitely is plaintext because server knows neither the client and its secret. But the ServerHello is a bit different, even though it's plaintext as well. If client and server uses the only DHE, the ServerHello could be encrypted by client's public key. However, because of multiple alternative DHE, server needs to send in plaintext as well.

After the key exchange, client and server communicate by encrypted message.

Server Parameters

In server parameters, server must send the EncryptedExtensions and an optional CertificateRequest to ask the client certificate. These messages are encrypted by server_handshake_traffic_secret.

The EncryptedExtensions sends a list of extensions agreed by server. The extensions of TLS are many, but they're not our topic in this blog so we won't continue to talk much.

Authentication

TLS generally uses a common set of messages for authentication, key confirmation, and handshake integrity: Certificate, CertificateVerify, and Finished. In the authentication stage, the core task is to verify the authentication of server, with the help the PKI.

Notes

The public key infrastructure signed the server's long term public key along with more details. The certificate usually has extension .pem which is encoded by based64 of DER, which is the binary format of ASN.1. The signature of the Certificate Authority is contained inside the ASN.1 format file as well.

The computations for the Authentication messages all uniformly take the following inputs:

  • The certificate and signing key to be used.
  • A Handshake Context
  • A Base Key to be used to compute a MAC key.

Hence, the details of authentication messages are listed below. Note that it's encrypted by the server_handshake_traffic_secret.

  • Certificate: the certificate issued by the CA
  • CertificateVerify: A signature(server's private key) over the value transcript-hash(Handshake Context, Certificate).
  • Finished: the verify data HMACed by the finished_key derived from server_handshake_traffic_secret.
      verify_data =
          HMAC(finished_key,
               Transcript-Hash(Handshake Context,
                               Certificate*, CertificateVerify*))
    

Conclusion

This blog introduces the TLS handshake and its messages. We focus on how the key is exchanged and derived to negotiate a final symmetric key to communicate. We don't talk much about the layout of each message and extensions, but I believe this blog helps a lot to understand the TLS1.3 handshake because we focus on the most significant part. That's how the client and server reach a consensus in a wild world full of snoops and malice.