1 After TLS 1.3 handshake is finished, 512-bit keying material is
2 exported. Various 256-bit keys are derived from it:
4 InitEncKey = BLAKE3-DeriveKey(seed, "go.cypherpunks.ru/udpobfs/v2 init enc")
5 InitMACKey = BLAKE3-DeriveKey(seed, "go.cypherpunks.ru/udpobfs/v2 init mac")
6 InitObfsKey = BLAKE3-DeriveKey(seed, "go.cypherpunks.ru/udpobfs/v2 init obfs")
7 RespEncKey = BLAKE3-DeriveKey(seed, "go.cypherpunks.ru/udpobfs/v2 resp enc")
8 RespMACKey = BLAKE3-DeriveKey(seed, "go.cypherpunks.ru/udpobfs/v2 resp mac")
9 RespObfsKey = BLAKE3-DeriveKey(seed, "go.cypherpunks.ru/udpobfs/v2 resp obfs")
11 Each peer has 64-bit packet sequence counter. It is fed to
12 BLAKE3(len=256, key=*EncKey) and then its XOF output is XORed with
13 plaintext packet. Then BLAKE3(len=48, key=*MACKey) is taken over the
14 sequence counter concatenated with the ciphertext. That MAC, lower
15 16-bits of the sequence counter are encrypted with Blowfish(key=*ObfsKey)
16 and prepended to the ciphertext.
18 ciphertext = BLAKE3(len=256, key=*EncKey)(SeqNum).XOF(len=len(plaintext))
19 ciphertext = ciphertext XOR plaintext
20 mac = BLAKE3(len=48, key=*MACKey)(SeqNum || ciphertext)
21 send(Blowfish(key=*ObfsKey)(mac || SeqNum[6:]) || ciphertext)
23 All of that gives only 8-byte overhead, providing at least some 48-bit
24 authentication and invisibility of cleartext nonces/sequences. BLAKE3 is
25 pretty fast algorithm, however ChaCha20-Poly1305 can be faster for small
26 messages -- so performance depends on message sizes. Blowfish is just
27 the fastest (serious) cipher with 64-bit blocksize in the Go's library.