# crypto — applied cryptography adapter pack

Applied cryptography toolkit built on Node's native node:crypto (zero npm deps). Symmetric encryption: AES-256-GCM authenticated encrypt/decrypt of text with a password-derived key (scrypt KDF...


Real, modern cryptography for the Leumas ecosystem, built entirely on Node's native
`node:crypto`. **Zero npm dependencies** — pure Node built-ins, so it loads everywhere with no
install step.

This is the "do actual crypto" pack: encrypt sensitive data, generate keypairs, sign & verify
payloads, mint and validate JSON Web Tokens, derive keys from passwords, and generate secure
secrets.

## Tools

| Tool | What it does |
|---|---|
| `encrypt` | AES-256-GCM authenticated encryption of text with a password. Derives a 32-byte key via scrypt from the password + a random salt, encrypts under a random IV. Returns `{ salt, iv, authTag, ciphertext }` (all base64). |
| `decrypt` | Reverses `encrypt`. Needs the password + the `salt/iv/authTag/ciphertext`. Throws on wrong password or tampered data (GCM is authenticated). |
| `generateKeyPair` | Generate an `rsa` (default 2048-bit), `ec` (default P-256), `ed25519`, or `ed448` keypair. Returns PEM `publicKey` + `privateKey`. |
| `publicKeyFromPrivate` | Derive/export the PEM public key from a private key. |
| `sign` | Digital signature of `data` with a PEM private key (RSA/EC use a hash, default sha256; Ed keys sign raw). |
| `verify` | Verify a signature against `data` with the matching public key → `{ valid }`. |
| `jwtSign` | Sign a JWT. HS* use a shared `secret`; RS*/PS*/ES* use a PEM `privateKey`. Auto-adds `iat`/`exp` (`expiresIn` accepts `3600`, `"15m"`, `"2h"`, `"7d"`). |
| `jwtVerify` | Verify a JWT signature + `exp`/`nbf`. Throws `status:401` on invalid/expired. Supports an `algorithms` allow-list. |
| `jwtDecode` | Decode header + payload **without** verifying (inspection only — never trust for auth). |
| `hmac` | Keyed MAC of arbitrary data (raw or encoded key, choose hash + output encoding). |
| `hash` | One-shot cryptographic hash of arbitrary data/bytes (sha256 default). |
| `pbkdf2` | PBKDF2 key derivation (iterations + salt). Auto-generates + returns a salt if none given. |
| `scrypt` | scrypt memory-hard key derivation (`N`/`r`/`p`). Auto-generates + returns a salt if none given. |
| `randomBytes` | CSPRNG bytes in the chosen encoding (base64 default). |
| `randomToken` | URL-safe random token (base64url) for API keys, nonces, reset tokens. |
| `uuid` | An RFC-4122 v4 UUID from the CSPRNG. |
| `timingSafeEqual` | Constant-time comparison of two secrets (resists timing attacks). |
| `listHashAlgorithms` | Hash algorithms available on this Node build. |

## Usage

Every tool takes ONE args object (an HTTP POST body maps 1:1). Through the registry:
`registry.run('crypto', '<tool>', args)`.

```js
import crypto from './index.js';
const { adapters } = crypto;

// Encrypt then decrypt
const box = adapters.encrypt({ text: 'top secret', password: 'hunter2' });
const { text } = adapters.decrypt({ password: 'hunter2', ...box });
// text === 'top secret'

// Keypair + sign + verify
const { publicKey, privateKey } = adapters.generateKeyPair({ type: 'ec' });
const { signature } = adapters.sign({ data: 'invoice#42', privateKey });
const { valid } = adapters.verify({ data: 'invoice#42', signature, publicKey }); // valid === true

// JWT (HS256)
const { token } = adapters.jwtSign({ payload: { sub: 'u_1', role: 'admin' }, secret: 's3cr3t', expiresIn: '2h' });
const { payload } = adapters.jwtVerify({ token, secret: 's3cr3t' });

// Key derivation + secure token
const kdf = adapters.pbkdf2({ password: 'hunter2' }); // returns salt + key
const { token: apiKey } = adapters.randomToken({ length: 32 });
```

## DRY boundary — what does NOT live here

This pack is deliberately scoped so it does not overlap its neighbours:

- **`a-text`** owns string→string primitives on **text**: `MD5Hash`, `SHA256Hash`, `SHA512Hash`,
  a hex-digest `HMAC`, `Base64Encode`/`Base64Decode`, and classical ciphers (`CaesarCipher`,
  `Rot13`, `VigenereEncode`/`Decode`). Those are deterministic display/obfuscation helpers with no
  key management. Reach for a-text when you just want the hex hash of a string.
- **`a-file-actions`** owns AES encryption of **files** on disk.
- **`crypto` (this pack)** owns **real applied cryptography on data and keys**: password-based
  AES-256-GCM of arbitrary text (authenticated, salted, random-IV), RSA/EC/Ed keypairs, digital
  signatures, JWTs, KDFs (PBKDF2/scrypt), and CSPRNG token/byte/compare primitives.

The small overlaps are intentional and distinct: `crypto.hmac`/`crypto.hash` are the general
data/bytes primitives (any key encoding, any output encoding) used to build signing flows, whereas
a-text's `HMAC`/`*Hash` are hex-only convenience wrappers for text. Use a-text for quick text
digests; use this pack when you're actually securing data, keys, or tokens.

## Notes

- Binary outputs default to base64 (base64url for `randomToken` and JWT segments). All results are
  plain JSON-serializable objects.
- `encrypt` uses scrypt (`N=16384, r=8, p=1`) → AES-256-GCM; the returned `salt`/`iv`/`authTag`
  make each ciphertext self-describing for `decrypt`.
- JWT algorithms: `HS256/384/512`, `RS256/384/512`, `PS256/384/512`, `ES256/384/512`. ES* use
  IEEE-P1363 (r‖s) signatures per the JWT spec.


---
Source: shared/engines/adapters/domain/crypto/README.md
Canonical: https://docs.leumas.tech/p/adapters/domain/crypto
