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.
202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3fThe public key is chosen by multiplying the point x=9 on the x25519 curve by the private key. 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
909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafThe public key is chosen by multiplying the point x=9 on the x25519 curve by the private key. The public key calculated is:
9fd7ad6dcff4298dd3f96d5b1b2af910a0535b1488d7f8fabb349a982880b615The public key calculation can be confirmed with command line tools:
### 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
early_secret = HKDF-Extract( salt=00, key=00...) empty_hash = SHA256("") derived_secret = HKDF-Expand-Label( key = early_secret, label = "derived", context = empty_hash, len = 32) handshake_secret = HKDF-Extract( salt = derived_secret, key = shared_secret) client_handshake_traffic_secret = HKDF-Expand-Label( key = handshake_secret, label = "c hs traffic", context = hello_hash, len = 32) server_handshake_traffic_secret = HKDF-Expand-Label( key = handshake_secret, label = "s hs traffic", context = hello_hash, len = 32) client_handshake_key = HKDF-Expand-Label( key = client_handshake_traffic_secret, label = "key", context = "", len = 16) server_handshake_key = HKDF-Expand-Label( key = server_handshake_traffic_secret, label = "key", context = "", len = 16) client_handshake_iv = HKDF-Expand-Label( key = client_handshake_traffic_secret, label = "iv", context = "", len = 12) server_handshake_iv = HKDF-Expand-Label( key = server_handshake_traffic_secret, label = "iv", context = "", len = 12)
$ hello_hash=da75ce1139ac80dae4044da932350cf65c97ccc9e33f1e6f7d2d4b18b736ffd5
$ 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 "key" "" 16)
$ server_handshake_key=$(./hkdf expandlabel $ssecret "key" "" 16)
$ client_handshake_iv=$(./hkdf expandlabel $csecret "iv" "" 12)
$ server_handshake_iv=$(./hkdf expandlabel $ssecret "iv" "" 12)
$ echo ckey: $client_handshake_key
$ echo skey: $server_handshake_key
$ echo civ: $client_handshake_iv
$ echo siv: $server_handshake_iv
ckey: 7154f314e6be7dc008df2c832baa1d39
skey: 844780a7acad9f980fa25c114e43402a
civ: 71abc2cae4c699d47c600268
siv: 4c042ddc120a38d1417fc815
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=844780a7acad9f980fa25c114e43402a
$ iv=4c042ddc120a38d1417fc815
### from this record
$ recdata=1703030475
$ authtag=e08b0e455a350ae54d76349aa68c71ae
$ 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
$ echo "da 1e c2 d7 bd a8 eb f7 3e dd 50 10 fb a8 08 9f d4 26 b0 ea 1e
... snip ...
ac 2d dd 1f 88 5d 42 ea 58 4c" | xxd -r -p > /tmp/msg1
$ cat /tmp/msg1 \
| ./aes_128_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 |e0..!0..........|
00000020 15 5a 92 ad c2 04 8f 90 30 0d 06 09 2a 86 48 86 |.Z......0...*.H.|
... snip ...
empty_hash = SHA256("") derived_secret = HKDF-Expand-Label( key = handshake_secret, label = "derived", context = empty_hash, len = 32) master_secret = HKDF-Extract( salt=derived_secret, key=00...) client_application_traffic_secret = HKDF-Expand-Label( key = master_secret, label = "c ap traffic", context = handshake_hash, len = 32) server_application_traffic_secret = HKDF-Expand-Label( key = master_secret, label = "s ap traffic", context = handshake_hash, len = 32) client_application_key = HKDF-Expand-Label( key = client_application_traffic_secret, label = "key", context = "", len = 16) server_application_key = HKDF-Expand-Label( key = server_application_traffic_secret, label = "key", context = "", len = 16) client_application_iv = HKDF-Expand-Label( key = client_application_traffic_secret, label = "iv", context = "", len = 12) server_application_iv = HKDF-Expand-Label( key = server_application_traffic_secret, label = "iv", context = "", len = 12)
$ handshake_hash=22844b930e5e0a59a09d5ac35fc032fc91163b193874a265236e568077378d8b
$ 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_application_key=$(./hkdf expandlabel $csecret "key" "" 16)
$ server_application_key=$(./hkdf expandlabel $ssecret "key" "" 16)
$ client_application_iv=$(./hkdf expandlabel $csecret "iv" "" 12)
$ server_application_iv=$(./hkdf expandlabel $ssecret "iv" "" 12)
$ echo ckey: $client_application_key
$ echo skey: $server_application_key
$ echo civ: $client_application_iv
$ echo siv: $server_application_iv
ckey: 49134b95328f279f0183860589ac6707
skey: 0b6d22c8ff68097ea871c672073773bf
civ: bc4dd5f7b98acff85466261d
siv: 1b13dd9f8d8f17091d34b349
### from the "Client Handshake Keys Calc" step
$ key=7154f314e6be7dc008df2c832baa1d39
$ iv=71abc2cae4c699d47c600268
### from this record
$ recdata=1703030035
$ authtag=5435d4eb22d0536c80c932e2f3c96083
$ 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
$ echo "71 55 df f4 74 1b df c0 c4 3a 1d e0 b0 11 33 ac 19 74 ed c8 8e 70
91 c3 ff 1e 26 60 cd 71 92 83 ba 40 f7 c1 0b" | xxd -r -p > /tmp/msg2
$ cat /tmp/msg2 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 14 00 00 20 97 60 17 a7 7a e4 7f 16 58 e2 8f 70 |... .`..z...X..p|
00000010 85 fe 37 d1 49 d1 e9 c9 1f 56 e1 ae bb e0 c6 bb |..7.I....V......|
00000020 05 4b d9 2b 16 |.K.+.|
### from the "Client Application Keys Calc" step
$ key=49134b95328f279f0183860589ac6707
$ iv=bc4dd5f7b98acff85466261d
### from this record
$ recdata=1703030015
$ authtag=b12f5f25a781957874742ab7fb305dd5
$ 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
$ echo "c7 40 61 53 5e" \
| xxd -r -p > /tmp/msg3
$ cat /tmp/msg3 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 70 69 6e 67 17 |ping.|
### from the "Server Application Keys Calc" step
$ key=0b6d22c8ff68097ea871c672073773bf
$ iv=1b13dd9f8d8f17091d34b349
### from this record
$ recdata=170303017d
$ authtag=bfe0c1f688627643a049c3492eaf6f0f
$ 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
$ echo "c4 d4 b7 1b 6f 4c 2f 30 13 02 74 e7 b4 6e 40 89 68 de 07 98 f3 60
... snip ...
9a 00 5f 88 e0 a2 da" | xxd -r -p > /tmp/msg5
$ cat /tmp/msg5 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 04 00 00 b2 00 02 a3 00 04 03 02 01 01 00 00 a0
... snip ...
### from the "Server Application Keys Calc" step
$ key=0b6d22c8ff68097ea871c672073773bf
$ iv=1b13dd9f8d8f17091d34b349
### from this record
$ recdata=1703030015
$ authtag=fa7fb16b663ecdfca3dbb81931a90ca7
$ 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
$ echo "370e5f168a" | xxd -r -p > /tmp/msg4
$ cat /tmp/msg4 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
00000000 70 6f 6e 67 17 |pong.|
The code for this project can be found on GitHub.
You may also be interested in the TLS 1.2 version of this document.
If you found this page useful or interesting let me know via Twitter @XargsNotBombs.