Violet

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.

Errors

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.

Nonces

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.

lua
core.log(core.crypto.to_hex(core.crypto.sha256("abc")))
-- ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

core.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.

lua
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.

lua
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.

lua
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.

lua
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)
end

Encoding 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.

lua
local expected = core.crypto.hmac_sha256(secret, body)
if not core.crypto.constant_time_equals(expected, received_mac) then
    return
end