QUIC DTLS TLS 1.3 TLS 1.2

The Illustrated TLS 1.3 Connection

Every byte explained and reproduced

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

Client Key Exchange Generation

The client begins by generating 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 Hello
The session begins with the client saying "Hello". The client provides information including the following:
  • client random data (used later in the handshake)
  • a list of cipher suites that the client supports
  • a list of public keys that the server might find suitable for key exchange
  • protocol versions that the client can support
Record Header 16 03 01 00 f8
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 f8 - 0xF8 (248) bytes of handshake message follows
Interestingly the version in this record is "3,1" (TLS 1.0) instead of "3,4" (TLS 1.3). This is done for interoperability with earlier implementations.
Handshake Header 01 00 00 f4
Each handshake message starts with a type and a length.
  • 01 - handshake message type 0x01 (client hello)
  • 00 00 f4 - 0xF4 (244) bytes of client hello data follows
Client Version 03 03
A protocol version of "3,3" (meaning TLS 1.2) is given. Because middleboxes have been created and widely deployed that do not allow protocol versions that they do not recognize, the TLS 1.3 session must be disguised as a TLS 1.2 session. This field is no longer used for version negotiation and is hardcoded to the 1.2 version. Instead, version negotiation is performed using the "Supported Versions" extension below.

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. This data will be used later in the session. In this example we've made the random data a predictable string.
Session ID 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff
In previous versions of TLS the client could provide an ID of a previously negotiated session, which allows the server and client to skip the time and cost of negotiating new keys.

In TLS 1.3 this "session resume" is done via the more flexible PSK (pre-shared keys) mechanism, so this field is no longer needed for that purpose. Instead, a non-empty value in this field is used to trigger "middlebox compatibility mode" which helps TLS 1.3 sessions to be disguised as resumed TLS 1.2 sessions. The client has generated random data to populate this field.
  • 20 - 0x20 (32) bytes of session ID follow
  • e0 e1 ... fe ff - fake session ID
Cipher Suites 00 08 13 02 13 03 13 01 00 ff
The client provides an ordered list of which cipher suites it will support for encryption. The list is in the order preferred by the client, with highest preference first.

In TLS 1.3 the list of possible cipher suites has been greatly reduced. All the remaining suites are AEAD algorithms which provide stronger encryption guarantees than many previous suites with an easier all-in-one implementation.
  • 00 08 - 8 bytes of cipher suite data
  • 13 02 - assigned value for TLS_AES_256_GCM_SHA384
  • 13 03 - assigned value for TLS_CHACHA20_POLY1305_SHA256
  • 13 01 - assigned value for TLS_AES_128_GCM_SHA256
  • 00 ff - assigned value for TLS_EMPTY_RENEGOTIATION_INFO_SCSV
Compression Methods 01 00
Previous versions of TLS supported compression, which was found to leak information about the encrypted data allowing it to be read (see CRIME).

TLS 1.3 no longer allows compression, so this field is always a single entry with the "null" compression method which performs no change to the data.
  • 01 - 1 bytes of compression methods
  • 00 - assigned value for "null" compression
Extensions Length 00 a3
The client has provided a list of optional extensions which the server can use to take action or enable new features.
  • 00 a3 - the extensions will take 0xA3 (163) 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 an HTTPS server would not be able to provide service for multiple hostnames (virtual hosts) on a single IP address 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 - EC Point Formats 00 0b 00 04 03 00 01 02
The client has indicated that it supports receiving elliptic curve data points in the following compression formats:
  • 00 0b - assigned value for extension "ec point formats"
  • 00 04 - 4 bytes of format types follow
  • 03 - 3 bytes of format types follow
  • 00 - assigned value for format "uncompressed"
  • 01 - assigned value for format "ansiX962_compressed_prime"
  • 02 - assigned value for format "ansiX962_compressed_char2"
Extension - Supported Groups 00 0a 00 16 00 14 00 1d 00 17 00 1e 00 19 00 18 01 00 01 01 01 02 01 03 01 04
The client has indicated that it supports elliptic curve (EC) cryptography for ten curve types. To make this extension more generic for other cryptography types it calls these "supported groups" instead of "supported curves".

This list is presented in descending order of the client's preference.
  • 00 0a - assigned value for extension "supported groups"
  • 00 16 - 0x16 (22) bytes of "supported group" extension data follows
  • 00 14 - 0x14 (20) 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 1e - assigned value for the curve "x448"
  • 00 19 - assigned value for the curve "secp521r1"
  • 00 18 - assigned value for the curve "secp384r1"
  • 01 00 - assigned value for the curve "ffdhe2048"
  • 01 01 - assigned value for the curve "ffdhe3072"
  • 01 02 - assigned value for the curve "ffdhe4096"
  • 01 03 - assigned value for the curve "ffdhe6144"
  • 01 04 - assigned value for the curve "ffdhe8192"
Extension - Session Ticket 00 23 00 00
The client indicates it has no session ticket to provide for this connection.
  • 00 23 - assigned value for extension "Session Ticket"
  • 00 00 - 0 bytes of "Session Ticket" extension data follows
Extension - Encrypt-Then-MAC 00 16 00 00
The client indicates it can support EtM, which prevents certain vulnerabilities in earlier versions of TLS. In TLS 1.3 this mechanism is always used, so this extension will have no effect in this session.
  • 00 16 - assigned value for extension "Encrypt Then MAC"
  • 00 00 - 0 bytes of "Encrypt Then MAC" extension data follows
Extension - Extended Master Secret 00 17 00 00
The client indicates support for extra cryptographic operations which prevent vulnerabilities in earlier versions of TLS (see RFC 7627 for details). In TLS 1.3 the vulnerabilities are no longer present, so this extension will have no effect in this session.
  • 00 17 - assigned value for extension "Extended Master Secret"
  • 00 00 - 0 bytes of "Extended Master Secret" extension data follows
Extension - Signature Algorithms 00 0d 00 1e 00 1c 04 03 05 03 06 03 08 07 08 08 08 09 08 0a 08 0b 08 04 08 05 08 06 04 01 05 01 06 01
This extension indicates which signature algorithms the client supports. This can influence the certificate that the server presents to the client, as well as the signature that is sent by the server in the CertificateVerify record.

This list is presented in descending order of the client's preference.
  • 00 0d - assigned value for extension "Signature Algorithms"
  • 00 1e - 0x1E (30) bytes of "Signature Algorithms" extension data follows
  • 00 1c - 0x1C (28) bytes of data are in the following list of algorithms
  • 04 03 - assigned value for ECDSA-SECP256r1-SHA256
  • 05 03 - assigned value for ECDSA-SECP384r1-SHA384
  • 06 03 - assigned value for ECDSA-SECP521r1-SHA512
  • 08 07 - assigned value for ED25519
  • 08 08 - assigned value for ED448
  • 08 09 - assigned value for RSA-PSS-PSS-SHA256
  • 08 0a - assigned value for RSA-PSS-PSS-SHA384
  • 08 0b - assigned value for RSA-PSS-PSS-SHA512
  • 08 04 - assigned value for RSA-PSS-RSAE-SHA256
  • 08 05 - assigned value for RSA-PSS-RSAE-SHA384
  • 08 06 - assigned value for RSA-PSS-RSAE-SHA512
  • 04 01 - assigned value for RSA-PKCS1-SHA256
  • 05 01 - assigned value for RSA-PKCS1-SHA384
  • 06 01 - assigned value for RSA-PKCS1-SHA512
Extension - Supported Versions 00 2b 00 03 02 03 04
The client indicates its support of TLS 1.3. This is the only indication in the Client Hello record that hints the client supports TLS 1.3, since for compatibility reasons it has otherwise pretended to be a TLS 1.2 connection attempt.
  • 00 2b - assigned value for extension "Supported Versions"
  • 00 03 - 3 bytes of "Supported Versions" extension data follows
  • 02 - 2 bytes of TLS versions follow
  • 03 04 - assigned value for TLS 1.3
Extension - PSK Key Exchange Modes 00 2d 00 02 01 01
The client indicates the modes available for establishing keys from pre-shared keys (PSKs). Since we do not use PSKs in this session, this extension has no effect.
  • 00 2d - assigned value for extension "PSK Key Exchange Modes"
  • 00 02 - 2 bytes of "PSK Key Exchange Modes" extension data follows
  • 01 - 1 bytes of exchange modes follow
  • 01 - assigned value for "PSK with (EC)DHE key establishment"
Extension - Key Share 00 33 00 26 00 24 00 1d 00 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 sends one or more ephemeral public keys using algorithm(s) that it thinks the server will support. This allows the rest of the handshake after the ClientHello and ServerHello messages to be encrypted, unlike previous protocol versions where the handshake was sent in the clear.
  • 00 33 - assigned value for extension "Key Share"
  • 00 26 - 0x26 (38) bytes of "Key Share" extension data follows
  • 00 24 - 0x24 (36) bytes of key share data follows
  • 00 1d - assigned value for x25519 (key exchange via curve25519)
  • 00 20 - 0x20 (32) bytes of public key follows
  • 35 80 ... 62 54 - public key from the step "Client Key Exchange Generation"
