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.
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:
202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3fThe public key is created from the private key as explained on the X25519 site. The public key calculated is:
358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254The 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
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:
909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafThe public key is created from the private key as explained on the X25519 site. The public key calculated is:
9fd7ad6dcff4298dd3f96d5b1b2af910a0535b1488d7f8fabb349a982880b615The 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
df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624I'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
$ (tail -c +6 clienthello; tail -c +6 serverhello) | openssl sha384
e05f64fcd082bdb0dce473adf669c2769f257a1c75a51b7887468b5e0e7a7de4f4d34555112077f16e079019d5a845bd
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)
$ 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
df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624I'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
### 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 |.......|
### 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 ...
### 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 |........\[email protected].|
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 ...
### 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..|
# 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
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)
$ 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 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'.|
### 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.|
### 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 ...
### 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 ...
### 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.|
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.