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.
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:
Each handshake message starts with a type and a length.
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.
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.
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.
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.
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).
Compression has characteristics that can weaken
the security of the encrypted data
(see CRIME).
so this feature has been removed from future TLS protocols.
The client has provided a list of optional
extensions which the server can use to
take action or enable new features.
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.
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.
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.
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.
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.
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.
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.
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.
if vers == 0 {
// Some TLS servers fail if the record version is
// greater than TLS 1.0 for the initial ClientHello.
vers = VersionTLS10
}
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.
Each handshake message starts with a type and a length.
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.
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.
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.
The server has selected cipher suite 0xC013
(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) from the
list of options given by the client.
The server has selected compression method
0x00 ("Null", which performs no compression)
from the list of options given by the client.
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.
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.
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
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.
Each handshake message starts with a type and a length.
The certificate message begins with the
length of all certificate data that will follow.
The length of the first (and only) certificate.
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.
Each handshake message starts with a type and a length.
The server chooses the elliptic curve that points will be calculated from.
The server provides its public key from the step "Server Key Exchange Generation".
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.
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.
Each handshake message starts with a type and a length.
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.
Each handshake message starts with a type and a length.
The client provides its public key from the step "Client Key Exchange Generation".
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.
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.
The client has sent an initialization vector for decrypting this block.
Because we have overridden the rand function it
is a predictable sequence.
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".
Each handshake message starts with a type and a length.
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 .
It can be reproduced as follows:
The calculation for verify_data is as follows:
The verify data calculated from this hash is .
We can show this on the command line:
### 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
### 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
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]
### 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.
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.
The server has sent an initialization vector for decrypting this block.
Because we have overridden the rand function it
is a predictable sequence.
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".
Each handshake message starts with a type and a length.
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 .
It can be reproduced as follows:
The calculation for verify_data is as follows:
The verify data calculated from this hash is .
We can show this on the command line:
### 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
### 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
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]
### 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.
The client has sent an initialization vector for decrypting this block.
Because we have overridden the rand function it
is a predictable sequence.
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".
The bytes "ping".
### 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
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.
The server has sent an initialization vector for decrypting this block.
Because we have overridden the rand function it
is a predictable sequence.
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".
The bytes "pong".
### 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
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.
The client has sent an initialization vector for decrypting this block.
Because we have overridden the rand function it
is a predictable sequence.
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".
A "Warning" alert is informational.
This message notifies the recipient that
the sender will not send any more messages on this connection.
### 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
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.