Server Key Exchange Generation

The server generates 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 Hello
The server says "Hello" back. The server provides information including the following:
  • server random data (used later in the handshake)
  • a selected cipher suite
  • a public key for key exchange
  • the negotiated protocol version
Record Header 16 03 03 00 7a
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 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 7a - 0x7A (122) bytes of handshake message follows
Handshake Header 02 00 00 76
Each handshake message starts with a type and a length.
  • 02 - handshake message type 0x02 (server hello)
  • 00 00 76 - 0x76 (118) bytes of server hello data follows
Server Version 03 03
A protocol version of "3,3" (meaning TLS 1.2) is given. Because middleboxes have been created and widely deployed that do not allow protocol versions that they do not recognize, the TLS 1.3 session must be disguised as a TLS 1.2 session. This field is no longer used for version negotiation and is hardcoded to the 1.2 version. Instead, version negotiation is performed using the "Supported Versions" extension below.

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. This data will be used later in the session. In this example we've made the random data a predictable string.
Session ID 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff
This legacy field is no longer used to identify and re-use sessions. Instead, the server echos the session ID provided by the client, if any.
  • 20 - 0x20 (32) bytes of session ID follow
  • e0 e1 ... fe ff - session ID copied from Client Hello
Cipher Suite 13 02
The server has selected cipher suite 0x1302 (TLS_AES_256_GCM_SHA384) 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 2e
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 understand and support all extensions listed.
  • 00 2e - the extensions will take 0x2E (46) bytes of data
Extension - Supported Versions 00 2b 00 02 03 04
The server indicates the negotiated TLS version of 1.3.
  • 00 2b - assigned value for extension "Supported Versions"
  • 00 02 - 2 bytes of "Supported Versions" extension data follows
  • 03 04 - assigned value for TLS 1.3
Extension - Key Share 00 33 00 24 00 1d 00 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 sends a public key using the algorithm of the public key sent by the client. Once this is sent encryption keys can be calculated and the rest of the handshake will be encrypted, unlike previous protocol versions where the handshake was sent in the clear.
  • 00 33 - assigned value for extension "Key Share"
  • 00 24 - 0x24 (36) bytes of "Key Share" extension data follows
  • 00 1d - assigned value for x25519 (key exchange via curve25519)
  • 00 20 - 0x20 (32) bytes of public key follows
  • 9f d7 ... b6 15 - public key from the step "Server Key Exchange Generation"
Server Handshake Keys Calc
The server now has the information to calculate the keys used to encrypt the rest of the handshake. It uses the following information in this calculation: First, the server finds the shared secret, which is the result of the key exchange that allows the client and server to agree on a number. The server multiplies the client's public key by the server's private key using the curve25519() algorithm. The 32-byte result is found to be:
df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
I've provided a tool to perform this calculation:
$ cc -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
We then calculate the SHA384 hash of all handshake messages to this point (ClientHello and ServerHello). The hash does not include the 5-byte "record" headers. This "hello_hash" is e05f64fcd082bdb0dce473adf669c2769f257a1c75a51b7887468b5e0e7a7de4f4d34555112077f16e079019d5a845bd:
$ (tail -c +6 clienthello; tail -c +6 serverhello) | openssl sha384
e05f64fcd082bdb0dce473adf669c2769f257a1c75a51b7887468b5e0e7a7de4f4d34555112077f16e079019d5a845bd
We then feed the hash and the shared secret into a set of key derivation operations, designed to ensure the integrity of the handshake process and to protect against known and possible attacks:
early_secret = HKDF-Extract(salt: 00, key: 00...)
empty_hash = SHA384("")
derived_secret = HKDF-Expand-Label(key: early_secret, label: "derived", ctx: empty_hash, len: 48)
handshake_secret = HKDF-Extract(salt: derived_secret, key: shared_secret)
client_secret = HKDF-Expand-Label(key: handshake_secret, label: "c hs traffic", ctx: hello_hash, len: 48)
server_secret = HKDF-Expand-Label(key: handshake_secret, label: "s hs traffic", ctx: hello_hash, len: 48)
client_handshake_key = HKDF-Expand-Label(key: client_secret, label: "key", ctx: "", len: 32)
server_handshake_key = HKDF-Expand-Label(key: server_secret, label: "key", ctx: "", len: 32)
client_handshake_iv = HKDF-Expand-Label(key: client_secret, label: "iv", ctx: "", len: 12)
server_handshake_iv = HKDF-Expand-Label(key: server_secret, label: "iv", ctx: "", len: 12)
This has introduced two new cryptographic methods:
  • HKDF-Extract - given a salt and some bytes of key material create 384 bits (48 bytes) of new key material, with the input key material's entropy evenly distributed in the output.
  • HKDF-Expand-Label - given the inputs of key material, label, and context data, create a new key of the requested length.
I've created an HKDF tool to perform these operations on the command line.
$ hello_hash=e05f64fcd082bdb0dce473adf669c2769f257a1c75a51b7887468b5e0e7a7de4f4d34555112077f16e079019d5a845bd
$ shared_secret=df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
$ zero_key=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
$ early_secret=$(./hkdf-384 extract 00 $zero_key)
$ empty_hash=$(openssl sha384 < /dev/null | sed -e 's/.* //')
$ derived_secret=$(./hkdf-384 expandlabel $early_secret "derived" $empty_hash 48)
$ handshake_secret=$(./hkdf-384 extract $derived_secret $shared_secret)
$ csecret=$(./hkdf-384 expandlabel $handshake_secret "c hs traffic" $hello_hash 48)
$ ssecret=$(./hkdf-384 expandlabel $handshake_secret "s hs traffic" $hello_hash 48)
$ client_handshake_key=$(./hkdf-384 expandlabel $csecret "key" "" 32)
$ server_handshake_key=$(./hkdf-384 expandlabel $ssecret "key" "" 32)
$ client_handshake_iv=$(./hkdf-384 expandlabel $csecret "iv" "" 12)
$ server_handshake_iv=$(./hkdf-384 expandlabel $ssecret "iv" "" 12)
$ echo hssec: $handshake_secret
$ echo ssec: $ssecret
$ echo csec: $csecret
$ echo skey: $server_handshake_key
$ echo siv: $server_handshake_iv
$ echo ckey: $client_handshake_key
$ echo civ: $client_handshake_iv

hssec: bdbbe8757494bef20de932598294ea65b5e6bf6dc5c02a960a2de2eaa9b07c929078d2caa0936231c38d1725f179d299
ssec: 23323da031634b241dd37d61032b62a4f450584d1f7f47983ba2f7cc0cdcc39a68f481f2b019f9403a3051908a5d1622
csec: db89d2d6df0e84fed74a2288f8fd4d0959f790ff23946cdf4c26d85e51bebd42ae184501972f8d30c4a3e4a3693d0ef0
skey: 9f13575ce3f8cfc1df64a77ceaffe89700b492ad31b4fab01c4792be1b266b7f
siv: 9563bc8b590f671f488d2da3
ckey: 1135b4826a9a70257e5a391ad93093dfd7c4214812f493b3e3daae1eb2b1ac69
civ: 4256d2e0e88babdd05eb2f27
From this we get the following key data:
  • handshake secret: bdbbe8757494bef20de932598294ea65b5e6bf6dc5c02a960a2de2eaa9b07c929078d2caa0936231c38d1725f179d299
  • server handshake traffic secret: 23323da031634b241dd37d61032b62a4f450584d1f7f47983ba2f7cc0cdcc39a68f481f2b019f9403a3051908a5d1622.
  • client handshake traffic secret: db89d2d6df0e84fed74a2288f8fd4d0959f790ff23946cdf4c26d85e51bebd42ae184501972f8d30c4a3e4a3693d0ef0.
  • server handshake key: 9f13575ce3f8cfc1df64a77ceaffe89700b492ad31b4fab01c4792be1b266b7f
  • server handshake IV: 9563bc8b590f671f488d2da3
  • client handshake key: 1135b4826a9a70257e5a391ad93093dfd7c4214812f493b3e3daae1eb2b1ac69
  • client handshake IV: 4256d2e0e88babdd05eb2f27
Client Handshake Keys Calc
The client now has the information to calculate the keys used to encrypt the rest of the handshake. It uses the following information in this calculation: First, the client finds the shared secret, which is the result of the key exchange that allows the client and server to agree on a number. The client multiplies the server's public key by the client's private key using the curve25519() algorithm. The properties of elliptic curve multiplication will cause this to result in the same number found by the server in its multiplication. The 32-byte result is found to be:
df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
I've provided a tool to perform this calculation:
$ cc -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
Since the shared secret above is the same number calculated by the server in "Server Handshake Keys Calc", the rest of the calculation is identical and the same values are found:
  • server handshake key: 9f13575ce3f8cfc1df64a77ceaffe89700b492ad31b4fab01c4792be1b266b7f
  • server handshake IV: 9563bc8b590f671f488d2da3
  • client handshake key: 1135b4826a9a70257e5a391ad93093dfd7c4214812f493b3e3daae1eb2b1ac69
  • client handshake IV: 4256d2e0e88babdd05eb2f27
