Encryption, hashing, CSRF protection, and secure operations.
Uses the Web Crypto API for:
- Hybrid RSA-OAEP + AES-256-GCM encryption for PII at rest
- HMAC-SHA256 for webhooks and CSRF tokens
- PBKDF2-SHA256 password hashing
- Constant-time comparison for timing-safe checks
AES-GCM decrypt with raw key bytes. The mirror of aesGcmEncryptBytes; note it measures the ciphertext, which carries the tag the plaintext does not.
AES-GCM decrypt raw data with an imported key
AES-GCM encrypt with raw key bytes, using whichever implementation is faster for this payload.
AES-GCM encrypt raw data with an imported key, returning IV and ciphertext
AES-GCM encrypt a text string with an imported key
Convert standard base64 to base64url (no padding). Works on both strings and Uint8Array (bytes are first encoded to base64).
Compute HMAC-SHA256 using Web Crypto API, returning raw ArrayBuffer
Concatenate byte arrays into one array.
Constant-time compare of two code sequences, given their lengths and a per-index code reader for each. Walks the longer sequence and folds every difference into one flag with XOR, so no early return leaks a length or the position of the first mismatch. Callers supply the code source (UTF-8 bytes, UTF-16 char codes, …), keeping this the single constant-time comparison loop.
Constant-time string comparison to prevent timing attacks Always iterates over the longer string and XORs the lengths so that different-length inputs don't leak via an early return.
Constant-time comparison for Uint8Arrays of equal length Caller must ensure arrays have the same length (validated by verifyPassword)
Default message for invalid/expired CSRF form submissions (request-scoped).
Decrypt a string value encrypted with encrypt() Expects format: enc:1:$base64iv:$base64ciphertext
Decrypt binary data encrypted with encryptBytes(). Expects ENCB binary format: magic + version + IV + ciphertext.
Decrypt data with a symmetric key
Decrypt a value encrypted with encryptWithOwnerKey, using the owner's private key (obtained from the session in admin views).
Legacy (v1) KEK derived from the stored password hash. Retained only to unwrap and migrate existing wrapped_data_keys — new wraps use deriveKEKFromPassword. Salt prefix is empty so this stays byte-compatible with keys wrapped before the v2 split.
Password-bound (v2) KEK derived from the raw password. Because the password is never stored, a database dump plus DB_ENCRYPTION_KEY cannot unwrap the DATA_KEY — this is what binds attendee PII at rest to the account password.
Encrypt a string value using AES-256-GCM via node:crypto (faster than Web Crypto for the small payloads this handles; output stays interoperable). Returns format: enc:1:$base64iv:$base64ciphertext Note: ciphertext includes the GCM auth tag appended.
Encrypt binary data with AES-256-GCM using compact binary format. Output: ENCB + version byte + 12-byte IV + ciphertext (with GCM auth tag). Overhead is only 33 bytes (vs ~76% bloat in the legacy text format).
Encrypt data with a symmetric key (for wrapping private key with DATA_KEY)
Encrypt a value with the site owner's public key (hybrid RSA+AES). Only the owner's password-derived private key can decrypt it. Used for attendee PII, email-preference blobs, and bulk-email drafts/templates. Can be called without authentication (e.g. from public ticket forms).
Format IV + ciphertext as a prefixed base64 string
Convert base64 string to Uint8Array
Convert a base64url string (no padding) back to a Uint8Array — the inverse of toBase64Url.
Generate a random 256-bit symmetric key for data encryption
Generate an RSA key pair for asymmetric encryption Returns { publicKey, privateKey } as exportable JWK strings
Generate a cryptographically secure random token Uses Web Crypto API getRandomValues
Generate a 5-byte uppercase hex ticket token for public ticket URLs
Get the most recently generated CSRF token (for synchronous JSX rendering)
Raw 256-bit encryption key bytes, decoded once from DB_ENCRYPTION_KEY
Get the encryption key bytes from environment variable (sync validation only) Expects DB_ENCRYPTION_KEY to be a base64-encoded 256-bit (32 byte) key
Derive the private key from session credentials Used to decrypt attendee PII in admin views Results are cached per session token for 10 seconds
Generate random bytes using Web Crypto API
Hash a password using PBKDF2 Returns format: pbkdf2:iterations:$base64salt:$base64hash
Hash a session token using SHA-256 Used to store session lookups without exposing the actual token
HMAC-SHA256 hash using DB_ENCRYPTION_KEY Used for blind indexes and hashing limited keyspace values Returns deterministic output for same input (unlike encrypt)
Hex-encoded HMAC-SHA256 of a UTF-8 message under the given secret.
Convert ArrayBuffer to base64 string
Convert ArrayBuffer to hex string
Decrypt data using hybrid encryption Expects format: hyb:1:$base64WrappedKey:$base64iv:$base64ciphertext Results are cached in a bounded LRU (ciphertext -> plaintext)
Encrypt data using hybrid encryption (RSA + AES)
Import a CryptoKey from DB_ENCRYPTION_KEY.
Import a private key from JWK string
Import a public key from JWK string
Check whether a token uses the signed format
Parse a prefixed encrypted payload into IV and ciphertext bytes. Validates the prefix and separator; throws on invalid format.
Run a function within a CSRF-token scope (one container per request)
Constant-time string comparison (over UTF-16 char codes) to prevent timing
attacks. Shares the one constant-time loop in constantTimeCodesEqual.
Explicitly set or clear the encryption key for testing. Bypasses Deno.env to avoid races between parallel test workers. Automatically clears all crypto caches (encryption, HMAC, and any registered via onEncryptionKeyChange).
Explicitly enable/disable fast PBKDF2 for testing without env var races
Explicitly set RSA key size for testing without env var races
Create a signed CSRF token: s1.{timestamp}.{nonce}.{hmac}
Decrypt a prefixed AES-GCM payload with the given key.
Encrypt plaintext with an AES-GCM key, returning prefixed format: enc:1:$base64iv:$base64ciphertext
Convert Uint8Array to base64 string
Convert Uint8Array to base64url string (no padding)
Unwrap a symmetric key Expects format: wk:1:$base64iv:$base64wrapped
Unwrap a key using a session token
Unwrap a session's DATA_KEY from its token. An authenticated session that reaches a data-key operation always carries a wrapped data key, so a missing one is a broken invariant — throw rather than invent a key.
Validate encryption key is present and valid Call this on startup to fail fast if key is missing
Verify a password against a hash Uses constant-time comparison to prevent timing attacks
Verify a signed CSRF token's signature and expiry
Wrap a DATA_KEY under the password-bound (v2) KEK in one step. The single place new wrapped_data_keys are produced — setup, login migration, invite acceptance, password change, and superuser creation all go through here, so the derive-then-wrap pair lives in exactly one spot.
Wrap a symmetric key with another key using AES-GCM Returns format: wk:1:$base64iv:$base64wrapped
Wrap a key using a session token (derives a wrapping key from the token)
Hands back the Web Crypto key the large-payload path needs. Nothing imports a key until a payload is actually big enough to want one.
Key length AES-256 takes, in bytes.
Encryption format version prefix Format: enc:1:$base64iv:$base64ciphertext
Prefix tagging a hybrid (RSA+AES) ciphertext, e.g. owner-key activity-log messages and attendee PII. Distinguishes them from env-key ENCRYPTION_PREFIX values so a decrypt path can route by format.
Usage
import * as mod from "docs/crypto.ts";