Skip to main content

AES GCM

AES with Galois/Counter Mode. The standard for authenticated encryption.

local AES = Cryptography.Encryption.AES

Encrypt

AES.Encrypt(Plaintext: buffer, Key: buffer, IV: buffer, AAD: buffer?) -> (buffer, buffer)

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

ParameterTypeDescription
PlaintextbufferData to encrypt
Keybuffer16, 24, or 32 bytes (AES 128/192/256)
IVbuffer12 bytes recommended
AADbuffer?Additional authenticated data (optional)
local Key = CSPRNG.RandomBytes(32) -- AES 256
local IV = CSPRNG.RandomBytes(12)
local Plaintext = buffer.fromstring("Secret data")

local Ciphertext, Tag = AES.Encrypt(Plaintext, Key, IV)

-- With AAD
local AAD = buffer.fromstring("metadata")
local Ciphertext2, Tag2 = AES.Encrypt(Plaintext, Key, IV, AAD)

Decrypt

AES.Decrypt(Ciphertext: buffer, Key: buffer, IV: buffer, Tag: buffer, AAD: buffer?) -> (boolean, buffer?)

Returns a success boolean and the decrypted data (or nil on failure).

local Success, Decrypted = AES.Decrypt(Ciphertext, Key, IV, Tag, AAD)
if Success then
print("Decrypted:", buffer.tostring(Decrypted))
else
warn("Decryption failed, authentication error")
end

The return signature is different from AEAD. You get an explicit boolean instead of checking for nil.

Key Sizes

Key SizeVariantRounds
16 bytesAES 12810
24 bytesAES 19212
32 bytesAES 25614
local Key128 = CSPRNG.RandomBytes(16)
local Key192 = CSPRNG.RandomBytes(24)
local Key256 = CSPRNG.RandomBytes(32)

For new applications, use AES 256 unless you have specific performance constraints.

Security Limitations

AES GCM has some important limitations you should understand.

Nonce Size

The 96 bit (12 byte) IV means you hit birthday bound concerns around 2^32 messages with the same key. After about 4 billion encryptions, you have a non negligible chance of IV collision. Rotate keys before you get anywhere near this limit.

Unlike ChaCha20 Poly1305, there is no built in extended nonce variant. If you need to encrypt many messages with the same key, consider using a counter based IV scheme, rotating keys frequently, or using ChaCha20 Poly1305 with XChaCha20 mode instead.

Not Key Committing

AES GCM is not key committing. This means the same ciphertext could potentially decrypt to valid (but different) plaintexts under different keys. An attacker who can choose ciphertext might find a collision where two different keys both produce valid decryptions.

This enables the Invisible Salamander attack: in a group messaging scenario, an attacker could craft a message that appears benign to moderators (using one key) but malicious to recipients (using another key).

If key commitment matters for your use case, include a hash or commitment of the key in your AAD, use a key committing AEAD construction, or verify the key through an independent channel.

Timing Attacks

The S box lookups in AES are theoretically vulnerable to cache timing attacks. In native implementations this is addressed with constant time table lookups or AES NI instructions. In Luau, we cannot guarantee constant time behavior, but practical exploitation in Roblox's environment is extremely difficult due to network jitter and the inability to make precise timing measurements.

When to Use AES GCM

Use AES GCM when you need compatibility with existing AES GCM systems, standards or regulations require AES specifically, or you are interfacing with hardware that has AES acceleration.

For new Roblox specific implementations, ChaCha20 Poly1305 is generally preferred because it is faster in pure software (no AES NI in Luau), XChaCha20 gives you larger nonces, and there are no table lookup timing concerns.