page wrnexusencryption { seo { title = "@wrnexus/encryption" description = "Hashing, HMAC, authenticated encryption, and key derivation." } view {
W WRNexusJS
Security · Package reference

@wrnexus/encryption

Hashing, HMAC, authenticated encryption, and key derivation.

v0.5.10Private registrySecurity

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/encryption@0.5.10

Request preview access. Never put registry tokens in source control.

Dependency-free crypto helpers for WRNexusJS: authenticated symmetric encryption (AES-256-GCM), hashing, and HMAC signing.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

Overview

This package provides small, focused cryptographic primitives for server-side use: encrypting secrets/tokens/database fields at rest with AES-256-GCM, deriving keys from passwords via PBKDF2, computing SHA-256 digests, and signing/verifying payloads with HMAC-SHA256. It is built entirely on the standard Web Crypto API (crypto.subtle) plus btoa/atob and TextEncoder/TextDecoder — no third-party dependencies. Reach for it whenever you need to protect sensitive values or verify webhook signatures. All functions are async (Web Crypto is promise-based).

bun add @wrnexus/encryption
Private package — the machine must be authenticated to the wrnexus npm org
(a read token in ~/.npmrc). Requires Bun (Node is not supported).

API

All keys are exchanged as base64 strings and all digests/signatures as hex strings.

ExportSignatureDescription
generateKey() => Promise<string>Generate a random 256-bit AES key, base64-encoded. Store it as a secret.
deriveKey(password: string, salt: string) => Promise<string>Derive a base64 AES-256 key from a password + salt using PBKDF2 (100,000 iterations, SHA-256).
encrypt(plaintext: string, key: string) => Promise<string>AES-256-GCM encrypt a string. Returns base64 of iv(12 bytes) ‖ ciphertext+tag. A fresh random IV is used each call.
decrypt(payload: string, key: string) => Promise<string>Decrypt a value produced by encrypt. Throws if the key is wrong or the data was tampered with.
sha256(data: string) => Promise<string>SHA-256 hex digest of a string (e.g. content hashing, dedup keys).
hmacSign(data: string, secret: string) => Promise<string>HMAC-SHA256 hex signature of data with secret (e.g. signing webhooks).
hmacVerify(data: string, secret: string, signature: string) => Promise<boolean>Constant-time verify of an HMAC-SHA256 hex signature.

Notes:

  • generateKey produces a 32-byte (256-bit) key via crypto.getRandomValues.
  • encrypt/decrypt require a base64-encoded 256-bit key; anything else throws "Encryption key must be a base64 256-bit key".
  • decrypt throws "Invalid ciphertext" if the payload is shorter than the 12-byte IV, and the underlying Web Crypto call throws on any authentication (tag) mismatch.
  • hmacVerify compares in constant time (length check plus XOR accumulation) to avoid timing leaks.

Usage

Symmetric encryption of a secret at rest:

import { generateKey, encrypt, decrypt } from "@wrnexus/encryption";

const key = await generateKey(); // store this safely (env/secret manager)

const box = await encrypt("card #1234", key); // opaque base64 string, safe to persist
const plain = await decrypt(box, key); // "card #1234"

Deriving a key from a user password instead of a random key:

import { deriveKey, encrypt } from "@wrnexus/encryption";

const key = await deriveKey("correct horse battery staple", "per-user-salt");
const box = await encrypt("secret note", key);

Hashing and webhook signature verification:

import { sha256, hmacSign, hmacVerify } from "@wrnexus/encryption";

const digest = await sha256("some content"); // 64-char hex string

const signature = await hmacSign(rawBody, webhookSecret);
const ok = await hmacVerify(rawBody, webhookSecret, incomingSignatureHeader);
if (!ok) throw new Error("Invalid webhook signature");

Requirements / Notes

  • Bun-only. Relies on the Web Crypto API (crypto.subtle, crypto.getRandomValues) and the global btoa/atob, TextEncoder/TextDecoder — all available in Bun's runtime.
  • No dependencies. The package has an empty dependency set; nothing is bundled beyond standard runtime APIs.
  • Algorithms: AES-256-GCM (encryption), PBKDF2 with 100k SHA-256 iterations (key derivation), SHA-256 (digest), HMAC-SHA256 (signing).
  • Keep generated/derived keys and HMAC secrets out of source control; treat them as first-class secrets.

Complete TypeScript API

Generated from the exact installed package declarations.

interface EncryptionKey {
    id: string;
    secret: string;
    active?: boolean;
    createdAt?: number;
}
interface EncryptionKeyring {
    active(): EncryptionKey;
    get(id: string): EncryptionKey | undefined;
    keys(): EncryptionKey[];
    rotate(key?: EncryptionKey): Promise<EncryptionKey>;
    remove(id: string): boolean;
}
declare function createKeyring(initial: EncryptionKey[]): EncryptionKeyring;
/** Versioned payload: `wrn1.<key-id>.<aes-gcm-payload>`. */
declare function seal(plaintext: string, keyring: EncryptionKeyring): Promise<string>;
declare function open(sealed: string, keyring: EncryptionKeyring): Promise<string>;
declare function sealedKeyId(sealed: string): string | null;
declare function needsRotation(sealed: string, keyring: EncryptionKeyring): boolean;

/**
 * @wrnexus/encryption — authenticated symmetric encryption (AES-256-GCM) via
 * WebCrypto, dependency-free. Use it to encrypt secrets, tokens, or database
 * fields at rest.
 *
 *   const key = await generateKey();                 // store this safely
 *   const box = await encrypt("card #1234", key);    // opaque base64 string
 *   const plain = await decrypt(box, key);           // "card #1234"
 *
 * A key derived from a password (PBKDF2) is also supported via `deriveKey`.
 */
/** SHA-256 hex digest of a string (e.g. content hashing, dedup keys). */
declare function sha256(data: string): Promise<string>;
/** HMAC-SHA256 hex signature of `data` with `secret` (e.g. signing webhooks). */
declare function hmacSign(data: string, secret: string): Promise<string>;
/** Constant-time verify of an HMAC-SHA256 signature. */
declare function hmacVerify(data: string, secret: string, signature: string): Promise<boolean>;
/** Generate a random 256-bit key, base64-encoded. Store it as a secret. */
declare function generateKey(): Promise<string>;
/**
 * Encrypt a string. Output is base64 of `iv(12) || ciphertext+tag`, safe to
 * store or transmit. Each call uses a fresh random IV.
 */
declare function encrypt(plaintext: string, key: string): Promise<string>;
/** Decrypt a value produced by `encrypt`. Throws if the key is wrong or data tampered. */
declare function decrypt(payload: string, key: string): Promise<string>;
/** Derive a base64 AES key from a password + salt (PBKDF2, 100k iterations). */
declare function deriveKey(password: string, salt: string): Promise<string>;

export { type EncryptionKey, type EncryptionKeyring, createKeyring, decrypt, deriveKey, encrypt, generateKey, hmacSign, hmacVerify, needsRotation, open, seal, sealedKeyId, sha256 };

Examples

Copy-ready examples from the installed package documentation.

Symmetric encryption of a secret at rest

import { generateKey, encrypt, decrypt } from "@wrnexus/encryption";

const key = await generateKey(); // store this safely (env/secret manager)

const box = await encrypt("card #1234", key); // opaque base64 string, safe to persist
const plain = await decrypt(box, key); // "card #1234"

Deriving a key from a user password instead of a random key

import { deriveKey, encrypt } from "@wrnexus/encryption";

const key = await deriveKey("correct horse battery staple", "per-user-salt");
const box = await encrypt("secret note", key);

Hashing and webhook signature verification

import { sha256, hmacSign, hmacVerify } from "@wrnexus/encryption";

const digest = await sha256("some content"); // 64-char hex string

const signature = await hmacSign(rawBody, webhookSecret);
const ok = await hmacVerify(rawBody, webhookSecret, incomingSignatureHeader);
if (!ok) throw new Error("Invalid webhook signature");
} }