Obfs4 Obfs4+

The Illustrated Obfs4 Connection

Every byte explained and reproduced

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

IAT Mode :

Server Identity Key & ID Generation

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:

909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
The public key is created from the private key as explained on https://x25519.xargs.org. 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

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
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 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:

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

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
Client Handshake
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 Session Key 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 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:

909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
The public key is created from the private key as explained on https://x25519.xargs.org. 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 Session Secrets 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
Server Handshake
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"
Length Distribution Seed Message
The server sends a message to the client that will be used to seed the randomized distribution of lengths of lengths for future Frames sent by the client.
Message Type: DRBG Length Seed 01
A 1-byte message type field. 0x01 = DRBG Length Seed.
Message Length 00 18
2-byte (u16) length field.
DRBG Length Seed f6 de 0e a1 f2 61 c8 1f bf e8 54 5b 23 91 60 9c 7d 2b fe bc be 45 9e 34
24 byte seed for the deterministic random bit generator used for selecting random padded frame lengths.
Client Session 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
Encrypted Frame
To reduce the fingerprint-ability of the protocol the entire packet is encrypted or obfuscated to be indistinguishable from uniform random. Once the outer frame and encrypted data are decrypted, the inner data can be parsed into one of several Message types, or (\x0x00..) padding.

The internal messages are discussed in their own sections below this one.
Obfuscated Frame Length fb 15
Obfs4 Frames begin with a 2-byte length field that is encrypted based on a NaCl secretbox nonce. The length can extend beyond one packet to account for network fragmentation.

The data following this length is encrypted obfs4 Messages as well as padding.
Encrypted Data 30 a8 06 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 Payload Message 00 00 04 70 6f 6e 67
This application data is represented in its own section below.
Client Payload Message
The client sends the data "ping".
Message Type: Payload 00
A 1-byte message type field. 0x00 = data payload.
Message Length 00 04
2-byte (u16) length field.
Application Data 70 69 6e 67
The bytes "ping".
Encrypted Frame
To reduce the fingerprint-ability of the protocol the entire packet is encrypted or obfuscated to be indistinguishable from uniform random. Once the outer frame and encrypted data are decrypted, the inner data can be parsed into one of several Message types, or (\x0x00..) padding.

The internal messages are discussed in their own sections below this one.
Obfuscated Frame Length A6 21
Obfs4 Frames begin with a 2-byte length field that is encrypted based on a NaCl secretbox nonce. The length can extend beyond one packet to account for network fragmentation.

The data following this length is encrypted obfs4 Messages as well as padding.
Encrypted Data 5c 71 16 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 Payload Message 00 00 04 70 6f 6e 67
This application data is represented in its own section below.
Server Payload Message
The server replies with the data "pong".
Message Type: Payload 00
A 1-byte message type field. 0x00 = data payload.
Message Length 00 04
2-byte (u16) length field.
Application Data 70 6f 6e 67
The bytes "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.

[print]