Encryption & Signing
The crypto module generates random values, computes HMACs, encrypts and decrypts data, encodes and verifies JWTs, and derives keys. In deterministic workflows, random generation and encryption (which creates a random nonce) run as recorded side effects; replay returns the recorded bytes. Other operations, including HMAC, decryption, JWT processing, PBKDF2, and comparison, run directly.
This page is an API reference. Each code block is an isolated call, not a complete key-management or authentication system. Names such as data, key, aad, payload, and token are application-provided values. Load keys and passwords through the application's secret-management boundary; do not hard-code, log, or return them in diagnostics. Before consuming any value, error result shown here, propagate or handle the error.
Loading
local crypto = require("crypto")
Random Generation
Random Bytes
local bytes, err = crypto.random.bytes(32)
| Parameter | Type | Description |
|---|---|---|
length |
integer | Number of bytes (1 to 1,048,576) |
Returns: string, error
Random String
local str, err = crypto.random.string(32)
local str, err = crypto.random.string(32, "0123456789abcdef")
| Parameter | Type | Description |
|---|---|---|
length |
integer | Output length in bytes (1 to 1,048,576) |
charset |
string? | ASCII byte alphabet to use (default: alphanumeric) |
Returns: string, error
The implementation selects bytes from the supplied alphabet. A non-ASCII alphabet can be split into invalid UTF-8, and modulo selection is exactly uniform only when the alphabet's byte length divides 256. For uniformly random secret material, use crypto.random.bytes and encode the result for the required transport format.
Random UUID
local id, err = crypto.random.uuid()
Returns: string, error
HMAC
HMAC-SHA256
local hex, err = crypto.hmac.sha256(key, data)
| Parameter | Type | Description |
|---|---|---|
key |
string | HMAC key |
data |
string | Data to authenticate |
Returns: string, error
HMAC-SHA512
local hex, err = crypto.hmac.sha512(key, data)
| Parameter | Type | Description |
|---|---|---|
key |
string | HMAC key |
data |
string | Data to authenticate |
Returns: string, error
Encryption
AES-GCM {id="encrypt-aes-gcm"}
local encrypted, err = crypto.encrypt.aes(data, key)
local encrypted, err = crypto.encrypt.aes(data, key, aad)
| Parameter | Type | Description |
|---|---|---|
data |
string | Plaintext to encrypt |
key |
string | 16, 24, or 32 bytes (AES-128/192/256) |
aad |
string? | Additional authenticated data |
Returns: string, error (nonce prepended)
Both encryption functions generate a nonce and prepend it to the ciphertext. Do not remove or reuse it, and use the same AAD during decryption. Ciphertext is not a secret-free log value: it can expose length and correlation information.
ChaCha20-Poly1305 {id="encrypt-chacha20"}
local encrypted, err = crypto.encrypt.chacha20(data, key)
local encrypted, err = crypto.encrypt.chacha20(data, key, aad)
| Parameter | Type | Description |
|---|---|---|
data |
string | Plaintext to encrypt |
key |
string | Must be 32 bytes |
aad |
string? | Additional authenticated data |
Returns: string, error (nonce prepended)
Decryption
AES-GCM {id="decrypt-aes-gcm"}
local plaintext, err = crypto.decrypt.aes(encrypted, key)
local plaintext, err = crypto.decrypt.aes(encrypted, key, aad)
| Parameter | Type | Description |
|---|---|---|
data |
string | Encrypted data from encrypt.aes |
key |
string | Same key used for encryption |
aad |
string? | Must match AAD used in encryption |
Returns: string, error
ChaCha20-Poly1305 {id="decrypt-chacha20"}
local plaintext, err = crypto.decrypt.chacha20(encrypted, key)
local plaintext, err = crypto.decrypt.chacha20(encrypted, key, aad)
| Parameter | Type | Description |
|---|---|---|
data |
string | Encrypted data from encrypt.chacha20 |
key |
string | Same key used for encryption |
aad |
string? | Must match AAD used in encryption |
Returns: string, error
JWT
Encode
local token, err = crypto.jwt.encode(payload, secret)
local token, err = crypto.jwt.encode(payload, secret, "HS256")
local token, err = crypto.jwt.encode(payload, private_key_pem, "RS256")
| Parameter | Type | Description |
|---|---|---|
payload |
table | JWT claims (_header for custom header) |
key |
string | Secret (HMAC) or PEM private key (RSA) |
alg |
string? | HS256, HS384, HS512, RS256 (default: HS256) |
Returns: string, error
Pass only one of the documented algorithm names. At this runtime pin, an unsupported value passed to encode falls back to HS256 instead of returning an error. Validate any configurable algorithm before this call, and do not copy untrusted fields into _header; in particular, do not let input override reserved JWT headers such as alg.
Verify
local claims, err = crypto.jwt.verify(token, secret)
local claims, err = crypto.jwt.verify(token, secret, "HS256", false)
local claims, err = crypto.jwt.verify(token, public_key_pem, "RS256")
| Parameter | Type | Description |
|---|---|---|
token |
string | JWT token to verify |
key |
string | Secret (HMAC) or PEM public key (RSA) |
alg |
string? | Expected algorithm (default: HS256) |
require_exp |
boolean? | Require an exp claim to be present (default: true); an exp that is present is always validated |
Returns: table, error
Whenever present, exp and nbf are validated against the JWT library's current wall clock, not the workflow time reference. Setting require_exp = false permits a missing exp claim; it does not disable validation of a claim that is present. Do not use either time-dependent result for replay-sensitive workflow control; perform the check in an activity or validate time against an explicitly replay-safe value.
Always pass the algorithm expected by the issuer; verification restricts the token to that exact method. Treat returned claims as authenticated data, not automatically authorized application input, and still validate issuer, audience, subject, and application-specific constraints.
Key Derivation
PBKDF2
local key, err = crypto.pbkdf2(password, salt, iterations, key_length)
local key, err = crypto.pbkdf2(password, salt, iterations, key_length, "sha512")
| Parameter | Type | Description |
|---|---|---|
password |
string | Password/passphrase |
salt |
string | Salt value |
iterations |
integer | Iteration count (max 10,000,000) |
key_length |
integer | Desired key length in bytes |
hash |
string? | sha256 or sha512 (default: sha256) |
Returns: string, error
The derived key is raw bytes. Use a fresh random salt for each stored password verifier and store the salt and work-factor parameters alongside the verifier; the salt need not be secret. Do not use a fixed example salt for production password storage.
Utility
Constant-Time Compare
local equal = crypto.constant_time_compare(a, b)
| Parameter | Type | Description |
|---|---|---|
a |
string | First string |
b |
string | Second string |
Returns: boolean
The result is false when lengths differ. The underlying constant-time comparison guarantee applies to equal-length inputs, so compare fixed-length digests or other same-length secrets.
Errors
| Condition | Kind | Retryable |
|---|---|---|
| Invalid length | errors.INVALID |
no |
| Empty key | errors.INVALID |
no |
| Invalid key size | errors.INVALID |
no |
| Decryption failed | errors.INTERNAL |
no |
| Token expired | errors.INTERNAL |
no |
See Error Handling for working with errors.