Next: Handshake, Previous: Verifier structure, Up: Developer
NONCE = 64bit(ZEROS) || 64bit(MAC(MAC_KEY, SERIAL)) PAYLOAD = DATA || PAD [|| ZEROS] CIPHERTEXT = ENCRYPT(KEY, NONCE, PAYLOAD) TAG = AUTH(AUTH_KEY, CIPHERTEXT || NONCE) MESSAGE = TAG || CIPHERTEXT || NONCE
SERIAL
is message’s serial number. Odds are reserved for
client (to server) messages, evens for server (to client) messages.
MAC
is BLAKE2b-MAC used to obfuscate SERIAL
. MAC’s key
MAC_KEY
is the first 256-bit of ChaCha20’s output with established
common key and zero nonce (message nonces start from 1).
MAC_KEY = 256bit(ENCRYPT(KEY, 0))
ENCRYPT
is ChaCha20 stream cipher, with established session
KEY
and obfuscated SERIAL
used as a nonce. 512 bit of
ChaCha20’s output is ignored and only remaining is XORed with ther data,
encrypting it.
DATA
is padded using ISO/IEC 7816-4 format (PAD
(0x80
byte) with optional ZEROS
following), to fill up packet to
conceal payload packet length.
AUTH
is Poly1305 authentication function. First 256 bits of
ChaCha20’s output are used as a one-time key for AUTH
.
AUTH_KEY = 256bit(ENCRYPT(KEY, NONCE))
To prevent replay attacks we must remember received SERIAL
s and
drop when receiving duplicate ones.
In encryptionless mode this scheme is slightly different:
NONCE = MAC(MAC_KEY, SERIAL) ENCODED = ENCLESS(DATA || PAD || ZEROS) PACKET = ENCODED || NONCE
ENCLESS
is AONT and chaffing function. There is no need in
explicit separate authentication.