QUIC DTLS TLS 1.3 TLS 1.2

The Illustrated TLS 1.2 Connection

Every byte explained and reproduced

In this demonstration a client connects to a server, negotiates a TLS 1.2 session, sends "ping", receives "pong", and then terminates the session. Click below to begin exploring.

Client Hello
The session begins with the client saying "Hello". The client provides the following:
  • protocol version
  • client random data (used later in the handshake)
  • an optional session id to resume
  • a list of cipher suites
  • a list of compression methods
  • a list of extensions
Record Header 16 03 01 00 a5
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 16 - type is 0x16 (handshake record)
  • 03 01 - protocol version is 3.1 (also known as TLS 1.0)
  • 00 a5 - 0xA5 (165) bytes of handshake message follows
Interestingly the version is 3.1 (TLS 1.0) instead of the expected "3,3" (TLS 1.2). Looking through the golang crypto/tls library we find the following comment:
if vers == 0 {
    // Some TLS servers fail if the record version is
    // greater than TLS 1.0 for the initial ClientHello.
    vers = VersionTLS10
}
Handshake Header 01 00 00 a1
Each handshake message starts with a type and a length.
  • 01 - handshake message type 0x01 (client hello)
  • 00 00 a1 - 0xA1 (161) bytes of client hello follows
Client Version 03 03
The protocol version of "3,3" (meaning TLS 1.2) is given.

The unusual version number ("3,3" representing TLS 1.2) is due to TLS 1.0 being a minor revision of the SSL 3.0 protocol. Therefore TLS 1.0 is represented by "3,1", TLS 1.1 is "3,2", and so on.
Client Random 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
The client provides 32 bytes of random data. In this example we've made the random data a predictable string.

The TLS 1.2 spec says that the first 4 bytes should be the current time in seconds-since-1970 but this is now recommended against as it enables fingerprinting of hosts and servers.
Session ID 00
The client can provide the ID of a previous TLS session against this server which it is able to resume. For this to work both the server and client will have remembered key information from the previous connection in memory. Resuming a connection saves a lot of computation and network round-trip time so it is performed whenever possible.
  • 00 - length of zero (no session id is provided)
Cipher Suites 00 20 cc a8 cc a9 c0 2f c0 30 c0 2b c0 2c c0 13 c0 09 c0 14 c0 0a 00 9c 00 9d 00 2f 00 35 c0 12 00 0a
The client provides an ordered list of which cryptographic methods it will support for key exchange, encryption with that exchanged key, and message authentication. The list is in the order preferred by the client, with highest preference first.
  • 00 20 - 0x20 (32) bytes of cipher suite data
  • cc a8 - assigned value for TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
  • cc a9 - assigned value for TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
  • c0 2f - assigned value for TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • c0 30 - assigned value for TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  • c0 2b - assigned value for TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  • c0 2c - assigned value for TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
  • c0 13 - assigned value for TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
  • c0 09 - assigned value for TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
  • c0 14 - assigned value for TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
  • c0 0a - assigned value for TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
  • 00 9c - assigned value for TLS_RSA_WITH_AES_128_GCM_SHA256
  • 00 9d - assigned value for TLS_RSA_WITH_AES_256_GCM_SHA384
  • 00 2f - assigned value for TLS_RSA_WITH_AES_128_CBC_SHA
  • 00 35 - assigned value for TLS_RSA_WITH_AES_256_CBC_SHA
  • c0 12 - assigned value for TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
  • 00 0a - assigned value for TLS_RSA_WITH_3DES_EDE_CBC_SHA
Compression Methods 01 00
The client provides an ordered list of which compression methods it will support. This compression would be applied before encryption (as encrypted data is usually incompressible).
  • 01 - 0x1 (1) bytes of compression methods follows
  • 00 - assigned value for no compression
Compression has characteristics that can weaken the security of the encrypted data (see CRIME). so this feature has been removed from future TLS protocols.
Extensions Length 00 58
The client has provided a list of optional extensions which the server can use to take action or enable new features.
  • 00 58 - the extensions will take 0x58 (88) bytes of data
Each extension will start with two bytes that indicate which extension it is, followed by a two-byte content length field, followed by the contents of the extension.
Extension - Server Name 00 00 00 18 00 16 00 00 13 65 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74
The client has provided the name of the server it is contacting, also known as SNI (Server Name Indication).

Without this extension a HTTPS server would not be able to provide service for multiple hostnames on a single IP address (virtual hosts) because it couldn't know which hostname's certificate to send until after the TLS session was negotiated and the HTTP request was made.
  • 00 00 - assigned value for extension "server name"
  • 00 18 - 0x18 (24) bytes of "server name" extension data follows
  • 00 16 - 0x16 (22) bytes of first (and only) list entry follows
  • 00 - list entry is type 0x00 "DNS hostname"
  • 00 13 - 0x13 (19) bytes of hostname follows
  • 65 78 61 ... 6e 65 74 - "example.ulfheim.net"
Extension - Status Request 00 05 00 05 01 00 00 00 00
The client provides permission for the server to provide OCSP information in its response. OCSP can be used to check whether a certificate has been revoked.

This form of the client sending an empty extension is necessary because it is a fatal error for the server to reply with an extension that the client did not provide first. Therefore the client sends an empty form of the extension, and the server replies with the extension populated with data.
  • 00 05 - assigned value for extension "status request"
  • 00 05 - 0x5 (5) bytes of "status request" extension data follows
  • 01 - assigned value for "certificate status type: OCSP"
  • 00 00 - 0x0 (0) bytes of responderID information
  • 00 00 - 0x0 (0) bytes of request extension information
Extension - Supported Groups 00 0a 00 0a 00 08 00 1d 00 17 00 18 00 19
The client has indicated that it supports elliptic curve (EC) cryptography for 4 curves. This extension was originally named "elliptic curves" but has been renamed "supported groups" to be generic to other cryptography types.
  • 00 0a - assigned value for extension "supported groups"
  • 00 0a - 0xA (10) bytes of "supported groups" extension data follows
  • 00 08 - 0x8 (8) bytes of data are in the curves list
  • 00 1d - assigned value for the curve "x25519"
  • 00 17 - assigned value for the curve "secp256r1"
  • 00 18 - assigned value for the curve "secp384r1"
  • 00 19 - assigned value for the curve "secp521r1"
Extension - EC Point Formats 00 0b 00 02 01 00
During elliptic curve (EC) cryptography the client and server will exchange information on the points selected, in either compressed or uncompressed form. This extension indicates that the client can only parse uncompressed information from the server.

In the next version of TLS the ability to negotiate points does not exist (instead a single point is pre-selected for each curve), so this extension would not be sent.
  • 00 0b - assigned value for extension "EC points format"
  • 00 02 - 0x2 (2) bytes of "EC points format" extension data follows
  • 01 - 0x1 (1) bytes of data are in the supported formats list
  • 00 - assigned value for uncompressed form
Extension - Signature Algorithms 00 0d 00 12 00 10 04 01 04 03 05 01 05 03 06 01 06 03 02 01 02 03
As TLS has developed it has become necessary to support stronger signature algorithms such as SHA-256 while still supporting earlier implementations that used MD5 and SHA1. This extension indicates which signature algorithms the client is capable of understanding and may influence the choice of certificate that the server sends to the client.
  • 00 0d - assigned value for extension "Signature Algorithms"
  • 00 12 - 0x12 (18) bytes of "Signature Algorithms" extension data follows
  • 00 10 - 0x10 (16) bytes of data are in the following list of algorithms
  • 04 01 - assigned value for RSA/PKCS1/SHA256
  • 04 03 - assigned value for ECDSA/SECP256r1/SHA256
  • 05 01 - assigned value for RSA/PKCS1/SHA384
  • 05 03 - assigned value for ECDSA/SECP384r1/SHA384
  • 06 01 - assigned value for RSA/PKCS1/SHA512
  • 06 03 - assigned value for ECDSA/SECP521r1/SHA512
  • 02 01 - assigned value for RSA/PKCS1/SHA1
  • 02 03 - assigned value for ECDSA/SHA1
