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