Skip to content

Generating TLS 1.3 Handshake Keys

Overview

After processing the server's hello message to determine the its preferred cipher suite and public key, you will have everything needed to compute symmetric encryption keys that are used to protect the rest of the handshake. These calculations are already written for you in the TLSContext class that was introduced in the first part of this assignment; however, you will need to pass the server's contributions into the context in order to complete the process.

Server Encryption Parameters

In the first step of the TLS 1.3 handshake, our client proposed an elliptic curve Diffie-Hellman key exchange using x25519 parameters. In response, the server provided an x25519 public key within a Key Share extension (type 51) attached to the server hello. This value is used by the client to complete the ECDHE key exchange from its side. Pass the the public key extracted from this extension to the set_server_public() method of TLSContext as shown below:

# After processing server hello and extensions
tls.set_server_public(server_public_key) # (1)
  1. tls is a TLSContext object. server_public_key contains the public key extracted from the Server Hello message. This value is a bytes object with key data only. Be sure to strip the key type and length fields.

In addition to the key exchange parameters, the client's hello message offered a list of cipher suites, i.e., symmetric encryption configurations, that the client is willing to accept. The server is responsible for selecting one of these options and providing the 2-byte assigned identifier back to the client. Use the set_cipher_suite() method to configure the TLSContext with the server's response.

tls.set_cipher_suite(server_cipher_suite) # (1)
  1. server_cipher_suite should be a 2-byte identifier associated with one of the cipher suites offered in the Client Hello message.

Handshake Transcript and Hash

In order to prevent attacks on the unencrypted handshake, TLS clients and servers maintain a transcript of each message handshake message sent and received. At different points during the handshake, clients and servers will compute a hash from this transcript. By incorporating this hash into the computation of encryption keys, TLS prevents outside attackers from making changes to the handshake that would allow them to break encryption. Any attempt to do so will change the key computation for either the server or the client.

Each time you send or receive a handshake message, use the TLSContext's update_transcript() method to update the internal handshake transcript so that this hash can be computed properly in later steps. Pass the raw handshake bytes to the method on each call, but be sure to exclude the 5-byte record header.

# After creating the client_hello message
tls.update_transcript(client_hello[5:]) # (1)
  1. In this example, client_hello is a bytes object containing a 5-byte record header that needs to be sliced away.

Computing Handshake Keys

After completing the previous steps, you will be able to compute the keys used by the client and server to send encrypted handshake messages. The code for this computation is already provided within the TLSContext class. The get_handshake_cipher() method returns a new object that you will use in future steps to encrypt and decrypt data.

handshake_cipher = tls.get_handshake_cipher()

# example decrypt: handshake_cipher.decrypt(record['data'], record['header'])
Back to top