Extension - Renegotiation Info ff 01 00 01 00
The presence of this extension prevents a type of attack performed with TLS renegotiation.

The ability to renegotiate a connection has been removed from the next version of this protocol (TLS 1.3) so this extension will no longer be necessary in the future.
  • ff 01 - assigned value for extension "Renegotiation Info"
  • 00 01 - 0x1 (1) bytes of "Renegotiation Info" extension data follows
  • 00 - length of renegotiation data is zero, because this is a new connection
Extension - SCT 00 12 00 00
The client provides permission for the server to return a signed certificate timestamp.

This form of the client sending an empty extension is necessary because it is a fatal error for the server to reply with an extension that the client did not provide first. Therefore the client sends an empty form of the extension, and the server replies with the extension populated with data, or changes behavior based on the client having sent the extension.
  • 00 12 - assigned value for extension "signed certificate timestamp"
  • 00 00 - 0x0 (0) bytes of "signed certificate timestamp" extension data follows
Server Hello
The server says "Hello" back. The server provides the following:
  • the selected protocol version
  • server random data (used later in the handshake)
  • the session id
  • the selected cipher suite
  • the selected compression method
  • a list of extensions
Record Header 16 03 03 00 31
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 16 - type is 0x16 (handshake record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 31 - 0x31 (49) bytes of handshake message follows
Handshake Header 02 00 00 2d
Each handshake message starts with a type and a length.
  • 02 - handshake message type 0x02 (server hello)
  • 00 00 2d - 0x2D (45) bytes of server hello data follows
Server Version 03 03
The protocol version of "3,3" (TLS 1.2) is given.

The unusual version number ("3,3" representing TLS 1.2) is due to TLS 1.0 being a minor revision of the SSL 3.0 protocol. Therefore TLS 1.0 is represented by "3,1", TLS 1.1 is "3,2", and so on.
Server Random 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f
The server provides 32 bytes of random data. In this example we've made the random data a predictable string.

The TLS 1.2 spec says that the first 4 bytes should be the current time in seconds-since-1970 but this is now recommended against as it enables fingerprinting of hosts and servers.
Session ID 00
The server can provide an ID for this session which a client can provide on a later session negotiation in an attempt to re-use the key data and skip most of the TLS negotiation process. For this to work both the server and client will store key information from the previous connection in memory. Resuming a connection saves a lot of computation and network round-trip time so it is performed whenever possible.
  • 00 - length of zero (no session id is used)
Cipher Suite c0 13
The server has selected cipher suite 0xC013 (TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) from the list of options given by the client.
Compression Method 00
The server has selected compression method 0x00 ("Null", which performs no compression) from the list of options given by the client.
Extensions Length 00 05
The server has returned a list of extensions to the client. Because the server is forbidden from replying with an extension that the client did not send in its hello message, the server knows that the client will support all extensions listed.
  • 00 05 - the extensions will take 0x5 (5) bytes of data
Extension - Renegotiation Info ff 01 00 01 00
The presence of this extension prevents a type of attack performed with TLS renegotiation.

The ability to renegotiate a connection has been removed from the next version of this protocol (TLS 1.3) so this extension will no longer be necessary in the future.
  • ff 01 - assigned value for extension "Renegotiation Info"
  • 00 01 - 0x1 (1) bytes of "Renegotiation Info" extension data follows
  • 00 - length of renegotiation data is zero, because this is a new connection
Server Certificate
The server provides a certificate containing the following:
  • the hostname of the server
  • the public key used by this server
  • proof from a trusted third party that the owner of this hostname holds the private key for this public key
Explore the server certificate.
Record Header 16 03 03 03 2f
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 16 - type is 0x16 (handshake record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 03 2f - 0x32F (815) bytes of handshake message follows
Handshake Header 0b 00 03 2b
Each handshake message starts with a type and a length.
  • 0b - handshake message type 0x0B (certificate)
  • 00 03 2b - 0x32B (811) of certificate message follows
Certificates Length 00 03 28
The certificate message begins with the length of all certificate data that will follow.
  • 00 03 28 - 0x328 (808) bytes of certificate list follows
Certificate Length 00 03 25
The length of the first (and only) certificate.
  • 00 03 25 - 0x325 (805) bytes of certificate follows
Certificate 30 82 03 21 30 82 02 09 a0 03 02 01 02 02 08 15 5a 92 ad c2 04 8f 90 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 00 30 22 31 0b 30 09 06 03 55 04 06 13 02 55 53 31 13 30 11 06 03 55 04 0a 13 0a 45 78 61 6d 70 6c 65 20 43 41 30 1e 17 0d 31 38 31 30 30 35 30 31 33 38 31 37 5a 17 0d 31 39 31 30 30 35 30 31 33 38 31 37 5a 30 2b 31 0b 30 09 06 03 55 04 06 13 02 55 53 31 1c 30 1a 06 03 55 04 03 13 13 65 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01 00 c4 80 36 06 ba e7 47 6b 08 94 04 ec a7 b6 91 04 3f f7 92 bc 19 ee fb 7d 74 d7 a8 0d 00 1e 7b 4b 3a 4a e6 0f e8 c0 71 fc 73 e7 02 4c 0d bc f4 bd d1 1d 39 6b ba 70 46 4a 13 e9 4a f8 3d f3 e1 09 59 54 7b c9 55 fb 41 2d a3 76 52 11 e1 f3 dc 77 6c aa 53 37 6e ca 3a ec be c3 aa b7 3b 31 d5 6c b6 52 9c 80 98 bc c9 e0 28 18 e2 0b f7 f8 a0 3a fd 17 04 50 9e ce 79 bd 9f 39 f1 ea 69 ec 47 97 2e 83 0f b5 ca 95 de 95 a1 e6 04 22 d5 ee be 52 79 54 a1 e7 bf 8a 86 f6 46 6d 0d 9f 16 95 1a 4c f7 a0 46 92 59 5c 13 52 f2 54 9e 5a fb 4e bf d7 7a 37 95 01 44 e4 c0 26 87 4c 65 3e 40 7d 7d 23 07 44 01 f4 84 ff d0 8f 7a 1f a0 52 10 d1 f4 f0 d5 ce 79 70 29 32 e2 ca be 70 1f df ad 6b 4b b7 11 01 f4 4b ad 66 6a 11 13 0f e2 ee 82 9e 4d 02 9d c9 1c dd 67 16 db b9 06 18 86 ed c1 ba 94 21 02 03 01 00 01 a3 52 30 50 30 0e 06 03 55 1d 0f 01 01 ff 04 04 03 02 05 a0 30 1d 06 03 55 1d 25 04 16 30 14 06 08 2b 06 01 05 05 07 03 02 06 08 2b 06 01 05 05 07 03 01 30 1f 06 03 55 1d 23 04 18 30 16 80 14 89 4f de 5b cc 69 e2 52 cf 3e a3 00 df b1 97 b8 1d e1 c1 46 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 00 03 82 01 01 00 59 16 45 a6 9a 2e 37 79 e4 f6 dd 27 1a ba 1c 0b fd 6c d7 55 99 b5 e7 c3 6e 53 3e ff 36 59 08 43 24 c9 e7 a5 04 07 9d 39 e0 d4 29 87 ff e3 eb dd 09 c1 cf 1d 91 44 55 87 0b 57 1d d1 9b df 1d 24 f8 bb 9a 11 fe 80 fd 59 2b a0 39 8c de 11 e2 65 1e 61 8c e5 98 fa 96 e5 37 2e ef 3d 24 8a fd e1 74 63 eb bf ab b8 e4 d1 ab 50 2a 54 ec 00 64 e9 2f 78 19 66 0d 3f 27 cf 20 9e 66 7f ce 5a e2 e4 ac 99 c7 c9 38 18 f8 b2 51 07 22 df ed 97 f3 2e 3e 93 49 d4 c6 6c 9e a6 39 6d 74 44 62 a0 6b 42 c6 d5 ba 68 8e ac 3a 01 7b dd fc 8e 2c fc ad 27 cb 69 d3 cc dc a2 80 41 44 65 d3 ae 34 8c e0 f3 4a b2 fb 9c 61 83 71 31 2b 19 10 41 64 1c 23 7f 11 a5 d6 5c 84 4f 04 04 84 99 38 71 2b 95 9e d6 85 bc 5c 5d d6 45 ed 19 90 94 73 40 29 26 dc b4 0e 34 69 a1 59 41 e8 e2 cc a8 4b b6 08 46 36 a0
The certificate is in ASN.1 DER encoding. The details of this format and the content of this binary payload are documented on another page. The certificate can be converted to the binary data in this message at the command line:
$ openssl x509 -outform der < server.crt | hexdump

0000000 30 82 03 21 30 82 02 09 a0 03 02 01 02 02 08 15
0000010 5a 92 ad c2 04 8f 90 30 0d 06 09 2a 86 48 86 f7
... snip ...
Server Key Exchange Generation

The server calculates a private/public keypair for key exchange. Key exchange is a technique where two parties can agree on the same number without an eavesdropper being able to tell what it is.

An explanation of the key exchange can be found on my X25519 site, but doesn't need to be understood in depth for the rest of this page.

The private key is chosen by selecting an integer between 0 and 2256-1. The server does this by generating 32 bytes (256 bits) of random data. The private key selected is:

909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
The public key is created from the private key as explained on the X25519 site. The public key calculated is:
9fd7ad6dcff4298dd3f96d5b1b2af910a0535b1488d7f8fabb349a982880b615
The public key calculation can be confirmed at the command line:
### requires openssl 1.1.0 or higher
$ openssl pkey -noout -text < server-ephemeral-private.key

X25519 Private-Key:
priv:
    90:91:92:93:94:95:96:97:98:99:9a:9b:9c:9d:9e:
    9f:a0:a1:a2:a3:a4:a5:a6:a7:a8:a9:aa:ab:ac:ad:
    ae:af
pub:
    9f:d7:ad:6d:cf:f4:29:8d:d3:f9:6d:5b:1b:2a:f9:
    10:a0:53:5b:14:88:d7:f8:fa:bb:34:9a:98:28:80:
    b6:15
Server Key Exchange
The server provides information for key exchange. As part of the key exchange process both the server and the client will have a keypair of public and private keys, and will send the other party their public key. The shared encryption key will then be generated using a combination of each party's private key and the other party's public key.

The parties have agreed on a cipher suite using ECDHE, meaning the keypairs will be based on a selected Elliptic Curve, Diffie-Hellman will be used, and the keypairs are Ephemeral (generated for each connection) rather than using the public/private key from the certificate.
Record Header 16 03 03 01 2c
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 16 - type is 0x16 (handshake record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 01 2c - 0x12C (300) bytes of handshake message follows
Handshake Header 0c 00 01 28
Each handshake message starts with a type and a length.
  • 0c - handshake message type 0x0c (server key exchange)
  • 00 01 28 - 0x128 (296) bytes of server key exchange follows
Curve Info 03 00 1d
The server chooses the elliptic curve that points will be calculated from.
  • 03 - assigned value for "named_curve": the following bytes will identify a specific curve
  • 00 1d - curve 0x001d ("curve x25519")
Public Key 20 9f d7 ad 6d cf f4 29 8d d3 f9 6d 5b 1b 2a f9 10 a0 53 5b 14 88 d7 f8 fa bb 34 9a 98 28 80 b6 15
The server provides its public key from the step "Server Key Exchange Generation".
  • 20 - length of 0x20 (32) bytes
  • 9f d7 ... b6 15 - public key
Signature 04 01 01 00 04 02 b6 61 f7 c1 91 ee 59 be 45 37 66 39 bd c3 d4 bb 81 e1 15 ca 73 c8 34 8b 52 5b 0d 23 38 aa 14 46 67 ed 94 31 02 14 12 cd 9b 84 4c ba 29 93 4a aa cc e8 73 41 4e c1 1c b0 2e 27 2d 0a d8 1f 76 7d 33 07 67 21 f1 3b f3 60 20 cf 0b 1f d0 ec b0 78 de 11 28 be ba 09 49 eb ec e1 a1 f9 6e 20 9d c3 6e 4f ff d3 6b 67 3a 7d dc 15 97 ad 44 08 e4 85 c4 ad b2 c8 73 84 12 49 37 25 23 80 9e 43 12 d0 c7 b3 52 2e f9 83 ca c1 e0 39 35 ff 13 a8 e9 6b a6 81 a6 2e 40 d3 e7 0a 7f f3 58 66 d3 d9 99 3f 9e 26 a6 34 c8 1b 4e 71 38 0f cd d6 f4 e8 35 f7 5a 64 09 c7 dc 2c 07 41 0e 6f 87 85 8c 7b 94 c0 1c 2e 32 f2 91 76 9e ac ca 71 64 3b 8b 98 a9 63 df 0a 32 9b ea 4e d6 39 7e 8c d0 1a 11 0a b3 61 ac 5b ad 1c cd 84 0a 6c 8a 6e aa 00 1a 9d 7d 87 dc 33 18 64 35 71 22 6c 4d d2 c2 ac 41 fb
Because the server and client have agreed to perform key exchange with ephemeral keys, they are not using the public and private keys associated with the server certificate. To prove that the server owns the certificate (giving the certificate validity in this TLS session), it signs the ephemeral public key with the private key associated with the server's certificate. This signature can be validated with the public key included in the server's certificate.
  • 04 01 - reserved value for RSA signature with SHA256 hash
  • 01 00 - length of signature (0x100 or 256 bytes)
  • 04 02 b6 ... ac 41 fb - the computed signature for SHA256(client_hello_random + server_hello_random + curve_info + public_key)
We can compute the signature ourselves using the server's private key, at the command line:
### client random from Client Hello
$ echo -en '\x00\x01\x02\x03\x04\x05\x06\x07'  > /tmp/compute
$ echo -en '\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' >> /tmp/compute
$ echo -en '\x10\x11\x12\x13\x14\x15\x16\x17' >> /tmp/compute
$ echo -en '\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f' >> /tmp/compute
### server random from Server Hello
$ echo -en '\x70\x71\x72\x73\x74\x75\x76\x77' >> /tmp/compute
$ echo -en '\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f' >> /tmp/compute
$ echo -en '\x80\x81\x82\x83\x84\x85\x86\x87' >> /tmp/compute
$ echo -en '\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f' >> /tmp/compute
### the curve info section from this message
$ echo -en '\x03\x00\x1d' >> /tmp/compute
### the public key sections from this msg
$ echo -en '\x20\x9f\xd7\xad\x6d\xcf\xf4\x29' >> /tmp/compute
$ echo -en '\x8d\xd3\xf9\x6d\x5b\x1b\x2a\xf9' >> /tmp/compute
$ echo -en '\x10\xa0\x53\x5b\x14\x88\xd7\xf8' >> /tmp/compute
$ echo -en '\xfa\xbb\x34\x9a\x98\x28\x80\xb6\x15' >> /tmp/compute
$ openssl dgst -sign server.key -sha256 /tmp/compute | hexdump

0000000 04 02 b6 61 f7 c1 91 ee 59 be 45 37 66 39 bd c3
... snip ...
00000f0 7d 87 dc 33 18 64 35 71 22 6c 4d d2 c2 ac 41 fb
Server Hello Done
The server indicates it's finished with its half of the handshake.
Record Header 16 03 03 00 04
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 16 - type is 0x16 (handshake record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 04 - 0x4 (4) bytes of handshake message follows
Handshake Header 0e 00 00 00
Each handshake message starts with a type and a length.
  • 0e - handshake message type 0x0e (server hello done)
  • 00 00 00 - 0x0 (0) bytes of hello done follows
Client Key Exchange Generation

The client calculates a private/public keypair for key exchange. Key exchange is a technique where two parties can agree on the same number without an eavesdropper being able to tell what the number is.

An explanation of the key exchange can be found on my X25519 site, but doesn't need to be understood in depth for the rest of this page.

The private key is chosen by selecting an integer between 0 and 2256-1. The client does this by generating 32 bytes (256 bits) of random data. The private key selected is:

202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
The public key is created from the private key as explained on the X25519 site. The public key calculated is:
358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254
The public key calculation can be confirmed at the command line:
### requires openssl 1.1.0 or higher
$ openssl pkey -noout -text < client-ephemeral-private.key

X25519 Private-Key:
priv:
    20:21:22:23:24:25:26:27:28:29:2a:2b:2c:2d:2e:
    2f:30:31:32:33:34:35:36:37:38:39:3a:3b:3c:3d:
    3e:3f
pub:
    35:80:72:d6:36:58:80:d1:ae:ea:32:9a:df:91:21:
    38:38:51:ed:21:a2:8e:3b:75:e9:65:d0:d2:cd:16:
    62:54
Client Key Exchange
The client provides information for key exchange. As part of the key exchange process both the server and the client will have a keypair of public and private keys, and will send the other party their public key. The shared encryption key will then be generated using a combination of each party's private key and the other party's public key.

The parties have agreed on a cipher suite using ECDHE, meaning the keypairs will be based on a selected Elliptic Curve, Diffie-Hellman will be used, and the keypairs are Ephemeral (generated for each connection) rather than using the public/private key from the certificate.
Record Header 16 03 03 00 25
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 16 - type is 0x16 (handshake record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 25 - 0x25 (37) bytes of handshake message follows
Handshake Header 10 00 00 21
Each handshake message starts with a type and a length.
  • 10 - handshake message type 0x10 (client key exchange)
  • 00 00 21 - 0x21 (33) bytes of client key exchange follows
Public Key 20 35 80 72 d6 36 58 80 d1 ae ea 32 9a df 91 21 38 38 51 ed 21 a2 8e 3b 75 e9 65 d0 d2 cd 16 62 54
The client provides its public key from the step "Client Key Exchange Generation".
  • 20 - length of 0x20 (32) bytes
  • 35 80 ... 62 54 - public key
Client Encryption Keys Calculation
The client now has the information to calculate the encryption keys that will be used by each side. It uses the following information in this calculation:
  • server random (from Server Hello)
  • client random (from Client Hello)
  • server public key (from Server Key Exchange)
  • client private key (from Client Key Generation)
The client multiplies the server's public key by the client's private key using the curve25519() algorithm. The 32-byte result is called the PreMasterSecret, and is found to be:
df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
I've provided a tool to perform this calculation:
$ gcc -o curve25519-mult curve25519-mult.c
$ ./curve25519-mult client-ephemeral-private.key \
                    server-ephemeral-public.key | hexdump

0000000 df 4a 29 1b aa 1e b7 cf a6 93 4b 29 b4 74 ba ad
0000010 26 97 e2 9f 1f 92 0d cc 77 c8 a0 a0 88 44 76 24
The client then calculates 48 bytes of the MasterSecret from the PreMasterSecret using the following method:
seed = "master secret" + client_random + server_random
a0 = seed
a1 = HMAC-SHA256(key=PreMasterSecret, data=a0)
a2 = HMAC-SHA256(key=PreMasterSecret, data=a1)
p1 = HMAC-SHA256(key=PreMasterSecret, data=a1 + seed)
p2 = HMAC-SHA256(key=PreMasterSecret, data=a2 + seed)
MasterSecret = p1[all 32 bytes] + p2[first 16 bytes]
Here we demonstrate on the command line:
### set up our PreMasterSecret as a hex string
$ pmshex=df4a291baa1eb7cfa6934b29b474baad
$ pmshex=${pmshex}2697e29f1f920dcc77c8a0a088447624
### client random from Client Hello
$ echo -en '\x00\x01\x02\x03\x04\x05\x06\x07' >  /tmp/c_rand
$ echo -en '\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' >> /tmp/c_rand
$ echo -en '\x10\x11\x12\x13\x14\x15\x16\x17' >> /tmp/c_rand
$ echo -en '\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f' >> /tmp/c_rand
### server random from Server Hello
$ echo -en '\x70\x71\x72\x73\x74\x75\x76\x77' >  /tmp/s_rand
$ echo -en '\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f' >> /tmp/s_rand
$ echo -en '\x80\x81\x82\x83\x84\x85\x86\x87' >> /tmp/s_rand
$ echo -en '\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f' >> /tmp/s_rand
### build the seed
$ echo -en 'master secret' > /tmp/seed
$ cat /tmp/c_rand /tmp/s_rand >> /tmp/seed
### a0 is the same as the seed
$ cat /tmp/seed > /tmp/a0
### a(n) is hmac-sha256(key=secret, data=a(n-1))
$ cat /tmp/a0 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$pmshex -binary > /tmp/a1
$ cat /tmp/a1 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$pmshex -binary > /tmp/a2
### p(n) is hmac-sha256(key=secret, data=a(n)+seed)
$ cat /tmp/a1 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$pmshex -binary > /tmp/p1
$ cat /tmp/a2 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$pmshex -binary > /tmp/p2
### first 48 bytes is MasterSecret
$ cat /tmp/p1 /tmp/p2 | head -c 48 > /tmp/mastersecret
$ hexdump /tmp/mastersecret

0000000 91 6a bf 9d a5 59 73 e1 36 14 ae 0a 3f 5d 3f 37
0000010 b0 23 ba 12 9a ee 02 cc 91 34 33 81 27 cd 70 49
0000020 78 1c 8e 19 fc 1e b2 a7 38 7a c0 6a e2 37 34 4c
This gives us a MasterSecret of:
916abf9da55973e13614ae0a3f5d3f37b023ba129aee02cc9134338127cd7049781c8e19fc1eb2a7387ac06ae237344c
We then generate the final encryption keys using a key expansion:
seed = "key expansion" + server_random + client_random
a0 = seed
a1 = HMAC-SHA256(key=MasterSecret, data=a0)
a2 = HMAC-SHA256(key=MasterSecret, data=a1)
a3 = HMAC-SHA256(key=MasterSecret, data=a2)
a4 = ...
p1 = HMAC-SHA256(key=MasterSecret, data=a1 + seed)
p2 = HMAC-SHA256(key=MasterSecret, data=a2 + seed)
p3 = HMAC-SHA256(key=MasterSecret, data=a3 + seed)
p4 = ...
p = p1 + p2 + p3 + p4 ...
client write mac key = [first 20 bytes of p]
server write mac key = [next 20 bytes of p]
client write key = [next 16 bytes of p]
server write key = [next 16 bytes of p]
client write IV = [next 16 bytes of p]
server write IV = [next 16 bytes of p]
We can demonstrate this on the command line:
### continued from above command line example
### set up our MasterSecret as a hex string
$ mshex=$(hexdump -ve '/1 "%02x"' /tmp/mastersecret)
### build the seed
$ echo -en 'key expansion' > /tmp/seed
$ cat /tmp/s_rand /tmp/c_rand >> /tmp/seed
### a0 is the same as the seed
$ cat /tmp/seed > /tmp/a0
### a(n) is hmac-sha256(key=secret, data=a(n-1))
$ cat /tmp/a0 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a1
$ cat /tmp/a1 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a2
$ cat /tmp/a2 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a3
$ cat /tmp/a3 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a4
### p(n) is hmac-sha256(key=secret, data=a(n)+seed)
$ cat /tmp/a1 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p1
$ cat /tmp/a2 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p2
$ cat /tmp/a3 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p3
$ cat /tmp/a4 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p4
### combine them into a single stream
$ cat /tmp/p1 /tmp/p2 /tmp/p3 /tmp/p4 > /tmp/p
$ dd if=/tmp/p of=/tmp/client_mac_key bs=1 skip=0  count=20
$ dd if=/tmp/p of=/tmp/server_mac_key bs=1 skip=20 count=20
$ dd if=/tmp/p of=/tmp/client_key     bs=1 skip=40 count=16
$ dd if=/tmp/p of=/tmp/server_key     bs=1 skip=56 count=16
$ dd if=/tmp/p of=/tmp/client_iv      bs=1 skip=72 count=16
$ dd if=/tmp/p of=/tmp/server_iv      bs=1 skip=88 count=16
$ hexdump /tmp/client_mac_key
0000000 1b 7d 11 7c 7d 5f 69 0b c2 63 ca e8 ef 60 af 0f
0000010 18 78 ac c2

$ hexdump /tmp/server_mac_key
0000000 2a d8 bd d8 c6 01 a6 17 12 6f 63 54 0e b2 09 06
0000010 f7 81 fa d2

$ hexdump /tmp/client_key
0000000 f6 56 d0 37 b1 73 ef 3e 11 16 9f 27 23 1a 84 b6

$ hexdump /tmp/server_key
0000000 75 2a 18 e7 a9 fc b7 cb cd d8 f9 8d d8 f7 69 eb

$ hexdump /tmp/client_iv
0000000 a0 d2 55 0c 92 38 ee bf ef 5c 32 25 1a bb 67 d6

$ hexdump /tmp/server_iv
0000000 43 45 28 db 49 37 d5 40 d3 93 13 5e 06 a1 1b b8
From this we get the following key data:
  • client MAC key: 1b7d117c7d5f690bc263cae8ef60af0f1878acc2
  • server MAC key: 2ad8bdd8c601a617126f63540eb20906f781fad2
  • client write key: f656d037b173ef3e11169f27231a84b6
  • server write key: 752a18e7a9fcb7cbcdd8f98dd8f769eb
  • client write IV: a0d2550c9238eebfef5c32251abb67d6
  • server write IV: 434528db4937d540d393135e06a11bb8
Client Change Cipher Spec
The client indicates that it has calculated the shared encryption keys and that all following messages from the client will be encrypted with the client write key.

In the next version of TLS this message type has been removed because it can be inferred.
Record 14 03 03 00 01 01
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 14 - type is 0x14 (ChangeCipherSpec record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 01 - 0x1 (1) bytes of change cipher spec follows
  • 01 - the payload of this message is defined as the byte 0x01
Client Handshake Finished
To verify that the handshake was successful and not tampered with, the client calculates verification data and encrypts it with the client write key.

The verification data is built from a hash of all handshake messages and verifies the integrity of the handshake process.
Record Header 16 03 03 00 40
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 16 - type is 0x16 (handshake record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 40 - 0x40 (64) bytes of handshake message follows
Encryption IV 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
The client has sent an initialization vector for decrypting this block. Because we have overridden the rand function it is a predictable sequence.
Encrypted Data 22 7b c9 ba 81 ef 30 f2 a8 a7 8f f1 df 50 84 4d 58 04 b7 ee b2 e2 14 c3 2b 68 92 ac a3 db 7b 78 07 7f dd 90 06 7c 51 6b ac b3 ba 90 de df 72 0f
This data is encrypted with the client write key. Because it contains a message authentication code (MAC) and padding it is larger than the decrypted data.

See below for the decrypted data.
Decryption
This data can be decrypted using the encryption IV and the client write key that was generated in the step "Client Encryption Keys Calculation".
### client key
$ hexkey=f656d037b173ef3e11169f27231a84b6
### IV for this record
$ hexiv=404142434445464748494a4b4c4d4e4f
### encrypted data
$ echo '22 7b c9 ba 81 ef 30 f2 a8 a7 8f f1 df 50 84 4d'  > /tmp/msg1
$ echo '58 04 b7 ee b2 e2 14 c3 2b 68 92 ac a3 db 7b 78' >> /tmp/msg1
$ echo '07 7f dd 90 06 7c 51 6b ac b3 ba 90 de df 72 0f' >> /tmp/msg1
$ xxd -r -p /tmp/msg1 \
  | openssl enc -d -nopad -aes-128-cbc -K $hexkey -iv $hexiv | hexdump

0000000 14 00 00 0c cf 91 96 26 f1 36 0c 53 6a aa d7 3a
0000010 a5 a0 3d 23 30 56 e4 ac 6e ba 7f d9 e5 31 7f ac
0000020 2d b5 b7 0e 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b

The last 32 bytes contain a 20-byte MAC and padding to bring the data to a
multiple of 16 bytes.  The 20-byte MAC can be reproduced as follows:

### from https://tools.ietf.org/html/rfc2246#section-6.2.3.1
$ sequence='0000000000000000'
$ rechdr='16 03 03'
$ datalen='00 10'
$ data='14 00 00 0c cf 91 96 26 f1 36 0c 53 6a aa d7 3a'
### from "Encryption Keys Calculation"
$ mackey=1b7d117c7d5f690bc263cae8ef60af0f1878acc2
$ echo $sequence $rechdr $datalen $data | xxd -r -p \
  | openssl dgst -sha1 -mac HMAC -macopt hexkey:$mackey

a5a03d233056e4ac6eba7fd9e5317fac2db5b70e
Handshake Header 14 00 00 0c
Each handshake message starts with a type and a length.
  • 14 - handshake message type 0x14 (finished)
  • 00 00 0c - 0xC (12) bytes of handshake finished follows
Verify Data cf 91 96 26 f1 36 0c 53 6a aa d7 3a
The verify_data is built from the master secret and the hash of the payload of all handshake records (type=0x16) previous to this one.

The SHA256 of all handshake messages before this one is 061dda04b3c2217ff73bd79b9cf88a2bb6ec505404aac8722db03ef417b54cb4. It can be reproduced as follows:
### combine the payload (without the 5-byte header) of each handshake message, in order
$ tail -c +6 clienthello > test.bin
$ tail -c +6 serverhello >> test.bin
$ tail -c +6 servercert >> test.bin
$ tail -c +6 serverkeyexchange >> test.bin
$ tail -c +6 serverhellodone >> test.bin
$ tail -c +6 clientkeyexchange >> test.bin
$ openssl sha256 test.bin

061dda04b3c2217ff73bd79b9cf88a2bb6ec505404aac8722db03ef417b54cb4
The calculation for verify_data is as follows:
seed = "client finished" + SHA256(all handshake messages)
a0 = seed
a1 = HMAC-SHA256(key=MasterSecret, data=a0)
p1 = HMAC-SHA256(key=MasterSecret, data=a1 + seed)
verify_data = p1[first 12 bytes]
The verify data calculated from this hash is cf919626f1360c536aaad73a. We can show this on the command line:
### set up our MasterSecret as a hex string
$ mshex=$(hexdump -ve '/1 "%02x"' /tmp/mastersecret)
### build the seed
$ echo -en 'client finished' > /tmp/seed
### add SHA256(all_messages) to seed
$ echo -en '\x06\x1d\xda\x04\xb3\xc2\x21\x7f' >> /tmp/seed
$ echo -en '\xf7\x3b\xd7\x9b\x9c\xf8\x8a\x2b' >> /tmp/seed
$ echo -en '\xb6\xec\x50\x54\x04\xaa\xc8\x72' >> /tmp/seed
$ echo -en '\x2d\xb0\x3e\xf4\x17\xb5\x4c\xb4' >> /tmp/seed
### a0 is the same as the seed
$ cat /tmp/seed > /tmp/a0
### a(n) is hmac-sha256(key=secret, data=a(n-1))
$ cat /tmp/a0 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a1
### p(n) is hmac-sha256(key=secret, data=a(n)+seed)
$ cat /tmp/a1 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p1
$ head -c 12 /tmp/p1 > /tmp/verify_data
$ hexdump /tmp/verify_data

0000000 cf 91 96 26 f1 36 0c 53 6a aa d7 3a
Server Encryption Keys Calculation
The server now has the information to calculate the encryption keys that will be used by each side. It uses the following information in this calculation:
  • server random (from Server Hello)
  • client random (from Client Hello)
  • client public key (from Client Key Exchange)
  • server private key (from Server Key Generation)
The server multiplies the client's public key by the server's private key using the curve25519() algorithm. The 32-byte result is called the PreMasterSecret, and is found to be:
df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
I've provided a tool to perform this calculation:
$ gcc -o curve25519-mult curve25519-mult.c
$ ./curve25519-mult server-ephemeral-private.key \
                    client-ephemeral-public.key | hexdump

0000000 df 4a 29 1b aa 1e b7 cf a6 93 4b 29 b4 74 ba ad
0000010 26 97 e2 9f 1f 92 0d cc 77 c8 a0 a0 88 44 76 24
This is identical to the PreMasterSecret found by the client, therefore the following calculations will be identical.

The server then calculates 48 bytes of the MasterSecret from the PreMasterSecret using the following method:
seed = "master secret" + client_random + server_random
a0 = seed
a1 = HMAC-SHA256(key=PreMasterSecret, data=a0)
a2 = HMAC-SHA256(key=PreMasterSecret, data=a1)
p1 = HMAC-SHA256(key=PreMasterSecret, data=a1 + seed)
p2 = HMAC-SHA256(key=PreMasterSecret, data=a2 + seed)
MasterSecret = p1[all 32 bytes] + p2[first 16 bytes]
Here we demonstrate on the command line:
### set up our PreMasterSecret as a hex string
$ pmshex=df4a291baa1eb7cfa6934b29b474baad
$ pmshex=${pmshex}2697e29f1f920dcc77c8a0a088447624
### client random from Client Hello
$ echo -en '\x00\x01\x02\x03\x04\x05\x06\x07' >  /tmp/c_rand
$ echo -en '\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' >> /tmp/c_rand
$ echo -en '\x10\x11\x12\x13\x14\x15\x16\x17' >> /tmp/c_rand
$ echo -en '\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f' >> /tmp/c_rand
### server random from Server Hello
$ echo -en '\x70\x71\x72\x73\x74\x75\x76\x77' >  /tmp/s_rand
$ echo -en '\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f' >> /tmp/s_rand
$ echo -en '\x80\x81\x82\x83\x84\x85\x86\x87' >> /tmp/s_rand
$ echo -en '\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f' >> /tmp/s_rand
### build the seed
$ echo -en 'master secret' > /tmp/seed
$ cat /tmp/c_rand /tmp/s_rand >> /tmp/seed
### a0 is the same as the seed
$ cat /tmp/seed > /tmp/a0
### a(n) is hmac-sha256(key=secret, data=a(n-1))
$ cat /tmp/a0 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$pmshex -binary > /tmp/a1
$ cat /tmp/a1 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$pmshex -binary > /tmp/a2
### p(n) is hmac-sha256(key=secret, data=a(n)+seed)
$ cat /tmp/a1 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$pmshex -binary > /tmp/p1
$ cat /tmp/a2 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$pmshex -binary > /tmp/p2
### first 48 bytes is MasterSecret
$ cat /tmp/p1 /tmp/p2 | head -c 48 > /tmp/mastersecret
$ hexdump /tmp/mastersecret

0000000 91 6a bf 9d a5 59 73 e1 36 14 ae 0a 3f 5d 3f 37
0000010 b0 23 ba 12 9a ee 02 cc 91 34 33 81 27 cd 70 49
0000020 78 1c 8e 19 fc 1e b2 a7 38 7a c0 6a e2 37 34 4c
This gives us a MasterSecret of:
916abf9da55973e13614ae0a3f5d3f37b023ba129aee02cc9134338127cd7049781c8e19fc1eb2a7387ac06ae237344c
We then generate the final encryption keys using a key expansion:
seed = "key expansion" + server_random + client_random
a0 = seed
a1 = HMAC-SHA256(key=MasterSecret, data=a0)
a2 = HMAC-SHA256(key=MasterSecret, data=a1)
a3 = HMAC-SHA256(key=MasterSecret, data=a2)
a4 = ...
p1 = HMAC-SHA256(key=MasterSecret, data=a1 + seed)
p2 = HMAC-SHA256(key=MasterSecret, data=a2 + seed)
p3 = HMAC-SHA256(key=MasterSecret, data=a3 + seed)
p4 = ...
p = p1 + p2 + p3 + p4 ...
client write mac key = [first 20 bytes of p]
server write mac key = [next 20 bytes of p]
client write key = [next 16 bytes of p]
server write key = [next 16 bytes of p]
client write IV = [next 16 bytes of p]
server write IV = [next 16 bytes of p]
We can demonstrate this on the command line:
### continued from above command line example
### set up our MasterSecret as a hex string
$ mshex=$(hexdump -ve '/1 "%02x"' /tmp/mastersecret)
### build the seed
$ echo -en 'key expansion' > /tmp/seed
$ cat /tmp/s_rand /tmp/c_rand >> /tmp/seed
### a0 is the same as the seed
$ cat /tmp/seed > /tmp/a0
### a(n) is hmac-sha256(key=secret, data=a(n-1))
$ cat /tmp/a0 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a1
$ cat /tmp/a1 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a2
$ cat /tmp/a2 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a3
$ cat /tmp/a3 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a4
### p(n) is hmac-sha256(key=secret, data=a(n)+seed)
$ cat /tmp/a1 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p1
$ cat /tmp/a2 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p2
$ cat /tmp/a3 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p3
$ cat /tmp/a4 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p4
$ cat /tmp/p1 /tmp/p2 /tmp/p3 /tmp/p4 > /tmp/p
$ dd if=/tmp/p of=/tmp/client_mac_key bs=1 skip=0  count=20
$ dd if=/tmp/p of=/tmp/server_mac_key bs=1 skip=20 count=20
$ dd if=/tmp/p of=/tmp/client_key     bs=1 skip=40 count=16
$ dd if=/tmp/p of=/tmp/server_key     bs=1 skip=56 count=16
$ dd if=/tmp/p of=/tmp/client_iv      bs=1 skip=72 count=16
$ dd if=/tmp/p of=/tmp/server_iv      bs=1 skip=88 count=16
$ hexdump /tmp/client_mac_key
0000000 1b 7d 11 7c 7d 5f 69 0b c2 63 ca e8 ef 60 af 0f
0000010 18 78 ac c2

$ hexdump /tmp/server_mac_key
0000000 2a d8 bd d8 c6 01 a6 17 12 6f 63 54 0e b2 09 06
0000010 f7 81 fa d2

$ hexdump /tmp/client_key
0000000 f6 56 d0 37 b1 73 ef 3e 11 16 9f 27 23 1a 84 b6

$ hexdump /tmp/server_key
0000000 75 2a 18 e7 a9 fc b7 cb cd d8 f9 8d d8 f7 69 eb

$ hexdump /tmp/client_iv
0000000 a0 d2 55 0c 92 38 ee bf ef 5c 32 25 1a bb 67 d6

$ hexdump /tmp/server_iv
0000000 43 45 28 db 49 37 d5 40 d3 93 13 5e 06 a1 1b b8
From this we get the following key data:
  • client MAC key: 1b7d117c7d5f690bc263cae8ef60af0f1878acc2
  • server MAC key: 2ad8bdd8c601a617126f63540eb20906f781fad2
  • client write key: f656d037b173ef3e11169f27231a84b6
  • server write key: 752a18e7a9fcb7cbcdd8f98dd8f769eb
  • client write IV: a0d2550c9238eebfef5c32251abb67d6
  • server write IV: 434528db4937d540d393135e06a11bb8
Server Change Cipher Spec
The server indicates that it has calculated the shared encryption keys and that all following messages from the server will be encrypted with the server write key.

In the next version of TLS this message type has been removed because it can be inferred.
Record 14 03 03 00 01 01
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 14 - type is 0x14 (ChangeCipherSpec record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 01 - 0x1 (1) bytes of change cipher spec follows
  • 01 - the payload of this message is defined as the byte 0x01
Server Handshake Finished
To verify that the handshake was successful and not tampered with, the server calculates verification data and encrypts it with the server write key.

The verification data is built from a hash of all handshake messages and verifies the integrity of the handshake process.
Record Header 16 03 03 00 40
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 16 - type is 0x16 (handshake record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 40 - 0x40 (64) bytes of handshake message follows
Encryption IV 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60
The server has sent an initialization vector for decrypting this block. Because we have overridden the rand function it is a predictable sequence.
Encrypted Data 18 e0 75 31 7b 10 03 15 f6 08 1f cb f3 13 78 1a ac 73 ef e1 9f e2 5b a1 af 59 c2 0b e9 4f c0 1b da 2d 68 00 29 8b 73 a7 e8 49 d7 4b d4 94 cf 7d
This data is encrypted with the server write key. Because it contains a message authentication code (MAC) and padding it is larger than the decrypted data.

See below for the decrypted data.
Decryption
This data can be decrypted using the encryption IV and the server write key that was generated in the step "Server Encryption Keys Calculation".
### server key
$ hexkey=752a18e7a9fcb7cbcdd8f98dd8f769eb
### IV for this record
$ hexiv=5152535455565758595a5b5c5d5e5f60
### encrypted data
$ echo '18 e0 75 31 7b 10 03 15 f6 08 1f cb f3 13 78 1a'  > /tmp/msg1
$ echo 'ac 73 ef e1 9f e2 5b a1 af 59 c2 0b e9 4f c0 1b' >> /tmp/msg1
$ echo 'da 2d 68 00 29 8b 73 a7 e8 49 d7 4b d4 94 cf 7d' >> /tmp/msg1
$ xxd -r -p /tmp/msg1 \
  | openssl enc -d -nopad -aes-128-cbc -K $hexkey -iv $hexiv | hexdump

0000000 14 00 00 0c 84 4d 3c 10 74 6d d7 22 f9 2f 0c 7e
0000010 20 c4 97 46 d2 a3 0f 23 57 39 90 58 07 53 52 43
0000020 af f2 bf e0 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b

The last 32 bytes contain a 20-byte MAC and padding to bring the data to a
multiple of 16 bytes.  The 20-byte MAC can be reproduced as follows:

### from https://tools.ietf.org/html/rfc2246#section-6.2.3.1
$ sequence='0000000000000000'
$ rechdr='16 03 03'
$ datalen='00 10'
$ data='14 00 00 0c 84 4d 3c 10 74 6d d7 22 f9 2f 0c 7e'
### from "Encryption Keys Calculation"
$ mackey=2ad8bdd8c601a617126f63540eb20906f781fad2
$ echo $sequence $rechdr $datalen $data | xxd -r -p \
  | openssl dgst -sha1 -mac HMAC -macopt hexkey:$mackey

20c49746d2a30f235739905807535243aff2bfe0
Handshake Header 14 00 00 0c
Each handshake message starts with a type and a length.
  • 14 - handshake message type 0x14 (finished)
  • 00 00 0c - 0xC (12) bytes of handshake finished follows
Verify Data 84 4d 3c 10 74 6d d7 22 f9 2f 0c 7e
The verify_data is built from the master secret and the hash of the payload of all handshake records (type=0x16) previous to this one.

The SHA256 of all handshake messages before this one is b2017ba28d0e27f03ae327456b6ff00b4d5bbf0ef7cda83ce1029b521c3e7c35. It can be reproduced as follows:
### combine the payload (without the 5-byte header) of each
### plaintext handshake message, in order
$ tail -c +6 clienthello > test.bin
$ tail -c +6 serverhello >> test.bin
$ tail -c +6 servercert >> test.bin
$ tail -c +6 serverkeyexchange >> test.bin
$ tail -c +6 serverhellodone >> test.bin
$ tail -c +6 clientkeyexchange >> test.bin
### add the decrypted payload of the client finished message
$ cat clientfinishedplain >> test.bin
$ openssl sha256 test.bin

b2017ba28d0e27f03ae327456b6ff00b4d5bbf0ef7cda83ce1029b521c3e7c35
The calculation for verify_data is as follows:
seed = "server finished" + SHA256(all handshake messages)
a0 = seed
a1 = HMAC-SHA256(key=MasterSecret, data=a0)
p1 = HMAC-SHA256(key=MasterSecret, data=a1 + seed)
verify_data = p1[first 12 bytes]
The verify data calculated from this hash is 844d3c10746dd722f92f0c7e. We can show this on the command line:
### set up our MasterSecret as a hex string
$ mshex=$(hexdump -ve '/1 "%02x"' /tmp/mastersecret)
### build the seed
$ echo -en 'server finished' > /tmp/seed
### add SHA256(all_messages) to seed
$ echo -en '\xb2\x01\x7b\xa2\x8d\x0e\x27\xf0' >> /tmp/seed
$ echo -en '\x3a\xe3\x27\x45\x6b\x6f\xf0\x0b' >> /tmp/seed
$ echo -en '\x4d\x5b\xbf\x0e\xf7\xcd\xa8\x3c' >> /tmp/seed
$ echo -en '\xe1\x02\x9b\x52\x1c\x3e\x7c\x35' >> /tmp/seed
### a0 is the same as the seed
$ cat /tmp/seed > /tmp/a0
### a(n) is hmac-sha256(key=secret, data=a(n-1))
$ cat /tmp/a0 | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/a1
### p(n) is hmac-sha256(key=secret, data=a(n)+seed)
$ cat /tmp/a1 /tmp/seed | openssl dgst -sha256 \
   -mac HMAC -macopt hexkey:$mshex -binary > /tmp/p1
$ head -c 12 /tmp/p1 > /tmp/verify_data
$ hexdump /tmp/verify_data

0000000 84 4d 3c 10 74 6d d7 22 f9 2f 0c 7e
Client Application Data
The client sends the data "ping".
Record Header 17 03 03 00 30
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 17 - type is 0x17 (application data)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 30 - 0x30 (48) bytes of application data follows
Encryption IV 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
The client has sent an initialization vector for decrypting this block. Because we have overridden the rand function it is a predictable sequence.
Encrypted Data 6c 42 1c 71 c4 2b 18 3b fa 06 19 5d 13 3d 0a 09 d0 0f c7 cb 4e 0f 5d 1c da 59 d1 47 ec 79 0c 99
This data is encrypted with the client write key. Because it contains a message authentication code (MAC) and padding it is larger than the decrypted data.

See below for the decrypted data.
Decryption
This data can be decrypted using the encryption IV and the client write key that was generated in the step "Client Encryption Keys Calculation".
### client key
$ hexkey=f656d037b173ef3e11169f27231a84b6
### IV for this record
$ hexiv=000102030405060708090a0b0c0d0e0f
### encrypted data
$ echo '6c 42 1c 71 c4 2b 18 3b fa 06 19 5d 13 3d 0a 09'  > /tmp/msg1
$ echo 'd0 0f c7 cb 4e 0f 5d 1c da 59 d1 47 ec 79 0c 99' >> /tmp/msg1
$ xxd -r -p /tmp/msg1 \
  | openssl enc -d -nopad -aes-128-cbc -K $hexkey -iv $hexiv | hexdump

0000000 70 69 6e 67 60 10 12 49 f7 4a 03 77 c9 ca cf 63
0000010 09 75 13 70 d8 0c fc aa 07 07 07 07 07 07 07 07

The last 28 bytes contain a 20-byte MAC and padding to bring the data to a
multiple of 16 bytes.  The 20-byte MAC can be reproduced as follows:

### from https://tools.ietf.org/html/rfc2246#section-6.2.3.1
$ sequence='0000000000000001'
$ rechdr='17 03 03'
$ datalen='00 04'
$ data='70 69 6e 67'
### from "Encryption Keys Calculation"
$ mackey=1b7d117c7d5f690bc263cae8ef60af0f1878acc2
$ echo $sequence $rechdr $datalen $data | xxd -r -p \
  | openssl dgst -sha1 -mac HMAC -macopt hexkey:$mackey

60101249f74a0377c9cacf6309751370d80cfcaa
Application Data 70 69 6e 67
The bytes "ping".
Server Application Data
The server replies with the data "pong".
Record Header 17 03 03 00 30
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 17 - type is 0x17 (application data)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 30 - 0x30 (48) bytes of application data follows
Encryption IV 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70
The server has sent an initialization vector for decrypting this block. Because we have overridden the rand function it is a predictable sequence.
Encrypted Data 97 83 48 8a f5 fa 20 bf 7a 2e f6 9d eb b5 34 db 9f b0 7a 8c 27 21 de e5 40 9f 77 af 0c 3d de 56
This data is encrypted with the server write key. Because it contains a message authentication code (MAC) and padding it is larger than the decrypted data.

See below for the decrypted data.
Decryption
This data can be decrypted using the encryption IV and the server write key that was generated in the step "Server Encryption Keys Calculation".
### server key
$ hexkey=752a18e7a9fcb7cbcdd8f98dd8f769eb
### IV for this record
$ hexiv=6162636465666768696a6b6c6d6e6f70
### encrypted data
$ echo '97 83 48 8a f5 fa 20 bf 7a 2e f6 9d eb b5 34 db'  > /tmp/msg1
$ echo '9f b0 7a 8c 27 21 de e5 40 9f 77 af 0c 3d de 56' >> /tmp/msg1
$ xxd -r -p /tmp/msg1 \
  | openssl enc -d -nopad -aes-128-cbc -K $hexkey -iv $hexiv | hexdump

0000000 70 6f 6e 67 5a c7 99 dc cf dc 0f af 95 2b dc 91
0000010 18 af 20 0e e3 1c 51 05 07 07 07 07 07 07 07 07

The last 28 bytes contain a 20-byte MAC and padding to bring the data to a
multiple of 16 bytes.  The 20-byte MAC can be reproduced as follows:

### from https://tools.ietf.org/html/rfc2246#section-6.2.3.1
$ sequence='0000000000000001'
$ rechdr='17 03 03'
$ datalen='00 04'
$ data='70 6f 6e 67'
### from "Encryption Keys Calculation"
$ mackey=2ad8bdd8c601a617126f63540eb20906f781fad2
$ echo $sequence $rechdr $datalen $data | xxd -r -p \
  | openssl dgst -sha1 -mac HMAC -macopt hexkey:$mackey

5ac799dccfdc0faf952bdc9118af200ee31c5105
Application Data 70 6f 6e 67
The bytes "pong".
Client Close Notify
The client sends an alert that it is closing the connection.
Record Header 15 03 03 00 30
TLS sessions are broken into the sending and receiving of "records", which are blocks of data with a type, a protocol version, and a length.
  • 15 - type is 0x15 (alert record)
  • 03 03 - protocol version is "3,3" (TLS 1.2)
  • 00 30 - 0x30 (48) bytes of alert data follows
Encryption IV 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
The client has sent an initialization vector for decrypting this block. Because we have overridden the rand function it is a predictable sequence.
Encrypted Data 0d 83 f9 79 04 75 0d d8 fd 8a a1 30 21 86 32 63 4f d0 65 e4 62 83 79 b8 8b bf 9e fd 12 87 a6 2d
This data is encrypted with the client write key. Because it contains a message authentication code (MAC) and padding it is larger than the decrypted data.

See below for the decrypted data.
Decryption
This data can be decrypted using the encryption IV and the client write key that was generated in the step "Client Encryption Keys Calculation".
### client key
$ hexkey=f656d037b173ef3e11169f27231a84b6
### IV for this record
$ hexiv=101112131415161718191a1b1c1d1e1f
### encrypted data
$ echo '0d 83 f9 79 04 75 0d d8 fd 8a a1 30 21 86 32 63'  > /tmp/msg1
$ echo '4f d0 65 e4 62 83 79 b8 8b bf 9e fd 12 87 a6 2d' >> /tmp/msg1
$ xxd -r -p /tmp/msg1 \
  | openssl enc -d -nopad -aes-128-cbc -K $hexkey -iv $hexiv | hexdump

0000000 01 00 92 79 9c ba 81 9f 31 07 44 c5 59 62 2b e4
0000010 2b ce 3d 6a 41 fb 09 09 09 09 09 09 09 09 09 09

The last 30 bytes contain a 20-byte MAC and padding to bring the data to a
multiple of 16 bytes.  The 20-byte MAC can be reproduced as follows:

### from https://tools.ietf.org/html/rfc2246#section-6.2.3.1
$ sequence='0000000000000002'
$ rechdr='15 03 03'
$ datalen='00 02'
$ data='01 00'
### from "Encryption Keys Calculation"
$ mackey=1b7d117c7d5f690bc263cae8ef60af0f1878acc2
$ echo $sequence $rechdr $datalen $data | xxd -r -p \
  | openssl dgst -sha1 -mac HMAC -macopt hexkey:$mackey

92799cba819f310744c559622be42bce3d6a41fb
Alert Level 01
  • 01 - assigned value for "Warning"
A "Warning" alert is informational.
Alert Type 00
  • 00 - assigned value for "Close Notify"
This message notifies the recipient that the sender will not send any more messages on this connection.

The code for this project, including packet captures, can be found on GitHub.

You may also be interested in the TLS 1.3 version of this document.

If you found this page useful or interesting let me know via Twitter @XargsNotBombs.

[print]