Skip to main content

ChaCha20 Poly1305 (AEAD)

This is what you should use for symmetric encryption. It is authenticated encryption with associated data (AEAD), meaning tampered ciphertext will fail to decrypt. You get confidentiality and integrity in one package.

local AEAD = Cryptography.Encryption.AEAD

Encrypt

AEAD.Encrypt(Message: buffer, Key: buffer, Nonce: buffer, AAD: buffer?, Rounds: number?, UseXChaCha20: boolean?) -> (buffer, buffer)

Encrypts data and returns ciphertext plus a 16 byte authentication tag.

ParameterTypeDescription
MessagebufferData to encrypt
Keybuffer32 bytes, use CSPRNG.RandomBytes(32)
Noncebuffer12 bytes for ChaCha20, 24 bytes for XChaCha20
AADbuffer?Additional authenticated data (optional)
Roundsnumber?Default 20, can use 12 or 8 for speed
UseXChaCha20boolean?Use extended nonce variant
local Key = CSPRNG.RandomBytes(32)
local Nonce = CSPRNG.RandomBytes(12)
local Plaintext = buffer.fromstring("Secret message")

-- Basic encryption
local Ciphertext, Tag = AEAD.Encrypt(Plaintext, Key, Nonce)

-- With additional authenticated data
local AAD = buffer.fromstring("user:alice,timestamp:12345")
local Ciphertext2, Tag2 = AEAD.Encrypt(Plaintext, Key, Nonce, AAD)

The AAD is authenticated but not encrypted. Use it for metadata you want to verify (like headers or routing info) but do not need to hide.

Decrypt

AEAD.Decrypt(Ciphertext: buffer, Key: buffer, Nonce: buffer, Tag: buffer, AAD: buffer?, Rounds: number?, UseXChaCha20: boolean?) -> buffer?

Decrypts and verifies. Returns nil if authentication fails.

local Decrypted = AEAD.Decrypt(Ciphertext, Key, Nonce, Tag, AAD)
if Decrypted then
print("Decrypted:", buffer.tostring(Decrypted))
else
warn("Authentication failed, data was tampered")
end
Always Check for nil

If Decrypt returns nil, the ciphertext was modified or the wrong key/nonce was used. Never try to use the data.

XChaCha20 Mode

Standard ChaCha20 uses a 96 bit nonce, which has a birthday bound around 2^32 messages with the same key. If you are encrypting a lot of messages or want to use random nonces without worrying, use XChaCha20 with its 192 bit nonce:

local Key = CSPRNG.RandomBytes(32)
local Nonce = CSPRNG.RandomBytes(24) -- 24 bytes for XChaCha20

local Ciphertext, Tag = AEAD.Encrypt(Plaintext, Key, Nonce, nil, nil, true)
local Decrypted = AEAD.Decrypt(Ciphertext, Key, Nonce, Tag, nil, nil, true)

XChaCha20 uses HChaCha20 to derive a subkey from the first 128 bits of the nonce, giving you essentially unlimited messages per key without birthday concerns.

Reduced Rounds

The default 20 rounds provide a large security margin. If you need more speed and can accept a smaller margin:

-- 12 rounds (ChaCha12), still considered secure
local Ciphertext, Tag = AEAD.Encrypt(Plaintext, Key, Nonce, nil, 12)

-- 8 rounds (ChaCha8), minimal security margin
local Ciphertext, Tag = AEAD.Encrypt(Plaintext, Key, Nonce, nil, 8)

For most applications, stick with 20 rounds. The performance difference in Luau is not dramatic enough to justify reduced security margin.

Raw Stream Ciphers

If you need just the stream cipher without Poly1305 authentication:

AEAD.ChaCha20(Data: buffer, Key: buffer, Nonce: buffer, Counter: number?, Rounds: number?) -> buffer

AEAD.XChaCha20(Data: buffer, Key: buffer, Nonce: buffer, Counter: number?, Rounds: number?) -> buffer

local Encrypted = AEAD.ChaCha20(Data, Key, Nonce, 1, 20)
local Decrypted = AEAD.ChaCha20(Encrypted, Key, Nonce, 1, 20)
No Authentication

These raw functions do not authenticate. An attacker can flip bits in your ciphertext and you will not know. Only use these if you have a separate authentication mechanism.

Security Properties

What AEAD gives you:

  • Confidentiality: attacker cannot read the plaintext
  • Integrity: attacker cannot modify ciphertext without detection
  • Authenticity: ciphertext was created by someone with the key

What AEAD does not give you:

Key commitment The same ciphertext can potentially decrypt to different plaintexts under different keys. This matters in multi key scenarios where an attacker might try to find a key that decrypts malicious content to something benign (Invisible Salamander attack). If this is a concern, include a hash of the key in your AAD.

Nonce misuse resistance Reusing a nonce with the same key is catastrophic. It leaks the XOR of plaintexts and breaks authentication. Use random nonces with XChaCha20 if you cannot guarantee uniqueness.