In this demonstration a client connects to a server, negotiates an Obfs4 session, sends 1450 bytes of "lorem ipsum" text, receives the same text reversed, and then terminates the session. Click below to begin exploring.
Disabled
Enabled
Paranoid
The server generates a private/public "identity" key-pair for long term use. This allows the server to distribute its long term public key out of band so that a client can perform a key exchange to obfuscate it's first message. 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 https://x25519.xargs.org, 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 https://x25519.xargs.org. 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
The "Node ID" is a random 20-byte long term value chosen to identify this specific obfs4 server.
The Node ID is chosen by selecting an integer between 0 and 2160-1. The server does this by generating 20 bytes (160 bits) of random data. The Node ID selected is:
9fd7ad6dcff4298dd3f96d5b1b2af910a0535b14
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 https://x25519.xargs.org, 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
One of the central design goals of Obfs4 is to make every packet entirely indistinguishable from uniform random. In order to achieve this we need a way of encoding the public key in a way that doesn't retain any of the structure or computational distinguishers. To do so we use the Elligator2 encoding scheme. The calculated Elligator2 "Representative" is:
f4d05df78ec6f67ac104319963cac09764ba890f892528169dcbbfdb1666a7bd
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 https://x25519.xargs.org, 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 https://x25519.xargs.org. 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 "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=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 can be found on GitHub.
This page was inspired by the TLS 1.X Explained sites and forked from the original source written by @XargsNotBombs.