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.

aes-gcm.ts
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 plaintext

The 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.

Cipher
abstract class Cipher {
  readonly id: string;
  abstract encrypt(input: Uint8Array, secret: Uint8Array): Uint8Array;
  abstract decrypt(input: Uint8Array, secret: Uint8Array): Uint8Array;
}
MemberMeaning
idA stable string identifier for the algorithm, e.g. "aes-gcm" or "argon2id".
encryptThe forward operation. For a symmetric cipher this encrypts; for a KDF it derives (with input the password and secret the salt).
decryptThe 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:

wire-format
[ 12 bytes IV | 16 bytes tag | N bytes ciphertext ]
MethodSignatureNotes
encryptencrypt(plaintext: Uint8Array, key: Uint8Array): Uint8ArrayGenerates a fresh random IV and returns IV || tag || ciphertext.
decryptdecrypt(blob: Uint8Array, key: Uint8Array): Uint8ArraySplits the blob, verifies the tag, returns the plaintext.

Error behavior is explicit:

ConditionError
Key is not 32 bytes (on encrypt)WativeError("ENCRYPT_FAILED")
Key is not 32 bytes (on decrypt)WativeError("DECRYPT_FAILED")
Blob shorter than IV + tagWativeError("DECRYPT_FAILED")
Authentication tag does not verifyWativeError("DECRYPT_FAILED")

AesGcmCipherOptions

The constructor accepts an options object, but this build pins both lengths — they exist to make the format explicit, not to be varied.

AesGcmCipherOptions
interface AesGcmCipherOptions {
  ivLength?: number;   // must be 12
  tagLength?: number;  // must be 16
}
OptionAllowed valueIf different
ivLength12Constructor throws WativeError("PROVIDER_IO").
tagLength16Constructor 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.

See also

Last updated on