@encrypt — AES-256-GCM Encryption
Two-way encryption using AES-256-GCM via the Web Crypto API. Key derived per-field via PBKDF2 from the master encryptionKey. Exposes a .decrypt() method on read, or transparent auto-decryption with :(decrypt=auto).
Syntax
src/interfaces.ts
// Manual decryption — raw ciphertext with .decrypt()
// @encrypt
encrypted?: string;
// Auto-decrypted on read — returns plain string
// @encrypt:(decrypt=auto)
autoDecrypted?: string;Config Requirement
src/db.ts
const orm = new ORMManager({
// ...
encryptionKey: process.env.ENCRYPTION_KEY, // min 32 characters
});Stored Format
(database column value)
aes256gcm$<iterations>$<iv>$<ciphertext>$<authTag>
// Example:
aes256gcm$600000$rb9p9H35g0XdRRYA$lNbL7X2zLtGe0duz+xI=$DQsjtq10eZgOP7M/0XZ9YA==- iterations — PBKDF2 iterations used for key derivation (inline-encoded for future-proofing)
- iv — 12-byte random nonce, base64-encoded
- ciphertext — AES-256-GCM encrypted payload
- authTag — 16-byte GCM authentication tag
Usage
src/auth.ts
// Insert — auto-encrypted before write
await User.insert({
name: "Alice",
encrypted: "sensitive-data",
});
// Fetch — raw ciphertext with .decrypt()
const user = await User.get({ name: "Alice" });
console.log(user.encrypted);
// "aes256gcm$600000$..."
const plaintext = await user.encrypted.decrypt();
console.log(plaintext);
// "sensitive-data"Auto-decrypt variant
src/auth.ts
// Interface
// @encrypt:(decrypt=auto)
autoDecrypted?: string;
// Usage — transparent, reads as plain string
const user = await User.get({ name: "Alice" });
console.log(user.autoDecrypted);
// "auto-decrypted-value" (plain string, no .decrypt() needed)Errors
| Error | Cause | Fix |
|---|---|---|
[@encrypt] field "X" requires encryptionKey in ORMManager config | Missing encryptionKey option | Set encryptionKey (min 32 chars) in the ORM constructor |
Expected 'aes256gcm$iv$ct$tag' format, got '...' | Stored value doesn't start with aes256gcm$ | Verify the column was written by @encrypt |
[@encrypt] failed to decrypt field "X": ... | Decryption failure (wrong key, tampered data, format mismatch) | Check encryptionKey matches the one used at write time |
Conventions
encryptionKeymust be at least 32 characters — store inENCRYPTION_KEYenv var- Use manual
.decrypt()when you need lazy/conditional decryption - Use
:(decrypt=auto)for fields that are always read in plaintext - The key is derived per-field (
PBKDF2(masterKey, fieldName)), so each field uses a unique encryption key - Key derivation is memoized in a
Map— repeated encrypt/decrypt on the same field reuse the cachedCryptoKey
Gotcha: .decrypt() returns a wrapper object
The field value at runtime is not a plain string — it's an object with .decrypt(), .toString(), .valueOf(), and [Symbol.toPrimitive](). This means:
typeof field === "string"isfalse(it's"object")field === "plaintext"isfalseeven if the decrypted value matches- Template literals and string concatenation work fine (
`${field}`,"" + field) — these triggertoString() - Comparison trick:
String(field) === "plaintext"orfield.toString() === "plaintext"