In this demonstration a client connects to a server, negotiates a QUIC connection with TLS encryption, sends "ping", receives "pong", then terminates the connection. Click below to begin exploring.
Also available in 中文
The connection begins with the client 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:
The public key is created from the private key as explained on the X25519 site. The public key calculated is:202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
The public key calculation can be confirmed at the command line:358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254
### 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
The client then derives encryption keys using the following process:0001020304050607
initial_salt = 38762cf7f55934b34d179ae6a4c80cadccbb7f0a initial_random = (random bytes from client given above) initial_secret = HKDF-Extract(salt: initial_salt, key: initial_random) client_secret = HKDF-Expand-Label(key: initial_secret, label: "client in", ctx: "", len: 32) server_secret = HKDF-Expand-Label(key: initial_secret, label: "server in", ctx: "", len: 32) client_key = HKDF-Expand-Label(key: client_secret, label: "quic key", ctx: "", len: 16) server_key = HKDF-Expand-Label(key: server_secret, label: "quic key", ctx: "", len: 16) client_iv = HKDF-Expand-Label(key: client_secret, label: "quic iv", ctx: "", len: 12) server_iv = HKDF-Expand-Label(key: server_secret, label: "quic iv", ctx: "", len: 12) client_hp_key = HKDF-Expand-Label(key: client_secret, label: "quic hp", ctx: "", len: 16) server_hp_key = HKDF-Expand-Label(key: server_secret, label: "quic hp", ctx: "", len: 16)
$ init_salt=38762cf7f55934b34d179ae6a4c80cadccbb7f0a
$ init_dcid=0001020304050607
$ init_secret=$(./hkdf extract $init_salt $init_dcid)
$ csecret=$(./hkdf expandlabel $init_secret "client in" "" 32)
$ ssecret=$(./hkdf expandlabel $init_secret "server in" "" 32)
$ client_init_key=$(./hkdf expandlabel $csecret "quic key" "" 16)
$ server_init_key=$(./hkdf expandlabel $ssecret "quic key" "" 16)
$ client_init_iv=$(./hkdf expandlabel $csecret "quic iv" "" 12)
$ server_init_iv=$(./hkdf expandlabel $ssecret "quic iv" "" 12)
$ client_init_hp=$(./hkdf expandlabel $csecret "quic hp" "" 16)
$ server_init_hp=$(./hkdf expandlabel $ssecret "quic hp" "" 16)
$ echo ckey: $client_init_key
$ echo civ: $client_init_iv
$ echo chp: $client_init_hp
$ echo skey: $server_init_key
$ echo siv: $server_init_iv
$ echo shp: $server_init_hp
ckey: b14b918124fda5c8d79847602fa3520b
civ: ddbc15dea80925a55686a7df
chp: 6df4e9d737cdf714711d7c617ee82981
skey: d77fc4056fcfa32bd1302469ee6ebf90
siv: fcb748e37ff79860faa07477
shp: 440b2725e91dc79b370711ef792faa3d
### "client header protection key" from calc step above
$ key=6df4e9d737cdf714711d7c617ee82981
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=ed78716be9711ba498b7ed868443bb2e
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
ed9895bb15
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Long header format | |
Fixed bit (always set) | ||
Packet type: Initial | ||
Reserved (always unset) | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Initial Keys Calc" step
$ key=b14b918124fda5c8d79847602fa3520b
$ iv=ddbc15dea80925a55686a7df
### from this record
$ recdata=c00000000108000102030405060705635f63696400410300
$ authtag=b3b7241ef6646a6c86e5c62ce08be099
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 06 00 40 ee 01 00 00 ea 03 03 00 01 02 03 04 05 |..@.............|
00000010 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 |................|
... snip ...
The server creates its own 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 server does this by generating 32
bytes (256 bits) of random data. The
private key
selected is:
The public key is created from the private key as explained on the X25519 site. The public key calculated is:909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
The public key calculation can be confirmed with command line tools:9fd7ad6dcff4298dd3f96d5b1b2af910a0535b1488d7f8fabb349a982880b615
### 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
and computes the same keys using the method shown in "Client Initial Keys Calc":0001020304050607
### "server header protection key" from calc step above
$ key=440b2725e91dc79b370711ef792faa3d
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=d5d9c823d07c616882ca770279249864
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
4d3acc3988
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Long header format | |
Fixed bit (always set) | ||
Packet type: Initial | ||
Reserved (always unset) | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### From the "Initial Keys Calc" step
$ key=d77fc4056fcfa32bd1302469ee6ebf90
$ iv=fcb748e37ff79860faa07477
### from this record
$ recdata=c00000000105635f63696405735f63696400407500
$ authtag=f0b517a926d62a54a9294136b143b033
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 02 00 42 40 00 00 06 00 40 5a 02 00 00 56 03 03 |..B@....@Z...V..|
00000010 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.|
... snip ...
I've provided a tool to perform this calculation:df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
$ 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
$ cat crypto_clienthello crypto_serverhello | openssl sha256
ff788f9ed09e60d8142ac10a8931cdb6a3726278d3acdba54d9d9ffc7326611b
early_secret = HKDF-Extract(salt=00, key=00...) empty_hash = SHA256("") derived_secret = HKDF-Expand-Label(key: early_secret, label: "derived", ctx: empty_hash, len: 32) 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: 32) server_secret = HKDF-Expand-Label(key: handshake_secret, label: "s hs traffic", ctx: hello_hash, len: 32) client_key = HKDF-Expand-Label(key: client_secret, label: "quic key", ctx: "", len: 16) server_key = HKDF-Expand-Label(key: server_secret, label: "quic key", ctx: "", len: 16) client_iv = HKDF-Expand-Label(key: client_secret, label: "quic iv", ctx: "", len: 12) server_iv = HKDF-Expand-Label(key: server_secret, label: "quic iv", ctx: "", len: 12)
$ hello_hash=ff788f9ed09e60d8142ac10a8931cdb6a3726278d3acdba54d9d9ffc7326611b
$ shared_secret=df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
$ zero_key=0000000000000000000000000000000000000000000000000000000000000000
$ early_secret=$(./hkdf extract 00 $zero_key)
$ empty_hash=$(openssl sha256 < /dev/null | sed -e 's/.* //')
$ derived_secret=$(./hkdf expandlabel $early_secret "derived" $empty_hash 32)
$ handshake_secret=$(./hkdf extract $derived_secret $shared_secret)
$ csecret=$(./hkdf expandlabel $handshake_secret "c hs traffic" $hello_hash 32)
$ ssecret=$(./hkdf expandlabel $handshake_secret "s hs traffic" $hello_hash 32)
$ client_handshake_key=$(./hkdf expandlabel $csecret "quic key" "" 16)
$ server_handshake_key=$(./hkdf expandlabel $ssecret "quic key" "" 16)
$ client_handshake_iv=$(./hkdf expandlabel $csecret "quic iv" "" 12)
$ server_handshake_iv=$(./hkdf expandlabel $ssecret "quic iv" "" 12)
$ client_handshake_hp=$(./hkdf expandlabel $csecret "quic hp" "" 16)
$ server_handshake_hp=$(./hkdf expandlabel $ssecret "quic hp" "" 16)
$ echo ckey: $client_handshake_key
$ echo civ: $client_handshake_iv
$ echo chp: $client_handshake_hp
$ echo skey: $server_handshake_key
$ echo siv: $server_handshake_iv
$ echo shp: $server_handshake_hp
ckey: 30a7e816f6a1e1b3434cf39cf4b415e7
civ: 11e70a5d1361795d2bb04465
chp: 84b3c21cacaf9f54c885e9a506459079
skey: 17abbf0a788f96c6986964660414e7ec
siv: 09597a2ea3b04c00487e71f3
shp: 2a18061c396c2828582b41b0910ed536
### "server header protection key" from calc step above
$ key=2a18061c396c2828582b41b0910ed536
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=296209dff2d02d3d50af692176dd4d50
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
ddb7ce7613
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Long header format | |
Fixed bit (always set) | ||
Packet type: Handshake | ||
Reserved (always unset) | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### From the "Handshake Keys Calc" step
$ key=17abbf0a788f96c6986964660414e7ec
$ iv=09597a2ea3b04c00487e71f3
### from this record
$ recdata=e00000000105635f63696405735f636964441400
$ authtag=8ffb53316d673a32b89259b5d33e94ad
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 06 00 43 ff 08 00 00 56 00 54 00 10 00 0b 00 09 |..C....V.T......|
00000010 08 70 69 6e 67 2f 31 2e 30 00 39 00 41 00 08 00 |.ping/1.0.9.A...|
... snip ...
### "server header protection key" from calc step above
$ key=2a18061c396c2828582b41b0910ed536
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=19681c3f0f102a30f5e647a3399abf54
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
e54e8fcd38
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Long header format | |
Fixed bit (always set) | ||
Packet type: Handshake | ||
Reserved (always unset) | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### From the "Handshake Keys Calc" step
$ key=17abbf0a788f96c6986964660414e7ec
$ iv=09597a2ea3b04c00487e71f3
### from this record
$ recdata=e00000000105635f63696405735f63696440cf01
$ authtag=2ed1025f98fea6d6024998184687dc06
$ recordnum=1
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 06 43 ff 40 b9 46 1e 8a 23 40 58 98 8e 7f 26 4d |.C.@.F..#@X...&M|
00000010 7a b6 a5 1a 21 c6 29 79 b7 a6 79 f4 a0 87 70 85 |z...!.)y..y...p.|
... snip ...
I've provided a tool to perform this calculation:df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
$ 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
### "client header protection key" from handshake keys calc step above
$ key=6df4e9d737cdf714711d7c617ee82981
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=ed1f7b0555cdb783fbdf5b52724b7d29
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
8f57c29e79
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Long header format | |
Fixed bit (always set) | ||
Packet type: Initial | ||
Reserved (always unset) | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Initial Keys Calc" step
$ key=b14b918124fda5c8d79847602fa3520b
$ iv=ddbc15dea80925a55686a7df
### from this record
$ recdata=c00000000105735f63696405635f63696400401701
$ authtag=0555cdb783fbdf5b52724b7d29f0afe3
$ recordnum=1
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 02 00 40 81 00 00 |..@...|
### "client header protection key" from handshake keys calc step above
$ key=84b3c21cacaf9f54c885e9a506459079
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=c6cc12512d7eda141ec057b804d30feb
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
5e8c3ee850
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Long header format | |
Fixed bit (always set) | ||
Packet type: Handshake | ||
Reserved (always unset) | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Handshake Keys Calc" step
$ key=30a7e816f6a1e1b3434cf39cf4b415e7
$ iv=11e70a5d1361795d2bb04465
### from this record
$ recdata=e00000000105735f63696405635f636964401600
$ authtag=12512d7eda141ec057b804d30feb515b
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 02 00 20 00 00 |.. ..|
$ cat crypto_clienthello crypto_serverhello crypto_extensions \
crypto_cert crypto_certverify crypto_s_finished | openssl sha256
b965185af5034eda0ea13ab424dde193afcb42451823a96921ae9d2dad9594ef
empty_hash = SHA256("") derived_secret = HKDF-Expand-Label(key: handshake_secret, label: "derived", ctx: empty_hash, len: 32) 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: 32) server_secret = HKDF-Expand-Label(key: master_secret, label: "s ap traffic", ctx: handshake_hash, len: 32) client_key = HKDF-Expand-Label(key: client_secret, label: "quic key", ctx: "", len: 16) server_key = HKDF-Expand-Label(key: server_secret, label: "quic key", ctx: "", len: 16) client_iv = HKDF-Expand-Label(key: client_secret, label: "quic iv", ctx: "", len: 12) server_iv = HKDF-Expand-Label(key: server_secret, label: "quic iv", ctx: "", len: 12) client_hp_key = HKDF-Expand-Label(key: client_secret, label: "quic hp", ctx: "", len: 16) server_hp_key = HKDF-Expand-Label(key: server_secret, label: "quic hp", ctx: "", len: 16)
$ handshake_hash=b965185af5034eda0ea13ab424dde193afcb42451823a96921ae9d2dad9594ef
$ handshake_secret=fb9fc80689b3a5d02c33243bf69a1b1b20705588a794304a6e7120155edf149a
$ zero_key=0000000000000000000000000000000000000000000000000000000000000000
$ empty_hash=$(openssl sha256 < /dev/null | sed -e 's/.* //')
$ derived_secret=$(./hkdf expandlabel $handshake_secret "derived" $empty_hash 32)
$ master_secret=$(./hkdf extract $derived_secret $zero_key)
$ csecret=$(./hkdf expandlabel $master_secret "c ap traffic" $handshake_hash 32)
$ ssecret=$(./hkdf expandlabel $master_secret "s ap traffic" $handshake_hash 32)
$ client_data_key=$(./hkdf expandlabel $csecret "quic key" "" 16)
$ server_data_key=$(./hkdf expandlabel $ssecret "quic key" "" 16)
$ client_data_iv=$(./hkdf expandlabel $csecret "quic iv" "" 12)
$ server_data_iv=$(./hkdf expandlabel $ssecret "quic iv" "" 12)
$ client_data_hp=$(./hkdf expandlabel $csecret "quic hp" "" 16)
$ server_data_hp=$(./hkdf expandlabel $ssecret "quic hp" "" 16)
$ echo skey: $server_data_key
$ echo siv: $server_data_iv
$ echo shp: $server_data_hp
$ echo ckey: $client_data_key
$ echo civ: $client_data_iv
$ echo chp: $client_data_hp
skey: fd8c7da9de1b2da4d2ef9fd5188922d0
siv: 02f6180e4f4aa456d7e8a602
shp: b7f6f021453e52b58940e4bba72a35d4
ckey: e010a295f0c2864f186b2a7e8fdc9ed7
civ: eb3fbc384a3199dcf6b4c808
chp: 8a6a38bc5cc40cb482a254dac68c9d2f
### "client header protection key" from handshake keys calc step above
$ key=84b3c21cacaf9f54c885e9a506459079
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=9da7e61daa07732aa10b5fbd11a00a62
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
b0b3b06690
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Long header format | |
Fixed bit (always set) | ||
Packet type: Handshake | ||
Reserved (always unset) | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Handshake Keys Calc" step
$ key=30a7e816f6a1e1b3434cf39cf4b415e7
$ iv=11e70a5d1361795d2bb04465
### from this record
$ recdata=e00000000105735f63696405635f636964403f01
$ authtag=5e98f22dc6f25979919bad302f448c0a
$ recordnum=1
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 02 01 40 46 00 01 06 00 40 24 14 00 00 20 50 ff |..@F....@$... P.|
00000010 b0 c1 a4 25 c6 41 89 1c 98 3d 12 67 26 02 6d 3d |...%.A...=.g&.m=|
00000020 b2 8e a3 51 0b dc 20 54 fc d6 37 ed ca cc |...Q.. T..7...|
### "client header protection key" from application keys calc step above
$ key=8a6a38bc5cc40cb482a254dac68c9d2f
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=e66e8ee950ba8b8ed10cba39a06ab7b0
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
4e1e62a65d
### first byte of result is xor'd into lower 5 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Short header format | |
Fixed bit (always set) | ||
"Spin" bit, optionally used to allow observers to measure RTT, but unused by this library | ||
Reserved (always unset) | ||
Key phase bit, used to signal when key rotation occurs | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Application Keys Calc" step
$ key=e010a295f0c2864f186b2a7e8fdc9ed7
$ iv=eb3fbc384a3199dcf6b4c808
### from this record
$ recdata=40735f63696400
$ authtag=8b8ed10cba39a06ab7b0670a50ef68e6
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 0f 00 00 40 04 70 69 6e 67 |...@.ping|
Bitmask | Meaning |
---|---|
0x4 | OFF: An "Offset" field is present in this frame (otherwise the offset is 0) |
0x2 | LEN: A "Length" field is present in this frame (otherwise consume all data in the frame) |
0x1 | FIN: This frame contains the final data of this stream, and the sender is done writing to it |
Bitmask | Meaning |
---|---|
0x2 | Indicates whether the stream is bi-directional ( |
0x1 | Indicates whether the stream was opened by client ( |
### "server header protection key" from handshake keys calc step above
$ key=2a18061c396c2828582b41b0910ed536
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=169e6f1b817e4623e1acbe1db3899b00
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
a5a6f88ece
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Long header format | |
Fixed bit (always set) | ||
Packet type: Handshake | ||
Reserved (always unset) | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Handshake Keys Calc" step
$ key=17abbf0a788f96c6986964660414e7ec
$ iv=09597a2ea3b04c00487e71f3
### from this record
$ recdata=e00000000105635f63696405735f636964401602
$ authtag=6f1b817e4623e1acbe1db3899b00ecfb
$ recordnum=2
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 02 01 1c 00 01 |.....|
### "server header protection key" from application keys calc step above
$ key=b7f6f021453e52b58940e4bba72a35d4
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=4057c883e94d9c296baa8ca0ea6e3a21
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
09cd79a059
### first byte of result is xor'd into lower 5 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Short header format | |
Fixed bit (always set) | ||
"Spin" bit, optionally used to allow observers to measure RTT, but unused by this library | ||
Reserved (always unset) | ||
Key phase bit, used to signal when key rotation occurs | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Application Keys Calc" step
$ key=fd8c7da9de1b2da4d2ef9fd5188922d0
$ iv=02f6180e4f4aa456d7e8a602
### from this record
$ recdata=40635f63696400
$ authtag=ea6e3a21faaf99af2fe10321692057d2
$ recordnum=0
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 02 00 12 00 00 1e 0f 00 00 40 04 70 6f 6e 67 |.........@.pong|
Bitmask | Meaning |
---|---|
0x4 | OFF: An "Offset" field is present in this frame (otherwise the offset is 0) |
0x2 | LEN: A "Length" field is present in this frame (otherwise consume all data in the frame) |
0x1 | FIN: This frame contains the final data of this stream, and the sender is done writing to it |
Bitmask | Meaning |
---|---|
0x2 | Indicates whether the stream is bi-directional ( |
0x1 | Indicates whether the stream was opened by client ( |
### "client header protection key" from application keys calc step above
$ key=8a6a38bc5cc40cb482a254dac68c9d2f
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=90588b44b10d7cd32b03e34502802f25
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
1ac9ce3a7a0
### first byte of result is xor'd into lower 5 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Short header format | |
Fixed bit (always set) | ||
"Spin" bit, optionally used to allow observers to measure RTT, but unused by this library | ||
Reserved (always unset) | ||
Key phase bit, used to signal when key rotation occurs | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Application Keys Calc" step
$ key=e010a295f0c2864f186b2a7e8fdc9ed7
$ iv=eb3fbc384a3199dcf6b4c808
### from this record
$ recdata=40735f63696401
$ authtag=8b44b10d7cd32b03e34502802f25a193
$ recordnum=1
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 02 00 0b 00 00 |.....|
### "server header protection key" from application keys calc step above
$ key=b7f6f021453e52b58940e4bba72a35d4
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=ffeb17b67ec27f97e50d271dc702d92c
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
f494fdfbb6
### first byte of result is xor'd into lower 5 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
Val | Meaning | |
---|---|---|
MSB | Short header format | |
Fixed bit (always set) | ||
"Spin" bit, optionally used to allow observers to measure RTT, but unused by this library | ||
Reserved (always unset) | ||
Key phase bit, used to signal when key rotation occurs | ||
LSB | Packet Number field length (indicates the "Packet Number" field below will have length of one byte) |
### from the "Application Keys Calc" step
$ key=fd8c7da9de1b2da4d2ef9fd5188922d0
$ iv=02f6180e4f4aa456d7e8a602
### from this record
$ recdata=40635f63696401
$ authtag=688be9fd7b302d9eb47cdf1fc4cd9aac
$ recordnum=1
### may need to add -I and -L flags for include and lib dirs
$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
$ cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 1c 00 00 11 67 72 61 63 65 66 75 6c 20 73 68 75 |....graceful shu|
00000010 74 64 6f 77 6e |tdown|
The code for this project, including packet captures, can be found on GitHub.
You may also be interested in a breakdown of TLS 1.3.
If you found this page useful or interesting let me know via Twitter @XargsNotBombs.