@wrnexus/encryption
Dependency-free crypto helpers for WrNexus: authenticated symmetric encryption (AES-256-GCM), hashing, and HMAC signing.
Part of the WrNexus 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).
Installation
bun add @wrnexus/encryption
Private package — the machine must be authenticated to the
wrnexusnpm 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.
| Export | Signature | Description |
|---|---|---|
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:
generateKeyproduces a 32-byte (256-bit) key viacrypto.getRandomValues.encrypt/decryptrequire a base64-encoded 256-bit key; anything else throws"Encryption key must be a base64 256-bit key".decryptthrows"Invalid ciphertext"if the payload is shorter than the 12-byte IV, and the underlying Web Crypto call throws on any authentication (tag) mismatch.hmacVerifycompares 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 globalbtoa/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.