Server Change Cipher Spec
This record served a purpose in earlier versions on TLS but is no longer needed. In "middlebox compatibility mode" this record is sent to help disguise the session as a TLS 1.2 session.
Record Header 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 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 01 - the length of the record payload is 1 bytes
  • 01 - the payload of this message is defined as the byte 0x01
Wrapped Record
The connection (including the handshake) is encrypted from this point on. The encryption of handshake data is new in TLS 1.3.

To reduce issues with middleboxes that block unrecognized TLS protocols, the encrypted handshake records are disguised as a TLS 1.2 session that has performed a successful session resume.

This wrapped record is discussed in its own section below this one.
Record Header 17 03 03 00 17
The TLS 1.3 records are encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 17 - 0x17 (23) bytes of wrapped data follows
Encrypted Data 6b e0 2f 9d a7 c2 dc
This data is encrypted with the server handshake key.
Auth Tag 9d de f5 6f 24 68 b9 0a df a2 51 01 ab 03 44 ae
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the server handshake key and the server handshake IV that were generated during the "Server Handshake Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 0. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Server Handshake Keys Calc" step
$ key=9f13575ce3f8cfc1df64a77ceaffe89700b492ad31b4fab01c4792be1b266b7f
$ iv=9563bc8b590f671f488d2da3
### from this record
$ recdata=1703030017
$ authtag=9ddef56f2468b90adfa25101ab0344ae
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "6b e0 2f 9d a7 c2 dc" | xxd -r -p > /tmp/msg1
$ cat /tmp/msg1 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  08 00 00 02 00 00 16                              |.......|
Encrypted Extensions 08 00 00 02 00 00
This handshake message is represented in its own section below.
Record Type 16
Each TLS 1.3 record disguised as TLS 1.2 application data has a final byte which indicates its actual record type.
  • 16 - type is 0x16 (handshake record)
Server Encrypted Extensions
Any extensions that aren't needed for negotiating encryption keys should be listed here so that they can be hidden from eavesdroppers and middleboxes.
Handshake Header 08 00 00 02
Each handshake message starts with a type and a length.
  • 08 - handshake message type 0x08 (encrypted extensions)
  • 00 00 02 - 2 bytes of handshake message data follows
Extensions 00 00
  • 00 00 - 0 bytes of extension data follows
Wrapped Record
The encryption of handshake data is new in TLS 1.3.

To reduce issues with middleboxes that block unrecognized TLS protocols, the encrypted handshake records are disguised as a TLS 1.2 session that has performed a successful session resume.

This wrapped record is discussed in its own section below this one.
Record Header 17 03 03 03 43
The TLS 1.3 records are encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 03 43 - 0x343 (835) bytes of wrapped data follows
Encrypted Data ba f0 0a 9b e5 0f 3f 23 07 e7 26 ed cb da cb e4 b1 86 16 44 9d 46 c6 20 7a f6 e9 95 3e e5 d2 41 1b a6 5d 31 fe af 4f 78 76 4f 2d 69 39 87 18 6c c0 13 29 c1 87 a5 e4 60 8e 8d 27 b3 18 e9 8d d9 47 69 f7 73 9c e6 76 83 92 ca ca 8d cc 59 7d 77 ec 0d 12 72 23 37 85 f6 e6 9d 6f 43 ef fa 8e 79 05 ed fd c4 03 7e ee 59 33 e9 90 a7 97 2f 20 69 13 a3 1e 8d 04 93 13 66 d3 d8 bc d6 a4 a4 d6 47 dd 4b d8 0b 0f f8 63 ce 35 54 83 3d 74 4c f0 e0 b9 c0 7c ae 72 6d d2 3f 99 53 df 1f 1c e3 ac eb 3b 72 30 87 1e 92 31 0c fb 2b 09 84 86 f4 35 38 f8 e8 2d 84 04 e5 c6 c2 5f 66 a6 2e be 3c 5f 26 23 26 40 e2 0a 76 91 75 ef 83 48 3c d8 1e 6c b1 6e 78 df ad 4c 1b 71 4b 04 b4 5f 6a c8 d1 06 5a d1 8c 13 45 1c 90 55 c4 7d a3 00 f9 35 36 ea 56 f5 31 98 6d 64 92 77 53 93 c4 cc b0 95 46 70 92 a0 ec 0b 43 ed 7a 06 87 cb 47 0c e3 50 91 7b 0a c3 0c 6e 5c 24 72 5a 78 c4 5f 9f 5f 29 b6 62 68 67 f6 f7 9c e0 54 27 35 47 b3 6d f0 30 bd 24 af 10 d6 32 db a5 4f c4 e8 90 bd 05 86 92 8c 02 06 ca 2e 28 e4 4e 22 7a 2d 50 63 19 59 35 df 38 da 89 36 09 2e ef 01 e8 4c ad 2e 49 d6 2e 47 0a 6c 77 45 f6 25 ec 39 e4 fc 23 32 9c 79 d1 17 28 76 80 7c 36 d7 36 ba 42 bb 69 b0 04 ff 55 f9 38 50 dc 33 c1 f9 8a bb 92 85 83 24 c7 6f f1 eb 08 5d b3 c1 fc 50 f7 4e c0 44 42 e6 22 97 3e a7 07 43 41 87 94 c3 88 14 0b b4 92 d6 29 4a 05 40 e5 a5 9c fa e6 0b a0 f1 48 99 fc a7 13 33 31 5e a0 83 a6 8e 1d 7c 1e 4c dc 2f 56 bc d6 11 96 81 a4 ad bc 1b bf 42 af d8 06 c3 cb d4 2a 07 6f 54 5d ee 4e 11 8d 0b 39 67 54 be 2b 04 2a 68 5d d4 72 7e 89 c0 38 6a 94 d3 cd 6e cb 98 20 e9 d4 9a fe ed 66 c4 7e 6f c2 43 ea be bb cb 0b 02 45 38 77 f5 ac 5d bf bd f8 db 10 52 a3 c9 94 b2 24 cd 9a aa f5 6b 02 6b b9 ef a2 e0 13 02 b3 64 01 ab 64 94 e7 01 8d 6e 5b 57 3b d3 8b ce f0 23 b1 fc 92 94 6b bc a0 20 9c a5 fa 92 6b 49 70 b1 00 91 03 64 5c b1 fc fe 55 23 11 ff 73 05 58 98 43 70 03 8f d2 cc e2 a9 1f c7 4d 6f 3e 3e a9 f8 43 ee d3 56 f6 f8 2d 35 d0 3b c2 4b 81 b5 8c eb 1a 43 ec 94 37 e6 f1 e5 0e b6 f5 55 e3 21 fd 67 c8 33 2e b1 b8 32 aa 8d 79 5a 27 d4 79 c6 e2 7d 5a 61 03 46 83 89 19 03 f6 64 21 d0 94 e1 b0 0a 9a 13 8d 86 1e 6f 78 a2 0a d3 e1 58 00 54 d2 e3 05 25 3c 71 3a 02 fe 1e 28 de ee 73 36 24 6f 6a e3 43 31 80 6b 46 b4 7b 83 3c 39 b9 d3 1c d3 00 c2 a6 ed 83 13 99 77 6d 07 f5 70 ea f0 05 9a 2c 68 a5 f3 ae 16 b6 17 40 4a f7 b7 23 1a 4d 94 27 58 fc 02 0b 3f 23 ee 8c 15 e3 60 44 cf d6 7c d6 40 99 3b 16 20 75 97 fb f3 85 ea 7a 4d 99 e8 d4 56 ff 83 d4 1f 7b 8b 4f 06 9b 02 8a 2a 63 a9 19 a7 0e 3a 10 e3 08 41
This data is encrypted with the server handshake key.
Auth Tag 58 fa a5 ba fa 30 18 6c 6b 2f 23 8e b5 30 c7 3e
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the server handshake key and the server handshake IV that were generated during the "Server Handshake Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 1. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Server Handshake Keys Calc" step
$ key=9f13575ce3f8cfc1df64a77ceaffe89700b492ad31b4fab01c4792be1b266b7f
$ iv=9563bc8b590f671f488d2da3
### from this record
$ recdata=1703030343
$ authtag=58faa5bafa30186c6b2f238eb530c73e
$ recordnum=1
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "ba f0 0a 9b e5 0f 3f 23 07 e7 26 ed cb da cb e4 b1 86 16
  ... snip ...
  a9 19 a7 0e 3a 10 e3 08 41" | xxd -r -p > /tmp/msg1
