obfs2 (The Twobfuscator) 0. Protocol overview This is a protocol obfuscation layer for TCP protocols. Its purpose is to keep a third party from telling what protocol is in use based on message contents. It is based on brl's ssh obfuscation protocol. It does not provide authentication or data integrity. It does not hide data lengths. It is more suitable for providing a layer of obfuscation for an existing authenticated protocol, like SSH or TLS. The protocol has two phases: in the first phase, the parties establish keys. In the second, the parties exchange superenciphered traffic. 1. Primitives, notation, and constants. H(x) is SHA256 of x. H^n(x) is H(x) called iteratively n times. E(K,s) is the AES-CTR-128 encryption of s using K as key. x | y is the concatenation of x and y. UINT32(n) is the 4 byte value of n in big-endian (network) order. SR(n) is n bytes of strong random data. WR(n) is n bytes of weaker random data. "xyz" is the ASCII characters 'x', 'y', and 'z', not NUL-terminated. s[:n] is the first n bytes of s. s[n:] is the last n bytes of s. MAGIC_VALUE is 0x2BF5CA7E SEED_LENGTH is 16 MAX_PADDING is 8192 HASH_ITERATIONS is 100000 KEYLEN is the length of the key used by E(K,s) -- that is, 16. IVLEN is the length of the IV used by E(K,s) -- that is, 16. HASHLEN is the length of the output of H() -- that is, 32. MAC(s, x) = H(s | x | s) A "byte" is an 8-bit octet. We require that HASHLEN >= KEYLEN + IVLEN 2. Key establishment phase. The party who opens the connection is the 'initiator'; the one who accepts it is the 'responder'. Each begins by generating a seed and a padding key as follows. The initiator generates: INIT_SEED = SR(SEED_LENGTH) INIT_PAD_KEY = MAC("Initiator obfuscation padding", INIT_SEED)[:KEYLEN] And the responder generates: RESP_SEED = SR(SEED_LENGTH) RESP_PAD_KEY = MAC("Responder obfuscation padding", INIT_SEED)[:KEYLEN] Each then generates a random number PADLEN in range from 0 through MAX_PADDING (inclusive). The initiator then sends: INIT_SEED | E(INIT_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN)) and the responder sends: RESP_SEED | E(RESP_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN)) Upon receiving the SEED from the other party, each party derives the other party's padding key value as above, and decrypts the next 8 bytes of the key establishment message. If the MAGIC_VALUE does not match, or the PADLEN value is greater than MAX_PADDING, the party receiving it should close the connection immediately. Otherwise, it should read the remaining PADLEN bytes of padding data and discard them. Additional keys are then derived as: INIT_SECRET = MAC("Initiator obfuscated data", INIT_SEED|RESP_SEED) RESP_SECRET = MAC("Responder obfuscated data", INIT_SEED|RESP_SEED) INIT_KEY = INIT_SECRET[:KEYLEN] INIT_IV = INIT_SECRET[KEYLEN:] RESP_KEY = RESP_SECRET[:KEYLEN] RESP_IV = RESP_SECRET[KEYLEN:] The INIT_KEY value keys a stream cipher used to encrypt values from initiator to responder thereafter. The stream cipher's IV is INIT_IV. The RESP_KEY value keys a stream cipher used to encrypt values from responder to initiator thereafter. That stream cipher's IV is RESP_IV. 3. Shared-secret extension Optionally, if the client and server share a secret value SECRET, they can replace the MAC function with: MAC(s,x) = H^n(s | x | H(SECRET) | s) where n = HASH_ITERATIONS.