# "Encryption & Signing" _Path: en/lua/security/crypto_ > "Cryptographic operations including encryption, HMAC, JWT, and key derivation. Adapted for workflows." ## Table of Contents - Encryption & Signing ## Content # 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 ```lua local crypto = require("crypto") ``` ### Random Bytes ```lua 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 ```lua 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 ```lua local id, err = crypto.random.uuid() ``` **Returns:** `string, error` ### HMAC-SHA256 ```lua 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 ```lua local hex, err = crypto.hmac.sha512(key, data) ``` | Parameter | Type | Description | |-----------|------|-------------| | `key` | string | HMAC key | | `data` | string | Data to authenticate | **Returns:** `string, error` ### AES-GCM {id="encrypt-aes-gcm"} ```lua 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"} ```lua 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) ### AES-GCM {id="decrypt-aes-gcm"} ```lua 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"} ```lua 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` ### Encode ```lua 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 ```lua 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. ### PBKDF2 ```lua 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. ### Constant-Time Compare ```lua 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](lua/core/errors.md) for working with errors. ## Navigation Previous: "Security & Access Control" (lua/security/security) Next: "Hash Functions" (lua/security/hash)