$ cat /tmp/msg1 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  08 00 00 02 00 00 0b 00  03 2e 00 00 03 2a 00 03  |.............*..|
00000010  25 30 82 03 21 30 82 02  09 a0 03 02 01 02 02 08  |58..!0..........|
00000020  15 5a 92 ad c2 04 8f 90  30 0d 06 09 2a 86 48 86  |.Z......0...*.H.|
00000000  0b 00 03 2e 00 00 03 2a  00 03 25 30 82 03 21 30  |.......*..58..!0|
00000010  82 02 09 a0 03 02 01 02  02 08 15 5a 92 ad c2 04  |...........Z....|
00000020  8f 90 30 0d 06 09 2a 86  48 86 f7 0d 01 01 0b 05  |..0...*.H.......|
00000030  00 30 22 31 0b 30 09 06  03 55 04 06 13 02 55 53  |.0"1.0...U....US|
00000040  31 13 30 11 06 03 55 04  0a 13 0a 45 78 61 6d 70  |1.0...U....Examp|
... snip ...
Server Certificate 0b 00 03 2e 00 00 03 2a 00 03 25 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 00 00
This handshake message is represented in its own section below.
Record Type 16
Each TLS 1.3 record disguised as TLS 1.2 application data has a final byte which indicates its actual record type.
  • 16 - type is 0x16 (handshake record)
Server Certificate
The server sends one or more certificates:
  • the certificate for this host, containing the hostname, a public key, and a signature from a third party asserting that the owner of the certificate's hostname holds the private key for this certificate
  • an optional list of further certificates, each of which signs the previous certificate, and which form a chain of trust leading from the host certificate to a trusted certificate that has been pre-installed on the client
In an effort to keep this example small we only send a host certificate. Certificates are in a binary format called DER which you can explore here.
Handshake Header 0b 00 03 2e
Each handshake message starts with a type and a length.
  • 0b - handshake message type 0x0B (certificate)
  • 00 03 2e - 0x32E (814) bytes of certificate payload follow
Request Context 00
This record is empty because this certificate was not sent in response to a Certificate Request.
  • 00 - 0 bytes of request context follows
Certificates Length 00 03 2a
  • 00 03 2a - 0x32A (810) bytes of certificates follow
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 ...
Certificate Extensions 00 00
The server can provide extension data for the certificate.
  • 00 00 - 0 bytes of extension data follows
Wrapped Record
The encryption of handshake data is new in TLS 1.3.

To reduce issues with middleboxes that block unrecognized TLS protocols, the encrypted handshake records are disguised as a TLS 1.2 session that has performed a successful session resume.

This wrapped record is discussed in its own section below this one.
Record Header 17 03 03 01 19
The TLS 1.3 records are encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 01 19 - 0x119 (281) bytes of wrapped data follows
Encrypted Data 73 71 9f ce 07 ec 2f 6d 3b ba 02 92 a0 d4 0b 27 70 c0 6a 27 17 99 a5 33 14 f6 f7 7f c9 5c 5f e7 b9 a4 32 9f d9 54 8c 67 0e be ea 2f 2d 5c 35 1d d9 35 6e f2 dc d5 2e b1 37 bd 3a 67 65 22 f8 cd 0f b7 56 07 89 ad 7b 0e 3c ab a2 e3 7e 6b 41 99 c6 79 3b 33 46 ed 46 cf 74 0a 9f a1 fe c4 14 dc 71 5c 41 5c 60 e5 75 70 3c e6 a3 4b 70 b5 19 1a a6 a6 1a 18 fa ff 21 6c 68 7a d8 d1 7e 12 a7 e9 99 15 a6 11 bf c1 a2 be fc 15 e6 e9 4d 78 46 42 e6 82 fd 17 38 2a 34 8c 30 10 56 b9 40 c9 84 72 00 40 8b ec 56 c8 1e a3 d7 21 7a b8 e8 5a 88 71 53 95 89 9c 90 58 7f 72 e8 dd d7 4b 26 d8 ed c1 c7 c8 37 d9 f2 eb bc 26 09 62 21 90 38 b0 56 54 a6 3a 0b 12 99 9b 4a 83 06 a3 dd cc 0e 17 c5 3b a8 f9 c8 03 63 f7 84 13 54 d2 91 b4 ac e0 c0 f3 30 c0 fc d5 aa 9d ee f9 69 ae 8a b2 d9 8d a8 8e bb 6e a8 0a 3a 11 f0 0e a2
This data is encrypted with the server handshake key.
Auth Tag 96 a3 23 23 67 ff 07 5e 1c 66 dd 9c be dc 47 13
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the server handshake key and the server handshake IV that were generated during the "Server Handshake Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 2. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Server Handshake Keys Calc" step
$ key=9f13575ce3f8cfc1df64a77ceaffe89700b492ad31b4fab01c4792be1b266b7f
$ iv=9563bc8b590f671f488d2da3
### from this record
$ recdata=1703030119
$ authtag=96a3232367ff075e1c66dd9cbedc4713
$ recordnum=2
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "73 71 9f ce 07 ec 2f 6d 3b ba 02 92 a0 d4 0b 27 70 c0 6a 27
  ... snip ...
  d9 8d a8 8e bb 6e a8 0a 3a 11 f0 0e a2" | xxd -r -p > /tmp/msg1
