std/cipher/aead

std/cipher/src/aead.trb

AEAD_CHACHA20_POLY1305 of RFC 8439 section 2.8: ChaCha20Poly1305 seals a message under a key and a nonce, with associated data that is authenticated and not hidden, and opens it again.

type ChaCha20Poly1305

type ChaCha20Poly1305 with Show, Encode, TryFrom<Bytes, CipherError>

A ChaCha20-Poly1305 key: exactly 32 bytes of the system's source of randomness. It seals with ChaCha20Poly1305.seal and opens with ChaCha20Poly1305.open.

Its Show and its Encode write no byte of the key, so a key that ends up in a log line or in an encoded configuration does not leak.

Examples

const bytes: Bytes = Buffer.from List.filled(32, 3)
const key = ChaCha20Poly1305.tryFrom(bytes).expect("32 bytes")
const nonce: Bytes = Buffer.from List.filled(12, 9)
const sealed = key.seal(nonce, "hello".bytes(), []).expect("a 12-byte nonce")
const opened = key.open(nonce, sealed, []).expect("sealed just now")
print(opened == "hello".bytes())
// prints true

Pitfalls

  • A nonce must never repeat under one key. Two messages sealed under the same key and nonce give away the XOR of their plaintexts, and let a forger make tags. Random 96-bit nonces are safe for up to about 2^32 messages per key; beyond that, count the messages and use the count, or change the key.
  • == and hash() of two keys are generated over the key and are not constant time: do not compare keys with them.
  • A message is at most 274 877 906 880 bytes (RFC 8439 section 2.8), a limit no list reaches.
  • Where the key and the nonces come from is the caller's: std/cipher makes no randomness (docs/design/RANDOM.md).
  • What is constant time: the comparison of the tag in ChaCha20Poly1305.open. The arithmetic of ChaCha20 and Poly1305 is plain Int arithmetic of the language, with no data-dependent branch or index in its source, and promises nothing more: a compiler or a CPU may introduce a timing effect.

fn tryFrom

static fn tryFrom(key: Bytes): Result<Self, CipherError>

The key of key, which is 32 bytes.

Errors

fn seal

fn seal(nonce: Bytes, plaintext: Bytes, associated: Bytes): Result<Bytes, CipherError>

plaintext sealed under this key and nonce (12 bytes): the ciphertext, which is as long as the plaintext, followed by the 16-byte tag. The tag also covers associated, which is not part of the answer and is not hidden: the one who opens has to give the same bytes (RFC 8439 section 2.8).

Errors

fn open

fn open(nonce: Bytes, sealed: Bytes, associated: Bytes): Result<Bytes, CipherError>

The plaintext of sealed, the ciphertext followed by its tag, under this key, nonce and associated. The tag is computed from the ciphertext and compared first, in constant time; the ciphertext is decrypted only after it matched, so no byte of a forgery is ever released.

Errors

  • CipherError.WrongLength for a nonce that is not 12 bytes.
  • CipherError.Forged for sealed bytes of fewer than 16 bytes, and for every tag that does not match, whichever of the key, the nonce, the associated data, the ciphertext or the tag is not the one that sealed it.

fn show

fn show(): String

ChaCha20Poly1305(key hidden): no byte of the key is shown.

fn encode

fn encode<Target: Encoder>(var target: Target)

What ChaCha20Poly1305.show writes, as a string: an encoded configuration never carries the key.