Encryption Primitives
The advanced, synchronous Cipher interface and its AesGcmCipher (AES-256-GCM, AesGcmCipherOptions) implementation, exposed for custom Provider authors who seal their own records.
Cipher is the single abstract base every encryption module in the library extends, and AesGcmCipher is its authenticated symmetric implementation. Both are exported for one advanced use: writing a custom Provider that seals its own records and wants the same primitive the built-in providers use. If you are only opening a workspace, you never touch these — a provider handles sealing for you.
import { AesGcmCipher } from "wative-core";
const cipher = new AesGcmCipher();
// A 32-byte AES-256 key you derived elsewhere (see Key Derivation).
const blob = cipher.encrypt(plaintext, key); // Uint8Array: [IV | tag | ciphertext]
const back = cipher.decrypt(blob, key); // the original plaintextThe Cipher contract
Cipher is deliberately minimal and synchronous — every operation returns bytes directly, never a promise. Each subclass owns its own option type; there is no shared options bag on the base.
abstract class Cipher {
readonly id: string;
abstract encrypt(input: Uint8Array, secret: Uint8Array): Uint8Array;
abstract decrypt(input: Uint8Array, secret: Uint8Array): Uint8Array;
}| Member | Meaning |
|---|---|
id | A stable string identifier for the algorithm, e.g. "aes-gcm" or "argon2id". |
encrypt | The forward operation. For a symmetric cipher this encrypts; for a KDF it derives (with input the password and secret the salt). |
decrypt | The inverse. Symmetric ciphers decrypt; one-way KDFs throw WativeError("ALGORITHM_IRREVERSIBLE"). |
The synchronous contract is what lets a provider's seal path stay synchronous once its key is in hand. Argon2Kdf is another Cipher — the KDF side of the same interface, where decrypt is the irreversible case.
AesGcmCipher
AesGcmCipher is AES-256-GCM: authenticated encryption with a random 96-bit IV per call. Its id is "aes-gcm". The key must be exactly 32 bytes (AES-256); anything else throws.
The output of encrypt — and the expected input to decrypt — is a single Uint8Array in this wire format:
[ 12 bytes IV | 16 bytes tag | N bytes ciphertext ]| Method | Signature | Notes |
|---|---|---|
encrypt | encrypt(plaintext: Uint8Array, key: Uint8Array): Uint8Array | Generates a fresh random IV and returns IV || tag || ciphertext. |
decrypt | decrypt(blob: Uint8Array, key: Uint8Array): Uint8Array | Splits the blob, verifies the tag, returns the plaintext. |
Error behavior is explicit:
| Condition | Error |
|---|---|
| Key is not 32 bytes (on encrypt) | WativeError("ENCRYPT_FAILED") |
| Key is not 32 bytes (on decrypt) | WativeError("DECRYPT_FAILED") |
| Blob shorter than IV + tag | WativeError("DECRYPT_FAILED") |
| Authentication tag does not verify | WativeError("DECRYPT_FAILED") |
GCM security depends on never reusing an IV with the same key. AesGcmCipher draws a fresh random IV inside every encrypt call for you — do not attempt to supply your own.
AesGcmCipherOptions
The constructor accepts an options object, but this build pins both lengths — they exist to make the format explicit, not to be varied.
interface AesGcmCipherOptions {
ivLength?: number; // must be 12
tagLength?: number; // must be 16
}| Option | Allowed value | If different |
|---|---|---|
ivLength | 12 | Constructor throws WativeError("PROVIDER_IO"). |
tagLength | 16 | Constructor throws WativeError("PROVIDER_IO"). |
In practice you construct it with no arguments: new AesGcmCipher().
When you need this
Reach for these primitives only when you are authoring a custom Provider and want to seal records yourself with the same authenticated cipher the built-in providers use. A typical flow derives a 32-byte key with Argon2Kdf, then seals each record with AesGcmCipher.encrypt and opens it with AesGcmCipher.decrypt.
Both classes are built on the audited @noble libraries — AesGcmCipher on @noble/ciphers (with @noble/hashes supplying the random IV), and Argon2Kdf on the same family. No native binding is required for either.