Crypto
On this page
The core.crypto module provides hashing, key exchange, authenticated encryption and secure random bytes to Lua scripts. Every function runs immediately and returns its result; nothing is asynchronous.
Keys, nonces, hashes and ciphertexts are binary strings (any byte value, including "\0"). Use to_hex or to_base64 before logging them or putting them in JSON.
A wrong argument type or a wrong key, nonce or secret length raises an error naming the function and the argument, for example crypto.chacha20poly1305_seal: key must be 32 bytes, got 31. Data that fails to authenticate or decode does not raise: those functions return nil and a reason.
Never seal two messages with the same key and nonce. Use a counter you never repeat, or 12 fresh bytes from random_bytes for each message.
Hashing
core.crypto.sha256(data: string) -> string
Available on GMS
Returns the 32-byte SHA-256 digest of data.
core.log(core.crypto.to_hex(core.crypto.sha256("abc")))
-- ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015adcore.crypto.hmac_sha256(key: string, data: string) -> string
Available on GMS
Returns the 32-byte HMAC-SHA-256 of data under key. key can be any length.
local mac = core.crypto.hmac_sha256(secret, body)
local header = core.crypto.to_hex(mac)Random Bytes
core.crypto.random_bytes(n: number) -> string
Available on GMS
Returns n bytes from the operating system's secure random generator. n must be an integer from 1 to 65536.
local nonce = core.crypto.random_bytes(12)Key Exchange (X25519)
core.crypto.x25519_keypair() -> string, string
Available on GMS
Returns a new random 32-byte secret key and its 32-byte public key. Keep the secret key to yourself and send the public key to the other side.
core.crypto.x25519(secret: string, peer_public: string) -> string | nil, string?
Available on GMS
Returns the 32-byte shared secret between your secret key and the other side's peer_public key; both sides get the same value. Both arguments must be 32 bytes. Returns nil, "invalid public key" when peer_public cannot produce a shared secret.
The shared secret is not meant to be used as an encryption key directly; derive one from it, for example with hmac_sha256.
local my_secret, my_public = core.crypto.x25519_keypair()
-- send my_public, receive their_public
local shared = assert(core.crypto.x25519(my_secret, their_public))
local key = core.crypto.hmac_sha256(shared, "session key")Authenticated Encryption (ChaCha20-Poly1305)
The sealed text is the ciphertext followed by a 16-byte tag, so it is 16 bytes longer than the plaintext. aad is optional extra data that is authenticated but not encrypted; the same aad must be passed to open the message.
core.crypto.chacha20poly1305_seal(key: string, nonce: string, plaintext: string [, aad: string]) -> string
Available on GMS
Encrypts and authenticates plaintext. key must be 32 bytes and nonce 12 bytes.
core.crypto.chacha20poly1305_open(key: string, nonce: string, ciphertext: string [, aad: string]) -> string | nil, string?
Available on GMS
Checks and decrypts a sealed text. Returns the plaintext, or nil, "auth failed" when the key, nonce, aad or any byte of the sealed text is wrong, and nil, "ciphertext too short" when it is shorter than the 16-byte tag. key must be 32 bytes and nonce 12 bytes.
local key = core.crypto.random_bytes(32)
local nonce = core.crypto.random_bytes(12)
local sealed = core.crypto.chacha20poly1305_seal(key, nonce, "hello", "header")
local plain, err = core.crypto.chacha20poly1305_open(key, nonce, sealed, "header")
if not plain then
core.log_error("Rejected message: " .. err)
endEncoding and Comparison
core.crypto.to_hex(data: string) -> string
Available on GMS
Returns data as lowercase hexadecimal, two characters per byte.
core.crypto.from_hex(hex: string) -> string | nil, string?
Available on GMS
Decodes hexadecimal (upper or lower case). Returns nil, "invalid hex" for an odd length or a character that is not a hex digit.
core.crypto.to_base64(data: string) -> string
Available on GMS
Returns data as standard Base64 with = padding.
core.crypto.from_base64(text: string) -> string | nil, string?
Available on GMS
Decodes standard Base64 with = padding. Returns nil, "invalid base64" for anything else, including whitespace, URL-safe characters and missing padding.
core.crypto.constant_time_equals(a: string, b: string) -> boolean
Available on GMS
Returns true when a and b are the same bytes. The time taken does not depend on where they differ, so use it to compare MACs and tokens.
local expected = core.crypto.hmac_sha256(secret, body)
if not core.crypto.constant_time_equals(expected, received_mac) then
return
end