cipher-kit
v3.0.0
Published
π Secure, Modern, and Cross-Platform Cryptography Helpers for Web, Node.js, Deno, Bun, and Cloudflare Workers
Maintainers
Readme
Highlights β¨
- Zero runtime dependencies β fully self-contained
- AES-256-GCM encryption with HKDF key derivation
- PBKDF2 password hashing with 320K iterations and constant-time verification
- Type-safe API with throwing and
Result<T>variants for every operation - Tree-shakable β import from
cipher-kit/nodeorcipher-kit/web-api - Cross-platform β Node.js, Deno, Bun, Cloudflare Workers, and all modern browsers
Installation π¦
Requires Node.js >= 18.
npm install cipher-kit
# or
pnpm add cipher-kitQuick Start π
// Node.js (synchronous)
import { createSecretKey, encrypt, decrypt } from "cipher-kit/node";
const secretKey = createSecretKey("my-32-char-high-entropy-secret!!");
const encrypted = encrypt("Hello, World!", secretKey);
const decrypted = decrypt(encrypted, secretKey); // "Hello, World!"// Web / Deno / Bun / Cloudflare Workers (async)
import { createSecretKey, encrypt, decrypt } from "cipher-kit/web-api";
const secretKey = await createSecretKey("my-32-char-high-entropy-secret!!");
const encrypted = await encrypt("Hello, World!", secretKey);
const decrypted = await decrypt(encrypted, secretKey); // "Hello, World!"Imports π₯
Three import patterns are available:
// 1. Root export β both kits via namespace objects
import { nodeKit, webKit } from "cipher-kit";
nodeKit.encrypt("data", key);
await webKit.encrypt("data", key);
// 2. Direct Node.js import (synchronous API)
import { createSecretKey, encrypt, decrypt } from "cipher-kit/node";
// 3. Direct Web Crypto import (async API)
import { createSecretKey, encrypt, decrypt } from "cipher-kit/web-api";The root export also re-exports shared utilities: stringifyObj, tryStringifyObj, parseToObj, tryParseToObj, ENCRYPTED_REGEX, matchEncryptedPattern, and all types. All entry points expose the same utilities.
API Reference π
createSecretKey / tryCreateSecretKey
Derives a secret key from a high-entropy secret using HKDF.
import { createSecretKey, tryCreateSecretKey } from "cipher-kit/node";
// Default β AES-256-GCM with SHA-256 HKDF
const secretKey = createSecretKey("my-32-char-high-entropy-secret!!");
// Custom options
const customKey = createSecretKey("my-32-char-high-entropy-secret!!", {
algorithm: "aes128gcm",
digest: "sha512",
salt: "my-unique-app-salt",
});
// Safe variant β returns Result<T> instead of throwing
const result = tryCreateSecretKey("my-32-char-high-entropy-secret!!");
if (result.success) {
console.log(result.result); // the derived NodeSecretKey / WebSecretKey
}Options:
| Option | Type | Default | Description |
| ------------- | ----------------------------------------------- | -------------- | -------------------------------------------------- |
| algorithm | "aes256gcm" | "aes192gcm" | "aes128gcm" | "aes256gcm" | Encryption algorithm |
| digest | "sha256" | "sha384" | "sha512" | "sha256" | HKDF digest algorithm |
| salt | string | "cipher-kit" | HKDF salt (min 8 chars) |
| info | string | "cipher-kit" | HKDF context info |
| extractable | boolean | false | Web CryptoKey extractable flag (no effect on Node) |
Security: HKDF is a key expansion function β it does not provide brute-force resistance. The
secretmust be high-entropy (e.g., a 256-bit random key). For human-chosen passwords, usehashPasswordinstead.The default
saltis"cipher-kit". Two deployments using the same secret and default salt will derive identical keys. For isolation between environments, provide a uniquesaltper deployment (e.g.,salt: "prod-us-east-1").
encrypt / decrypt / tryEncrypt / tryDecrypt
Encrypts and decrypts UTF-8 strings using the provided secret key. Output encoding defaults to base64url; pass { outputEncoding: "hex" } or "base64" to change it.
import { createSecretKey, encrypt, decrypt, tryEncrypt } from "cipher-kit/node";
const secretKey = createSecretKey("my-32-char-high-entropy-secret!!");
const encrypted = encrypt("Hello, World!", secretKey);
const decrypted = decrypt(encrypted, secretKey); // "Hello, World!"
// Hex encoding
const hex = encrypt("Hello, World!", secretKey, { outputEncoding: "hex" });
decrypt(hex, secretKey, { inputEncoding: "hex" }); // "Hello, World!"
// Safe variant
const result = tryEncrypt("Hello, World!", secretKey);
if (result.success) {
console.log(result.result); // encrypted string
}Wire format: Both platforms output
iv.cipher.tag.(3 dot-separated segments with trailing dot). The format is cross-platform compatible β data encrypted on Node can be decrypted on Web and vice versa.Nonce exhaustion: AES-GCM uses random 96-bit IVs. Rotate keys before ~2^32 encryptions with the same key to avoid nonce collision.
encryptObj / decryptObj / tryEncryptObj / tryDecryptObj
Encrypts and decrypts plain objects (POJOs). Class instances, Maps, Sets, etc. are rejected.
import { createSecretKey, encryptObj, decryptObj } from "cipher-kit/node";
const key = createSecretKey("my-32-char-high-entropy-secret!!");
const encrypted = encryptObj({ user: "Alice", role: "admin" }, key);
const obj = decryptObj<{ user: string; role: string }>(encrypted, key);
console.log(obj.user); // "Alice"hash / tryHash
Hashes a UTF-8 string using the specified digest algorithm. Not suitable for passwords β use hashPassword instead.
import { hash } from "cipher-kit/node";
const hashed = hash("Hello, World!"); // SHA-256, base64url
const hexHash = hash("Hello, World!", { digest: "sha512", outputEncoding: "hex" });Options: digest ("sha256" | "sha384" | "sha512", default "sha256"), outputEncoding ("base64url" | "base64" | "hex", default "base64url").
hashPassword / tryHashPassword / verifyPassword / tryVerifyPassword
Hashes passwords using PBKDF2 (320K iterations by default) with constant-time verification.
import { hashPassword, verifyPassword } from "cipher-kit/node";
const { result, salt } = hashPassword("user-password");
// Store result and salt in your database
verifyPassword("user-password", result, salt); // true
verifyPassword("wrong-password", result, salt); // false
// Custom options
const custom = hashPassword("user-password", {
digest: "sha256",
iterations: 500_000,
saltLength: 32,
});hashPassword options: digest (default "sha512"), outputEncoding (default "base64url"), saltLength (default 16, min 8), iterations (default 320000, min 100000), keyLength (default 64, min 16).
verifyPassword options: digest (default "sha512"), inputEncoding (default "base64url"), iterations (default 320000), keyLength (default 64). Must match the values used during hashing.
Note: Node uses
crypto.timingSafeEqualfor constant-time comparison. The Web implementation uses a best-effort full-loop XOR pattern since the Web Crypto API does not expose atimingSafeEqualequivalent.Unicode normalization: All secret and password inputs are NFKC-normalized before processing. This means that equivalent Unicode representations (e.g.,
"cafΓ©"composed vs. decomposed) produce identical keys and hashes. This is the recommended approach per NIST SP 800-63B.
generateUuid / tryGenerateUuid
Generates a cryptographically random UUID (v4). Synchronous on both platforms.
import { generateUuid } from "cipher-kit/node";
const id = generateUuid(); // "550e8400-e29b-41d4-a716-446655440000"Encoding Utilities
Convert between strings and bytes, or re-encode between formats. Synchronous on both platforms.
import { convertStrToBytes, convertBytesToStr, convertEncoding } from "cipher-kit/node";
const bytes = convertStrToBytes("Hello", "utf8");
const str = convertBytesToStr(bytes, "base64url"); // "SGVsbG8"
const hex = convertEncoding("SGVsbG8", "base64url", "hex"); // "48656c6c6f"Supported encodings: "utf8", "base64", "base64url", "hex", "latin1". Each function has a try* variant returning Result<T>.
Object Utilities
Serialize and parse plain objects with strict validation. Available from the root export and both platform entry points.
import { stringifyObj, parseToObj } from "cipher-kit";
const json = stringifyObj({ name: "Alice", role: "admin" });
const obj = parseToObj<{ name: string; role: string }>(json);
console.log(obj.name); // "Alice"Each function has a try* variant (tryStringifyObj, tryParseToObj).
Type Guards
import { isNodeSecretKey } from "cipher-kit/node";
import { isWebSecretKey } from "cipher-kit/web-api";
isNodeSecretKey(key); // true if key is NodeSecretKey
isWebSecretKey(key); // true if key is WebSecretKeyRegex Utilities
Validate the structural shape of encrypted payloads before decryption. This is a structural check only β it validates the dot-separated format but does not verify whether individual segments contain valid base64, base64url, or hex encoding.
import { ENCRYPTED_REGEX, matchEncryptedPattern } from "cipher-kit";
matchEncryptedPattern("abc.def.ghi."); // true β iv.cipher.tag.
matchEncryptedPattern("abc.def."); // false β missing tag segmentENCRYPTED_REGEX exposes the underlying regex: /^([A-Za-z0-9+/_-][A-Za-z0-9+/=_-]*)\.([A-Za-z0-9+/_-][A-Za-z0-9+/=_-]*)\.([A-Za-z0-9+/_-][A-Za-z0-9+/=_-]*)\.$/.
The Result Pattern π―
Every throwing function has a try* variant that returns Result<T> instead of throwing.
type Result<T> =
| ({ success: true; error?: undefined } & T) // success β value fields spread in
| { success: false; error: ErrorStruct }; // failure β error details
interface ErrorStruct {
readonly message: string;
readonly description: string;
}import { tryEncrypt } from "cipher-kit/node";
const result = tryEncrypt("Hello", secretKey);
if (result.success) {
console.log(result.result); // encrypted string
} else {
console.error(result.error.message, result.error.description);
}Type Exports π·οΈ
All types are importable from any entry point:
import type {
NodeSecretKey,
WebSecretKey,
CreateSecretKeyOptions,
EncryptOptions,
DecryptOptions,
HashOptions,
HashPasswordOptions,
VerifyPasswordOptions,
CipherEncoding,
Encoding,
EncryptionAlgorithm,
DigestAlgorithm,
Result,
ErrorStruct,
} from "cipher-kit";Contributions π€
- Open an issue or feature request
- Submit a PR to improve the package
- Star the repo if you find it useful
Crafted carefully by WolfieLeader
This project is licensed under the MIT License.