$ cat /tmp/msg1 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  0f 00 01 04 08 04 01 00  5c bb 24 c0 40 93 32 da  |........\.$.@.2.|
00000010  a9 20 bb ab bd b9 bd 50  17 0b e4 9c fb e0 a4 10  |. .....P........|
00000020  7f ca 6f fb 10 68 e6 5f  96 9e 6d e7 d4 f9 e5 60  |..o..h._..m....`|
... snip ...
Server Certificate Verify 0f 00 01 04 08 04 01 00 5c bb 24 c0 40 93 32 da a9 20 bb ab bd b9 bd 50 17 0b e4 9c fb e0 a4 10 7f ca 6f fb 10 68 e6 5f 96 9e 6d e7 d4 f9 e5 60 38 d6 7c 69 c0 31 40 3a 7a 7c 0b cc 86 83 e6 57 21 a0 c7 2c c6 63 40 19 ad 1d 3a d2 65 a8 12 61 5b a3 63 80 37 20 84 f5 da ec 7e 63 d3 f4 93 3f 27 22 74 19 a6 11 03 46 44 dc db c7 be 3e 74 ff ac 47 3f aa ad de 8c 2f c6 5f 32 65 77 3e 7e 62 de 33 86 1f a7 05 d1 9c 50 6e 89 6c 8d 82 f5 bc f3 5f ec e2 59 b7 15 38 11 5e 9c 8c fb a6 2e 49 bb 84 74 f5 85 87 b1 1b 8a e3 17 c6 33 e9 c7 6c 79 1d 46 62 84 ad 9c 4f f7 35 a6 d2 e9 63 b5 9b bc a4 40 a3 07 09 1a 1b 4e 46 bc c7 a2 f9 fb 2f 1c 89 8e cb 19 91 8b e4 12 1d 7e 8e d0 4c d5 0c 9a 59 e9 87 98 01 07 bb bf 29 9c 23 2e 7f db e1 0a 4c fd ae 5c 89 1c 96 af df f9 4b 54 cc d2 bc 19 d3 cd aa 66 44 85 9c
This handshake message is represented in its own section below.
Record Type 16
Each TLS 1.3 record disguised as TLS 1.2 application data has a final byte which indicates its actual record type.
  • 16 - type is 0x16 (handshake record)
Server Certificate Verify
The server provides information that ties the public key generated during Server Key Exchange Generation to the ownership of the certificate's private key.
Handshake Header 0f 00 01 04
Each handshake message starts with a type and a length.
  • 0f - handshake message type 0x0f (certificate verify)
  • 00 01 04 - 0x104 (260) bytes of handshake message data follows
Signature 08 04 01 00 5c bb 24 c0 40 93 32 da a9 20 bb ab bd b9 bd 50 17 0b e4 9c fb e0 a4 10 7f ca 6f fb 10 68 e6 5f 96 9e 6d e7 d4 f9 e5 60 38 d6 7c 69 c0 31 40 3a 7a 7c 0b cc 86 83 e6 57 21 a0 c7 2c c6 63 40 19 ad 1d 3a d2 65 a8 12 61 5b a3 63 80 37 20 84 f5 da ec 7e 63 d3 f4 93 3f 27 22 74 19 a6 11 03 46 44 dc db c7 be 3e 74 ff ac 47 3f aa ad de 8c 2f c6 5f 32 65 77 3e 7e 62 de 33 86 1f a7 05 d1 9c 50 6e 89 6c 8d 82 f5 bc f3 5f ec e2 59 b7 15 38 11 5e 9c 8c fb a6 2e 49 bb 84 74 f5 85 87 b1 1b 8a e3 17 c6 33 e9 c7 6c 79 1d 46 62 84 ad 9c 4f f7 35 a6 d2 e9 63 b5 9b bc a4 40 a3 07 09 1a 1b 4e 46 bc c7 a2 f9 fb 2f 1c 89 8e cb 19 91 8b e4 12 1d 7e 8e d0 4c d5 0c 9a 59 e9 87 98 01 07 bb bf 29 9c 23 2e 7f db e1 0a 4c fd ae 5c 89 1c 96 af df f9 4b 54 cc d2 bc 19 d3 cd aa 66 44 85 9c
Because the server is generating ephemeral keys for each session (optional in TLS 1.2, mandatory in TLS 1.3) the session is not inherently tied to the certificate as it was in previous versions of TLS, when the certificate's public/private key were used for key exchange.

To prove that the server owns the server certificate (giving the certificate validity in this TLS session), it signs a hash of the handshake messages using the private key associated with the certificate. The signature can be proven valid by the client by using the public key included in the certificate.
  • 08 04 - reserved value for RSA-PSS-RSAE-SHA256 signature
  • 01 00 - 0x100 (256) bytes of signature data follows
  • 5c bb 24 ... 44 85 9c - a signature over this handshake's hash
The signing process can't be reproduced byte-for-byte at the command line because the signing tool introduces random or changing data into the signature.

We can verify the signature using the server's certificate at the command line:
### find the hash of the conversation to this point, excluding
### 5-byte record headers or 1-byte wrapped record trailers
$ handshake_hash=$((
   tail -c +6 clienthello;
   tail -c +6 serverhello;
   perl -pe 's/.$// if eof' serverextensions;
   perl -pe 's/.$// if eof' servercert) | openssl sha384)

### build the data that was signed:
### 1. add 64 space characters
$ echo -n '                                ' > /tmp/tosign
$ echo -n '                                ' >> /tmp/tosign
### 2. add this fixed string
$ echo -n 'TLS 1.3, server CertificateVerify' >> /tmp/tosign
### 3. add a single null character
$ echo -en '\0' >> /tmp/tosign
### 4. add hash of handshake to this point
$ echo $handshake_hash | xxd -r -p >> /tmp/tosign

### copy the signature that we want to verify
$ echo "5c bb 24 c0 40 93 32 da a9 20 bb ab bd b9 bd 50 17 0b e4 9c
  fb e0 a4 10 7f ca 6f fb 10 68 e6 5f 96 9e 6d e7 d4 f9 e5 60 38 d6
  7c 69 c0 31 40 3a 7a 7c 0b cc 86 83 e6 57 21 a0 c7 2c c6 63 40 19
  ad 1d 3a d2 65 a8 12 61 5b a3 63 80 37 20 84 f5 da ec 7e 63 d3 f4
  93 3f 27 22 74 19 a6 11 03 46 44 dc db c7 be 3e 74 ff ac 47 3f aa
  ad de 8c 2f c6 5f 32 65 77 3e 7e 62 de 33 86 1f a7 05 d1 9c 50 6e
  89 6c 8d 82 f5 bc f3 5f ec e2 59 b7 15 38 11 5e 9c 8c fb a6 2e 49
  bb 84 74 f5 85 87 b1 1b 8a e3 17 c6 33 e9 c7 6c 79 1d 46 62 84 ad
  9c 4f f7 35 a6 d2 e9 63 b5 9b bc a4 40 a3 07 09 1a 1b 4e 46 bc c7
  a2 f9 fb 2f 1c 89 8e cb 19 91 8b e4 12 1d 7e 8e d0 4c d5 0c 9a 59
  e9 87 98 01 07 bb bf 29 9c 23 2e 7f db e1 0a 4c fd ae 5c 89 1c 96
  af df f9 4b 54 cc d2 bc 19 d3 cd aa 66 44 85 9c" | xxd -r -p > /tmp/sig

### extract the public key from the certificate
$ openssl x509 -pubkey -noout -in server.crt > server.pub

### verify the signature
$ cat /tmp/tosign | openssl dgst -verify server.pub -sha256 \
    -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -signature /tmp/sig

Verified OK
Wrapped Record
The encryption of handshake data is new in TLS 1.3.

To reduce issues with middleboxes that block unrecognized TLS protocols, the encrypted handshake records are disguised as a TLS 1.2 session that has performed a successful session resume.

This wrapped record is discussed in its own section below this one.
Record Header 17 03 03 00 45
The TLS 1.3 records are encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 45 - 0x45 (69) bytes of wrapped data follows
Encrypted Data 10 61 de 27 e5 1c 2c 9f 34 29 11 80 6f 28 2b 71 0c 10 63 2c a5 00 67 55 88 0d bf 70 06 00 2d 0e 84 fe d9 ad f2 7a 43 b5 19 23 03 e4 df 5c 28 5d 58 e3 c7 62 24
This data is encrypted with the server handshake key.
Auth Tag 07 84 40 c0 74 23 74 74 4a ec f2 8c f3 18 2f d0
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the server handshake key and the server handshake IV that were generated during the "Server Handshake Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 3. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Server Handshake Keys Calc" step
$ key=9f13575ce3f8cfc1df64a77ceaffe89700b492ad31b4fab01c4792be1b266b7f
$ iv=9563bc8b590f671f488d2da3
### from this record
$ recdata=1703030045
$ authtag=078440c0742374744aecf28cf3182fd0
$ recordnum=3
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "10 61 de 27 e5 1c 2c 9f 34 29 11 80 6f 28 2b 71 0c 10 63 2c a5 00 67 55 88 0d bf 70 06 00 2d 0e 84 fe d9 ad f2 7a 43 b5 19
  23 03 e4 df 5c 28 5d 58 e3 c7 62 24" | xxd -r -p > /tmp/msg1
$ cat /tmp/msg1 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  14 00 00 30 7e 30 ee cc  b6 b2 3b e6 c6 ca 36 39  |...0~0....;...69|
00000010  92 e8 42 da 87 7e e6 47  15 ae 7f c0 cf 87 f9 e5  |..B..~.G........|
00000020  03 21 82 b5 bb 48 d1 e3  3f 99 79 05 5a 16 0c 8d  |.!...H..?.y.Z...|
00000030  bb b1 56 9c 16                                    |..V..|
Server Finished 14 00 00 30 7e 30 ee cc b6 b2 3b e6 c6 ca 36 39 92 e8 42 da 87 7e e6 47 15 ae 7f c0 cf 87 f9 e5 03 21 82 b5 bb 48 d1 e3 3f 99 79 05 5a 16 0c 8d bb b1 56 9c
This handshake message is represented in its own section below.
Record Type 16
Each TLS 1.3 record disguised as TLS 1.2 application data has a final byte which indicates its actual record type.
  • 16 - type is 0x16 (handshake record)
Server Handshake Finished
To verify that the handshake was successful and not tampered with, the server calculates verification data that client will agree on. The verification data is built from a hash of all handshake messages.
Handshake Header 14 00 00 30
Each handshake message starts with a type and a length.
  • 14 - handshake message type 0x14 (finished)
  • 00 00 30 - 0x30 (48) bytes of handshake finished data follows
Verify Data 7e 30 ee cc b6 b2 3b e6 c6 ca 36 39 92 e8 42 da 87 7e e6 47 15 ae 7f c0 cf 87 f9 e5 03 21 82 b5 bb 48 d1 e3 3f 99 79 05 5a 16 0c 8d bb b1 56 9c
The verify_data is built using the server_secret from the "Server Handshake Keys Calc" step and a SHA384 hash of every handshake record before this point (Client Hello to Server Certificate Verify).
finished_key = HKDF-Expand-Label(key: server_secret, label: "finished", ctx: "", len: 48)
finished_hash = SHA384(Client Hello ... Server Cert Verify)
verify_data = HMAC-SHA384(key: finished_key, msg: finished_hash)
We can use the HKDF tool to reproduce this on the command line.
### find the hash of the conversation to this point, excluding
### 5-byte record headers or 1-byte wrapped record trailers
$ fin_hash=$((
    tail -c +6 clienthello;
    tail -c +6 serverhello;
    perl -pe 's/.$// if eof' serverextensions;
    perl -pe 's/.$// if eof' servercert;
    perl -pe 's/.$// if eof' servercertverify) | openssl sha384)
$ sht_secret=23323da031634b241dd37d61032b62a4f450584d1f7f47983ba2f7cc0cdcc39a68f481f2b019f9403a3051908a5d1622
$ fin_key=$(./hkdf-384 expandlabel $sht_secret "finished" "" 48)
$ echo $fin_hash | xxd -r -p \
    | openssl dgst -sha384 -mac HMAC -macopt hexkey:$fin_key

7e30eeccb6b23be6c6ca363992e842da877ee64715ae7fc0cf87f9e5032182b5bb48d1e33f9979055a160c8dbbb1569c
Server Application Keys Calc
The server now has the information to calculate the keys used to encrypt application traffic. It uses the following information in this calculation:
  • The handshake secret (from "Server Handshake Key Calc")
  • The SHA384 hash of every handshake message from Client Hello to Server Handshake Finished
We calculate the SHA384 hash of all handshake messages to this point(Client Hello, Server Hello, [unwrapped] Encrypted Extensions, [unwrapped] Server Certificate, [unwrapped] Server Certificate Verify, [unwrapped] Server Finished). The hash input does not include the 5-byte "record" headers of ClientHello and ServerHello. It also doesn't include the trailing 1-byte "record type" of unwrapped records. This "handshake_hash" is fa6800169a6baac19159524fa7b9721b41be3c9db6f3f93fa5ff7e3db3ece204d2b456c51046e40ec5312c55a86126f5:
# strip first 5 bytes of hello records, and trailing byte of unwrapped records
$ (tail -c +6 clienthello; tail -c +6 serverhello; \
   perl -pe 's/.$// if eof' serverextensions; \
   perl -pe 's/.$// if eof' servercert; \
   perl -pe 's/.$// if eof' servercertverify; \
   perl -pe 's/.$// if eof' serverfinished) | openssl sha384

fa6800169a6baac19159524fa7b9721b41be3c9db6f3f93fa5ff7e3db3ece204d2b456c51046e40ec5312c55a86126f5
    
We then feed the hash and the handshake secret into a set of key derivation operations, designed to ensure the integrity of the handshake process and to protect against known and possible attacks:
empty_hash = SHA384("")
derived_secret = HKDF-Expand-Label(key: handshake_secret, label: "derived", ctx: empty_hash, len: 48)
master_secret = HKDF-Extract(salt: derived_secret, key: 00...)
client_secret = HKDF-Expand-Label(key: master_secret, label: "c ap traffic", ctx: handshake_hash, len: 48)
server_secret = HKDF-Expand-Label(key: master_secret, label: "s ap traffic", ctx: handshake_hash, len: 48)
client_application_key = HKDF-Expand-Label(key: client_secret, label: "key", ctx: "", len: 32)
server_application_key = HKDF-Expand-Label(key: server_secret, label: "key", ctx: "", len: 32)
client_application_iv = HKDF-Expand-Label(key: client_secret, label: "iv", ctx: "", len: 12)
server_application_iv = HKDF-Expand-Label(key: server_secret, label: "iv", ctx: "", len: 12)
I've created an HKDF tool to perform these operations on the command line.
$ handshake_hash=fa6800169a6baac19159524fa7b9721b41be3c9db6f3f93fa5ff7e3db3ece204d2b456c51046e40ec5312c55a86126f5
$ handshake_secret=bdbbe8757494bef20de932598294ea65b5e6bf6dc5c02a960a2de2eaa9b07c929078d2caa0936231c38d1725f179d299
$ zero_key=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
$ empty_hash=$(openssl sha384 < /dev/null | sed -e 's/.* //')
$ derived_secret=$(./hkdf-384 expandlabel $handshake_secret "derived" $empty_hash 48)
$ master_secret=$(./hkdf-384 extract $derived_secret $zero_key)
$ csecret=$(./hkdf-384 expandlabel $master_secret "c ap traffic" $handshake_hash 48)
$ ssecret=$(./hkdf-384 expandlabel $master_secret "s ap traffic" $handshake_hash 48)
$ client_application_key=$(./hkdf-384 expandlabel $csecret "key" "" 32)
$ server_application_key=$(./hkdf-384 expandlabel $ssecret "key" "" 32)
$ client_application_iv=$(./hkdf-384 expandlabel $csecret "iv" "" 12)
$ server_application_iv=$(./hkdf-384 expandlabel $ssecret "iv" "" 12)
$ echo skey: $server_application_key
$ echo siv: $server_application_iv
$ echo ckey: $client_application_key
$ echo civ: $client_application_iv

skey: 01f78623f17e3edcc09e944027ba3218d57c8e0db93cd3ac419309274700ac27
siv: 196a750b0c5049c0cc51a541
ckey: de2f4c7672723a692319873e5c227606691a32d1c59d8b9f51dbb9352e9ca9cc
civ: bb007956f474b25de902432f
From this we get the following key data:
  • server application key: 01f78623f17e3edcc09e944027ba3218d57c8e0db93cd3ac419309274700ac27
  • server application IV: 196a750b0c5049c0cc51a541
  • client application key: de2f4c7672723a692319873e5c227606691a32d1c59d8b9f51dbb9352e9ca9cc
  • client application IV: bb007956f474b25de902432f
Client Application Keys Calc
The client now has the information to calculate the keys used to encrypt application traffic. It performs the same calculation shown in "Server Application Keys Calc" and finds the same values:
  • server application key: 01f78623f17e3edcc09e944027ba3218d57c8e0db93cd3ac419309274700ac27
  • server application IV: 196a750b0c5049c0cc51a541
  • client application key: de2f4c7672723a692319873e5c227606691a32d1c59d8b9f51dbb9352e9ca9cc
  • client application IV: bb007956f474b25de902432f
Client Change Cipher Spec
This record served a purpose in earlier versions on TLS but is no longer needed. In "middlebox compatibility mode" this record is sent to help disguise the session as a TLS 1.2 session.
Record Header 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 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 01 - the length of the record payload is 1 bytes
  • 01 - the payload of this message is defined as the byte 0x01
Wrapped Record
To reduce issues with middleboxes that block unrecognized TLS protocols, TLS 1.3 records are disguised as TLS 1.2 application data records.

The wrapped record is discussed in its own section below this one.
Record Header 17 03 03 00 45
The TLS 1.3 record is encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 45 - 0x45 (69) bytes of wrapped data follows
Encrypted Data 9f f9 b0 63 17 51 77 32 2a 46 dd 98 96 f3 c3 bb 82 0a b5 17 43 eb c2 5f da dd 53 45 4b 73 de b5 4c c7 24 8d 41 1a 18 bc cf 65 7a 96 08 24 e9 a1 93 64 83 7c 35
This data is encrypted with the client handshake key.
Auth Tag 0a 69 a8 8d 4b f6 35 c8 5e b8 74 ae bc 9d fd e8
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the client handshake key and the client handshake IV that were generated during the "Client Handshake Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 0. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Client Handshake Keys Calc" step
$ key=1135b4826a9a70257e5a391ad93093dfd7c4214812f493b3e3daae1eb2b1ac69
$ iv=4256d2e0e88babdd05eb2f27
### from this record
$ recdata=1703030045
$ authtag=0a69a88d4bf635c85eb874aebc9dfde8
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "9f f9 b0 63 17 51 77 32 2a 46 dd 98 96 f3 c3 bb 82 0a b5
  17 43 eb c2 5f da dd 53 45 4b 73 de b5 4c c7 24 8d 41 1a 18 bc
  cf 65 7a 96 08 24 e9 a1 93 64 83 7c 35" | xxd -r -p > /tmp/msg2
$ cat /tmp/msg2 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  14 00 00 30 bf f5 6a 67  1b 6c 65 9d 0a 7c 5d d1  |...0..jg.le..|].|
00000010  84 28 f5 8b dd 38 b1 84  a3 ce 34 2d 9f de 95 cb  |.(...8....4-....|
00000020  d5 05 6f 7d a7 91 8e e3  20 ea b7 a9 3a bd 8f 1c  |..o}.... ...:...|
00000030  02 45 4d 27 16                                    |.EM'.|
Client Handshake Finished 14 00 00 30 bf f5 6a 67 1b 6c 65 9d 0a 7c 5d d1 84 28 f5 8b dd 38 b1 84 a3 ce 34 2d 9f de 95 cb d5 05 6f 7d a7 91 8e e3 20 ea b7 a9 3a bd 8f 1c 02 45 4d 27
This handshake message is represented in its own section below.
Record Type 16
Each TLS 1.3 record disguised as TLS 1.2 application data has a final non-zero byte which indicates its actual record type.
  • 16 - type is 0x16 (handshake record)
Client Handshake Finished
To verify that the handshake was successful and not tampered with, the client calculates verification data that the server will agree on, and encrypts it with the client handshake key. The verification data is built from a hash of all handshake messages.
Handshake Header 14 00 00 30
Each handshake message starts with a type and a length.
  • 14 - handshake message type 0x14 (finished)
  • 00 00 30 - 0x30 (48) bytes of handshake finished data follow
Verify Data bf f5 6a 67 1b 6c 65 9d 0a 7c 5d d1 84 28 f5 8b dd 38 b1 84 a3 ce 34 2d 9f de 95 cb d5 05 6f 7d a7 91 8e e3 20 ea b7 a9 3a bd 8f 1c 02 45 4d 27
The verify_data is built using the client_secret from the "Server Handshake Keys Calc" step and a SHA384 hash of every handshake record before this point (Client Hello to Server Finished).
finished_key = HKDF-Expand-Label(key: client_secret, label: "finished", ctx: "", len: 48)
finished_hash = SHA384(Client Hello ... Server Finished)
verify_data = HMAC-SHA384(key: finished_key, msg: finished_hash)
We can use the HKDF tool to reproduce this on the command line.
### find the hash of the conversation to this point, excluding
### 5-byte record headers or 1-byte wrapped record trailers
$ fin_hash=$((
    tail -c +6 clienthello;
    tail -c +6 serverhello;
    perl -pe 's/.$// if eof' serverextensions;
    perl -pe 's/.$// if eof' servercert;
    perl -pe 's/.$// if eof' servercertverify;
    perl -pe 's/.$// if eof' serverfinished) | openssl sha384)
$ cht_secret=db89d2d6df0e84fed74a2288f8fd4d0959f790ff23946cdf4c26d85e51bebd42ae184501972f8d30c4a3e4a3693d0ef0
$ fin_key=$(./hkdf-384 expandlabel $cht_secret "finished" "" 48)
$ echo $fin_hash | xxd -r -p \
    | openssl dgst -sha384 -mac HMAC -macopt hexkey:$fin_key

bff56a671b6c659d0a7c5dd18428f58bdd38b184a3ce342d9fde95cbd5056f7da7918ee320eab7a93abd8f1c02454d27
Wrapped Record
To reduce issues with middleboxes that block unrecognized TLS protocols, TLS 1.3 records are disguised as TLS 1.2 application data records.

The wrapped record is discussed in its own section below this one.
Record Header 17 03 03 00 15
The TLS 1.3 record is encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 15 - the length of the record payload is 0x15 (21) bytes
All data following this header is the encrypted form of the actual record.
Encrypted Data 82 81 39 cb 7b
This data is encrypted with the client application key.

See below for the decrypted data.
Auth Tag 73 aa ab f5 b8 2f bf 9a 29 61 bc de 10 03 8a 32
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the client application key and the client application IV that were generated during the "Client Application Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 0. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Client Application Keys Calc" step
$ key=de2f4c7672723a692319873e5c227606691a32d1c59d8b9f51dbb9352e9ca9cc
$ iv=bb007956f474b25de902432f
### from this record
$ recdata=1703030015
$ authtag=73aaabf5b82fbf9a2961bcde10038a32
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "82 81 39 cb 7b" | xxd -r -p > /tmp/msg3
$ cat /tmp/msg3 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  70 69 6e 67 17                                    |ping.|
Client Application Data 70 69 6e 67
This application data is represented in its own section below.
Record Type 17
Each TLS 1.3 record disguised as TLS 1.2 application data has a final byte which indicates its actual record type.
  • 17 - type is 0x17 (application data)
Client Application Data
The client sends the data "ping".
Application Data 70 69 6e 67
The bytes "ping".
Wrapped Record
To reduce issues with middleboxes that block unrecognized TLS protocols, TLS 1.3 records are disguised as TLS 1.2 application data records.

The wrapped records are discussed in their own sections below this one.
Record Header 17 03 03 00 ea
The TLS 1.3 record is encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 ea - the length of the record payload is 0xEA (234) bytes
All data following this header is the encrypted form of the actual record.
Encrypted Data 38 2d 8c 19 a4 7f 4e 8d 9b 0c 51 0b c3 48 db 2c c9 9b 24 1c d0 d1 8b 31 d0 ca 1a c1 2d c1 e3 03 c5 8d 0c 7e 9e 27 29 4c 6b 0e 31 98 f7 d3 19 eb 14 62 2e c4 8b 6a c8 f8 66 d7 49 4f a7 75 c8 80 ff 43 ad 4b 1a f5 3a 03 ca 19 77 95 77 8f ff 2f fe 1d 3b 99 b3 4d e7 82 a7 6a bf a8 40 e6 36 6c d7 34 9d 9b cf f6 41 f5 e0 df f9 5e 40 d7 2e 09 ef fe 18 ee 64 67 2c b9 60 05 40 44 88 ad 18 96 c4 4a 5f d1 74 99 8e 9b 00 94 d8 e6 d8 4d 29 29 b7 88 3d c9 a3 c3 c7 31 3a 87 29 3f 31 b6 1d 24 d9 90 97 c8 85 3b fb eb 95 d1 d0 1f 99 ca 05 b0 50 18 59 cf 63 40 e8 37 70 75 97 01 52 fa 94 f5 f5 be 29 06 e7 2a 15 e4 08 36 a4 1f 4c d3 db e7 d5 13 c1 6e 88 61 1d 3e ae 93
This data is encrypted with the server application key.

See below for the decrypted data.
Auth Tag 38 d9 db 1f 91 ca 3d 58 42 60 2a 61 0b 43 a4 63
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the server application key and the server application IV that were generated during the "Server Application Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 0. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Server Application Keys Calc" step
$ key=01f78623f17e3edcc09e944027ba3218d57c8e0db93cd3ac419309274700ac27
$ iv=196a750b0c5049c0cc51a541
### from this record
$ recdata=17030300ea
$ authtag=38d9db1f91ca3d5842602a610b43a463
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "38 2d 8c 19 a4 7f 4e 8d 9b 0c 51 0b c3 48 db 2c c9 9b 24
  ... snip ...
  13 c1 6e 88 61 1d 3e ae 93" | xxd -r -p > /tmp/msg5
$ cat /tmp/msg5 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  04 00 00 d5 00 00 1c 20  00 00 00 00 08 00 00 00  |....... ........|
00000010  00 00 00 00 00 00 c0 41  42 43 44 45 46 47 48 49  |.......ABCDEFGHI|
... snip ...
Server New Session Ticket 1 04 00 00 d5 00 00 1c 20 00 00 00 00 08 00 00 00 00 00 00 00 00 00 c0 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 49 56 44 41 54 41 49 56 44 41 54 41 00 41 45 53 cb 11 9d 4d bd 2a 21 ec c2 26 a6 09 0e e8 ca 58 df 09 03 9b 35 96 f4 de 79 98 0e a3 25 d5 14 62 5c 0c 21 c5 0f 03 26 1d c4 2c e7 c5 97 0c 4c 01 ea 33 1c ff c8 99 66 ef 54 8b e4 df 9a 8b a4 38 5b eb 86 80 fd 0b 78 df b8 e9 8e fc 8f cc d8 14 fe cd 1d 9b ce 89 ca 05 dc 28 c2 49 e5 bd 61 d0 3a 56 8f 9a 0a 46 fb fd 05 30 2d b6 b2 f7 a3 13 e3 32 67 bf 0b cb dc ec fb 04 a4 d8 2f 5a 69 45 1f 56 7a b5 19 9b b2 6c 5c f2 00 72 f0 45 03 73 02 8f e0 71 d4 f4 1d 8f 61 ae 02 4d 69 bb ae 4c 00 00
This handshake message is represented in its own section below.
Record Type 16
Each TLS 1.3 record disguised as TLS 1.2 application data has a final byte which indicates its actual record type.
  • 16 - type is 0x16 (handshake data)
Server New Session Ticket 1
The server provides a session ticket that the client can use to start a new session later. Successfully resuming a connection in this way will skip most of the computation and network delay in session startup.

Because each session ticket is meant to be single-use, and because the server expects a browser to open multiple connections, it makes a size vs. speed decision to provide the client with two session tickets for each negotiated session. This is the first ticket.
Handshake Header 04 00 00 d5
Each handshake message starts with a type and a length.
  • 04 - handshake message type 0x04 (new session ticket)
  • 00 00 d5 - 0xD5 (213) bytes of session ticket data follow
Ticket Lifetime 00 00 1c 20
The ticket lifetime of 0x1C20 (7200) seconds, or 2 hours.
Ticket Age Add 00 00 00 00
When sending this ticket back to the server, it must add this number of milliseconds to the timestamp indicating when the ticket was generated. This prevents attackers from correlating the resumed session with the session that generated this ticket.
Ticket Nonce 08 00 00 00 00 00 00 00 00
A per-ticket value that is unique to each ticket generated during this session.
  • 08 - 8 bytes of nonce data follows
  • 00 00 00 00 00 00 00 - nonce value
Session Ticket 00 c0 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 49 56 44 41 54 41 49 56 44 41 54 41 00 41 45 53 cb 11 9d 4d bd 2a 21 ec c2 26 a6 09 0e e8 ca 58 df 09 03 9b 35 96 f4 de 79 98 0e a3 25 d5 14 62 5c 0c 21 c5 0f 03 26 1d c4 2c e7 c5 97 0c 4c 01 ea 33 1c ff c8 99 66 ef 54 8b e4 df 9a 8b a4 38 5b eb 86 80 fd 0b 78 df b8 e9 8e fc 8f cc d8 14 fe cd 1d 9b ce 89 ca 05 dc 28 c2 49 e5 bd 61 d0 3a 56 8f 9a 0a 46 fb fd 05 30 2d b6 b2 f7 a3 13 e3 32 67 bf 0b cb dc ec fb 04 a4 d8 2f 5a 69 45 1f 56 7a b5 19 9b b2 6c 5c f2 00 72 f0 45 03 73 02 8f e0 71 d4 f4 1d 8f 61 ae 02 4d 69 bb ae 4c
This is the ticket that can be sent to the server to resume a session. The data inside is meaningful to the server, and may contain enough information for the server to safely resume the connection without storing any information on the server (such as in memory). This information is not meaningful or understandable to the client.
  • 00 c0 - 0xC0 (192) bytes of ticket data follows
  • 41 42 ... ae 4c - session ticket
Ticket Extensions 00 00
The server provides extensions to provide more information about the ticket or to request a behavioral change from the client.

  • 00 00 - 0 bytes of "Ticket Extensions" extension data follows
Wrapped Record
To reduce issues with middleboxes that block unrecognized TLS protocols, TLS 1.3 records are disguised as TLS 1.2 application data records.

The wrapped records are discussed in their own sections below this one.
Record Header 17 03 03 00 ea
The TLS 1.3 record is encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 ea - the length of the record payload is 0xEA (234) bytes
All data following this header is the encrypted form of the actual record.
Encrypted Data 38 ad fb 1d 01 fd 95 a6 03 85 e8 bb f1 fd 8d cb 46 70 98 97 e7 d6 74 c2 f7 37 0e c1 1d 8e 33 eb 4f 4f e7 f5 4b f4 dc 0b 92 fa e7 42 1c 33 c6 45 3c eb c0 73 15 96 10 a0 97 40 ab 2d 05 6f 8d 51 cf a2 62 00 7d 40 12 36 da fc 2f 72 92 ff 0c c8 86 a4 ef 38 9f 2c ed 12 26 c6 b4 dc f6 9d 99 4f f9 14 8e f9 69 bc 77 d9 43 3a b1 d3 a9 32 54 21 82 82 9f 88 9a d9 5f 04 c7 52 f9 4a ce 57 14 6a 5d 84 b0 42 bf b3 48 5a 64 e7 e9 57 b0 89 80 cd 08 ba f9 69 8b 89 29 98 6d 11 74 d4 aa 6d d7 a7 e8 c0 86 05 2c 3c 76 d8 19 34 bd f5 9b 96 6e 39 20 31 f3 47 1a de bd dd db e8 4f cf 1f f4 08 84 6a e9 b2 8c a4 a9 e7 28 84 4a 49 3d 80 45 5d 6e af f2 05 b4 0a 1e f1 85 74 ef
This data is encrypted with the server application key.

See below for the decrypted data.
Auth Tag c0 b9 6a d3 83 af bd 8d fc 86 f8 08 7c 1f 7d c8
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the server application key and the server application IV that were generated during the "Server Application Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 1. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Server Application Keys Calc" step
$ key=01f78623f17e3edcc09e944027ba3218d57c8e0db93cd3ac419309274700ac27
$ iv=196a750b0c5049c0cc51a541
### from this record
$ recdata=17030300ea
$ authtag=c0b96ad383afbd8dfc86f8087c1f7dc8
$ recordnum=1
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "38 ad fb 1d 01 fd 95 a6 03 85 e8 bb f1 fd 8d cb 46 70
  ... snip ...
  b4 0a 1e f1 85 74 ef" | xxd -r -p > /tmp/msg5
$ cat /tmp/msg5 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  04 00 00 d5 00 00 1c 20  00 00 00 00 08 00 00 00  |....... ........|
00000010  00 00 00 00 01 00 c0 41  42 43 44 45 46 47 48 49  |.......ABCDEFGHI|
... snip ...
Server New Session Ticket 2 04 00 00 d5 00 00 1c 20 00 00 00 00 08 00 00 00 00 00 00 00 01 00 c0 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 49 56 44 41 54 41 49 56 44 41 54 41 00 41 45 53 cb 11 9d 4d bd 2a 21 ec c2 26 a6 09 0e e8 ca 58 df 09 03 9b 35 96 f4 de 79 98 0e a3 25 d5 14 62 5c 0c 21 c5 0f 03 26 1d c4 2c e7 c5 97 0c 4c 01 16 06 fb 99 8a 86 c3 fa 30 e5 5e ea 91 f1 ff f3 18 fc 7b d5 88 31 bf 49 c8 8d 7b 59 05 91 a6 5c 7d e8 cf c6 77 46 8a 54 fd be c0 d8 53 be 20 21 c8 bb fc db e5 1f 5d 9a 0c 70 85 84 1a 01 e4 95 85 f6 8b 4a fe e1 d7 07 e2 cb b1 a0 b4 23 aa 7e 32 d5 60 7b d9 9d d4 db 3c 9a aa ed 43 d3 5d 26 b4 b1 c6 84 71 71 ea a0 7a 9b c8 cb f7 58 49 9a 00 00
This handshake message is represented in its own section below.
Record Type 16
Each TLS 1.3 record disguised as TLS 1.2 application data has a final byte which indicates its actual record type.
  • 16 - type is 0x16 (handshake data)
Server New Session Ticket 2
The server provides a session ticket that the client can use to start a new session later. Successfully resuming a connection in this way will skip most of the computation and network delay in session startup.

Because each session ticket is meant to be single-use, and because the server expects a browser to open multiple connections, it makes a size vs. speed decision to provide the client with two session tickets for each negotiated session. This is the second ticket.
Handshake Header 04 00 00 d5
Each handshake message starts with a type and a length.
  • 04 - handshake message type 0x04 (new session ticket)
  • 00 00 d5 - 0xD5 (213) bytes of session ticket data follow
Ticket Lifetime 00 00 1c 20
The ticket lifetime of 0x1C20 (7200) seconds, or 2 hours.
Ticket Age Add 00 00 00 00
When sending this ticket back to the server, it must add this number of milliseconds to the timestamp indicating when the ticket was generated. This prevents attackers from correlating the resumed session with the session that generated this ticket.
Ticket Nonce 08 00 00 00 00 00 00 00 01
A per-ticket value that is unique to each ticket generated during this session.
  • 08 - 8 bytes of nonce data follows
  • 00 00 00 00 00 00 01 - nonce value
Session Ticket 00 c0 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 49 56 44 41 54 41 49 56 44 41 54 41 00 41 45 53 cb 11 9d 4d bd 2a 21 ec c2 26 a6 09 0e e8 ca 58 df 09 03 9b 35 96 f4 de 79 98 0e a3 25 d5 14 62 5c 0c 21 c5 0f 03 26 1d c4 2c e7 c5 97 0c 4c 01 16 06 fb 99 8a 86 c3 fa 30 e5 5e ea 91 f1 ff f3 18 fc 7b d5 88 31 bf 49 c8 8d 7b 59 05 91 a6 5c 7d e8 cf c6 77 46 8a 54 fd be c0 d8 53 be 20 21 c8 bb fc db e5 1f 5d 9a 0c 70 85 84 1a 01 e4 95 85 f6 8b 4a fe e1 d7 07 e2 cb b1 a0 b4 23 aa 7e 32 d5 60 7b d9 9d d4 db 3c 9a aa ed 43 d3 5d 26 b4 b1 c6 84 71 71 ea a0 7a 9b c8 cb f7 58 49 9a
This is the ticket that can be sent to the server to resume a session. The data inside is meaningful to the server, and may contain enough information for the server to safely resume the connection without storing any information on the server (such as in memory). This information is not meaningful or understandable to the client.
  • 00 c0 - 0xC0 (192) bytes of ticket data follows
  • 41 42 ... 49 9a - session ticket
Ticket Extensions 00 00
The server provides extensions to provide more information about the ticket or to request a behavioral change from the client.

  • 00 00 - 0 bytes of "Ticket Extensions" extension data follows
Wrapped Record
To reduce issues with middleboxes that block unrecognized TLS protocols, TLS 1.3 records are disguised as TLS 1.2 application data records.

The wrapped record is discussed in its own section below this one.
Record Header 17 03 03 00 15
The TLS 1.3 record is encrypted into a TLS 1.2 record "wrapper" that looks like application data.
  • 17 - type is 0x17 (application data)
  • 03 03 - legacy protocol version of "3,3" (TLS 1.2)
  • 00 15 - the length of the record payload is 0x15 (21) bytes
All data following this header is the encrypted form of the actual record.
Encrypted Data 0c da 85 f1 44
This data is encrypted with the server application key.

See below for the decrypted data.
Auth Tag 7a e2 3f a6 6d 56 f4 c5 40 84 82 b1 b1 d4 c9 98
This is the AEAD authentication tag that protects the integrity of the encrypted data and the record header.
Decryption
This data is encrypted using the server application key and the server application IV that were generated during the "Server Application Keys Calc" step. The IV will be modified by XOR'ing it by the count of records that have already been encrypted with this key, which in this case is 2. The process also takes as input the 5-byte record header that this record begins with, as authenticated data that must match for the decryption to succeed.

Because the openssl command line tool does not yet support AEAD ciphers, I've written command line tools to both decrypt and encrypt this data.
### from the "Server Application Keys Calc" step
$ key=01f78623f17e3edcc09e944027ba3218d57c8e0db93cd3ac419309274700ac27
$ iv=196a750b0c5049c0cc51a541
### from this record
$ recdata=1703030015
$ authtag=7ae23fa66d56f4c5408482b1b1d4c998
$ recordnum=2
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_256_gcm_decrypt aes_256_gcm_decrypt.c -lssl -lcrypto
$ echo "0c da 85 f1 44" | xxd -r -p > /tmp/msg4
$ cat /tmp/msg4 \
  | ./aes_256_gcm_decrypt $iv $recordnum $key $recdata $authtag \
  | hexdump -C

00000000  70 6f 6e 67 17                                    |pong.|
Server Application Data 70 6f 6e 67
This application data is represented in its own section below.
Record Type 17
Each TLS 1.3 record disguised as TLS 1.2 application data has a final byte which indicates its actual record type.
  • 17 - type is 0x17 (application data)
Server Application Data
The server replies with the data "pong".
Application Data 70 6f 6e 67
The bytes "pong".

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

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

[print]