@zudojs/crypto
Safe defaults for hashing, AES-256-GCM encryption, scrypt password storage, Ed25519 signatures, opaque tokens and secure random values, all backed by node:crypto.
OVERVIEW
Node.js already ships a crypto module, but it lets you pick weak algorithms, short keys and bad parameters without complaint. @zudojs/crypto wraps it with one good choice per job and rejects the rest at runtime. You call hashPassword and get scrypt with OWASP parameters; you call encrypt and get AES-256-GCM with a fresh random IV.
Three words come up constantly on this page. Hashing turns data into a fixed-size fingerprint that cannot be turned back into the data. Encryption scrambles data with a key so that only someone holding the same key can read it again. Signing proves that a message came from the holder of a private key and was not changed on the way.
Every function is async and talks to node:crypto through a provider. A provider is just an object that implements the raw operations; the default one is created for you, and you can swap it in tests.
- Storing user passwords and checking them at login
- Issuing API keys, session or reset tokens and storing only their hash
- Encrypting a secret before it goes into a database
- Signing a payload so another service can verify it
- Random codes, IDs or bytes that must be unpredictable
- You need JWTs or a login flow: use @zudojs/auth, which builds on this package
- You need bcrypt, Argon2, ChaCha20 or AES-CBC: they are deliberately not implemented
- You need
Math.random()-style values for a game or a shuffle: plain JavaScript is fine
INSTALLATION
Install the package. Its two dependencies, @zudojs/constants and @zudojs/errors, come along automatically. Node.js 24 or newer is required.
QUICK START
This is the package's own smoke test: hash a string, store and check a password, encrypt and decrypt a secret, then mint a session token. Save it as quick.ts and run it.
The hex digest is always the same for "hello". Everything else (the password hash, the envelope, the token) is different on every run because each one starts from fresh random bytes.
- Do not invent your own scheme. Only SHA-2, SHA-3, HMAC, AES-256-GCM, scrypt, PBKDF2 and Ed25519/RSA/ECDSA signatures exist here. Ask
hashfor"md5"or"sha1"and it throwsTypeError: Unsupported hash algorithm. - Do not reuse an IV. An IV (also called a nonce) is the random 12-byte value that makes each encryption unique. Leave it out and
encryptdraws a fresh one every call. Reusing one under the same key leaks your plaintexts; a wrong-sized one is rejected withERR_CRYPTO_CIPHER. - Do not store plain passwords. Store the
encodedstring thathashPasswordreturns and check withverifyPassword. A plainsha256of a password is not a password hash: it is fast enough to brute-force.
HASHING AND HMAC
A hash is a fixed-size fingerprint of some data. The same input always produces the same output, and you cannot recover the input from it. Use it to fingerprint files, deduplicate content, or store a lookup key for a token.
Call hash for the full result, or a shorthand such as sha256 when you only want the encoded string. Both accept a string, a Uint8Array or an ArrayBuffer.
Supported algorithms are sha256, sha384, sha512, sha3-256, sha3-384 and sha3-512. Encodings are hex (default), base64 and base64url.
HMAC: a hash with a secret
An HMAC is a hash mixed with a secret key. Anyone can compute a plain hash, but only someone holding the key can compute the matching HMAC, so it proves a message was not tampered with. The key must be at least 16 bytes.
timingSafeEqualString (strings) or timingSafeEqual (bytes), never with ===. A normal comparison stops at the first different character, and that timing difference can be measured by an attacker.
ENCRYPTION
Encryption scrambles data with a key so it can be unscrambled later by anyone holding the same key. This package uses AES-256-GCM, which also detects tampering: if one byte of the ciphertext is changed, decryption fails instead of returning garbage.
The key must be exactly 32 bytes. Generate one with randomBytesSecure(32) and keep it outside your code, for example in an environment variable encoded as hex.
Envelopes: the easy path
An envelope is one string that bundles the IV, the authentication tag and the ciphertext, so you can store it in a single database column. This example encrypts a string and gets it back.
The envelope format is v1.aes-256-gcm.iv.authTag.ciphertext, each binary part in base64url. Your envelope will differ from the one shown because the IV is random every time.
Pieces: when you store fields separately
encryptString returns the three parts as bytes so you can store them in separate columns. decryptString takes them back in the same order.
Use encrypt and decrypt for raw Uint8Array data; they have the same shape. The optional aad option binds extra unencrypted context (such as a user ID) to the ciphertext, so it will not decrypt under a different context.
CryptoError with code ERR_CRYPTO_CIPHER and message Decryption failed. Catch it and treat it as "this data is not for this key", not as a bug.
PASSWORD HASHING
A password hash is a deliberately slow hash with a random salt mixed in. Slow means an attacker who steals your database can only try a few guesses per second. The salt means two users with the same password get different hashes.
This package uses scrypt with OWASP defaults (cost 16384, block size 8, parallelization 5, 16-byte salt, 32-byte output; new hashes must use cost ≥ 16384 (PASSWORD_HASH.SCRYPT.MIN_COST, checked by assertNewHashCost), older stored hashes still verify). The result is one self-describing string that starts with v1$scrypt$, so you can raise the cost later and old hashes still verify.
verifyPassword never throws over its inputs. A wrong password, a malformed stored string, or a hash with out-of-range parameters all return false. The one exception is configuration: since 1.3.0 it throws a CryptoError if the provider does not declare the passwordHashing capability, rather than reporting a provider that cannot hash as a wrong password. Passwords longer than 1024 characters are also rejected so an attacker cannot make your server grind on huge input.
To make hashing slower, pass options. cost must be a power of two.
isValidPassword(password) only checks length (8 characters by default). It is a convenience for form validation, not a strength meter.
DIGITAL SIGNATURES
A signature proves that a message came from whoever holds a private key and was not altered. Anyone with the matching public key can check it, but nobody can forge it. Use it for webhooks, signed URLs, or any payload that crosses a trust boundary.
The default algorithm is Ed25519. Generate a key pair once, export both halves as PEM text, and keep the private one secret. PEM is the familiar -----BEGIN PRIVATE KEY----- text format.
sign and verify do the same for Uint8Array data. Keys may be PEM text, DER bytes, or a Node KeyObject. RSA (rsa-sha256/384/512) and ECDSA (ecdsa-sha256/384/512) are available through the algorithm option when you already hold such keys; random bytes are never a valid signing key.
algorithm: "rsa-sha256" throws. A malformed key or signature makes verify return false rather than throw.
OPAQUE TOKENS
An opaque token is a long random string that means nothing by itself; your database maps it to a user or session. API keys, session cookies, password-reset links and CSRF tokens are all opaque tokens. The minimum is 16 random bytes (128 bits); the default is 32.
The typed generators add a recognisable prefix so tokens are easy to spot in logs and secret scanners. Store only the SHA-256 hash of a token; if the database leaks, the attacker still cannot use it.
Other generators follow the same pattern: generateRefreshToken (ref_), generateVerificationToken (verify_), generatePasswordResetToken (reset_) and generateCsrfToken (csrf_). generateToken({ bytes, encoding, prefix }) is the general form.
TOKEN_TTL exports sensible lifetimes in milliseconds (for example TOKEN_TTL.PASSWORD_RESET_MS is 15 minutes). The package does not track expiry for you; store an expiresAt column next to the hash.
SECURE RANDOM
Math.random() is predictable and must never be used for anything secret. These helpers draw from the operating system's cryptographic random source and are safe for keys, salts, codes and IDs.
randomInteger(min, max) is uniform (no bias toward low numbers) for ranges up to 248. Character helpers draw each character independently, so leading zeros in numeric codes are kept.
KEY DERIVATION
Key derivation stretches a human-typed passphrase into a fixed-length key you can use for encryption. It is the same slow-hash idea as password hashing, but the output is the key bytes rather than a stored string. If you only need to check a password, use hashPassword instead.
This derives a 32-byte AES key from a passphrase with PBKDF2-SHA256 (600 000 iterations by default). Keep the returned salt: you need the same salt to derive the same key again.
deriveScrypt(password, options) does the same with scrypt, and deriveKey(password, CryptoAlgorithm.SCRYPT, options) picks by enum. Salts must be at least 16 bytes; generateSalt() makes one. Cost, block size, parallelization, iterations and keyLength are capped by PASSWORD_HASH.LIMITS; out-of-range values throw before any derivation runs.
SERVICE AND PROVIDER
The loose functions above are all you need most of the time. Two wrappers exist for apps that prefer one object to pass around.
CryptoService bundles the common operations as methods and turns every failure into a CryptoError. CryptoFactory adds app-wide defaults (password cost, token encoding) on top of a service.
Swapping the provider in tests
A provider is the object that actually does the work; the default is createNodeCryptoProvider(). Every function accepts a provider option, or you can replace the process-wide default. This subclasses the real provider to count hash calls, then restores the default.
Capabilities, and what a provider must implement
Every provider declares a capabilities object with seven boolean flags: hash, hmac, encryption, signing, random, keyDerivation and passwordHashing. Before 1.3.0 nothing read them — a provider declaring signing: false still had its sign called. Each flag is now checked at the operation that needs it, and an operation the configured provider does not declare is refused with a CryptoError naming both the capability and the operation. A capabilities object that is missing or malformed fails the same way a false flag does. A provider that declares every capability it implements is unaffected.
verifyPassword is part of this: it throws rather than reporting an undeclared passwordHashing capability as a wrong password, which would otherwise read as a failed login forever.
setDefaultCryptoProvider now validates the provider before installing it. All twelve methods listed in CRYPTO_PROVIDER_METHODS — randomBytes, randomInt, randomUUID, hash, hmac, encrypt, decrypt, sign, verify, deriveKey, hashPassword, verifyPassword — must be functions, and each of the seven capability flags must be a boolean. Anything else throws a CryptoError from that call and the provider is not installed; a non-object argument throws a TypeError. Previously a partial object such as {} installed cleanly and failed much later, as a bare TypeError from inside whichever operation reached the missing method first.
The checks are exported for anyone writing their own provider or wrapper: assertCryptoProvider, assertProviderCapability, and the fixed-operation shorthands assertRandomCapability, assertHashCapability, assertHmacCapability and assertPasswordHashingCapability.
ERRORS
Cryptographic failures throw CryptoError from @zudojs/errors, re-exported here. It carries a stable code such as ERR_CRYPTO_CIPHER, an operation, and the original Node error as cause. Bad arguments (wrong algorithm name, short HMAC key) throw plain TypeError or RangeError instead.
The verify-style helpers are the exception: verifyPassword, verifyTokenHash and verify return false for bad input instead of throwing, so login code stays simple. That covers the input only — since 1.3.0 a provider that does not declare the capability an operation needs makes that operation throw a CryptoError, verifyPassword included, so a misconfigured provider is not mistaken for a wrong password.
API REFERENCE
Everything below is exported from @zudojs/crypto. All functions that touch node:crypto return a Promise.
Hashing
| Name | What it does | Notes |
|---|---|---|
| hash(input, { algorithm?, encoding?, provider? }) | Hashes data; returns { algorithm, digest, encoded } | Default sha256 / hex. md5 and sha1 throw. |
| sha256 / sha384 / sha512 / sha3_256 / sha3_384 / sha3_512(input, encoding?) | Returns the encoded digest string | Encoding defaults to hex. |
| hmac(input, key, algorithm?, encoding?) | Keyed hash | Key is a Uint8Array of 16+ bytes. |
| hmacSha256 / hmacSha384 / hmacSha512(input, key, encoding?) | HMAC shorthands | Same key rule. |
| timingSafeEqual(a, b), timingSafeEqualString(a, b) | Constant-time comparison of bytes or strings | Use instead of ===. |
Encryption
| Name | What it does | Notes |
|---|---|---|
| encrypt(plaintext, key, { iv?, aad?, provider? }) | AES-256-GCM; returns { algorithm, ciphertext, iv, authTag } | Key 32 bytes. IV 12 bytes, random if omitted. |
| decrypt(ciphertext, key, iv, authTag, aad?) | Reverses encrypt | Throws ERR_CRYPTO_CIPHER on tampering. |
| encryptString / decryptString | Same, for UTF-8 strings | |
| encryptEnvelope(plaintext, key, options?) | Returns one v1.aes-256-gcm.iv.tag.ct string | Best choice for storage. |
| decryptEnvelope(envelope, key, aad?) | Returns plaintext bytes | Validates every field first. |
Passwords and key derivation
| Name | What it does | Notes |
|---|---|---|
| hashPassword(password, options?) | scrypt hash; returns { encoded, salt, hash, cost, blockSize, parallelization } | Options: saltBytes, keyBytes, cost, blockSize, parallelization. |
| verifyPassword(password, encoded) | Checks a password | Never throws; returns false. |
| isPasswordHash(value), isValidPassword(value, min?) | Shape checks | isValidPassword is length only. |
| derivePbkdf2(password, options?), deriveScrypt(password, options?) | Turn a passphrase into key bytes; returns { key, salt, algorithm } | Pass salt to reproduce a key. |
| deriveKey(password, CryptoAlgorithm, options?) | Picks PBKDF2 or scrypt by enum | |
| generateSalt(length?) | Random salt bytes | Minimum 16. |
Signatures and keys
| Name | What it does | Notes |
|---|---|---|
| sign(data, privateKey, { algorithm? }) | Returns a signature Uint8Array | Default ed25519; also rsa-* and ecdsa-*. |
| verify(data, signature, publicKey, { algorithm? }) | Returns boolean | Malformed input gives false. |
| signString / verifyString | Same for UTF-8 strings | |
| generateEd25519KeyPair() | Returns { privateKey, publicKey } KeyObjects | Synchronous. |
| exportPrivateKeyPem(key), exportPublicKeyPem(key), derivePublicKey(privateKey) | PEM export and public-key recovery | |
| generateCryptoKey(length, { algorithm, usages?, extractable? }) | Random symmetric key as a CryptoKey object | Symmetric algorithms only. |
| createCryptoKey(bytes, options), exportCryptoKey(key) | Wrap existing bytes; read them back | Export requires extractable: true. |
Tokens and random
| Name | What it does | Notes |
|---|---|---|
| generateToken({ bytes?, encoding?, prefix? }) | Random opaque token string | Default 32 bytes base64url; minimum 16. |
| generateApiKey / generateSessionToken / generateRefreshToken / generateVerificationToken / generatePasswordResetToken / generateCsrfToken() | Prefixed tokens | Prefixes in TOKEN_PREFIX. |
| generateOtp(digits?) | Numeric one-time code | 4 to 12 digits, default 6. |
| hashToken(token), verifyTokenHash(token, storedHash) | SHA-256 hex for storage and constant-time check | hashTokenForStorage is an alias. |
| hasTokenPrefix / removeTokenPrefix(token, prefix), isValidToken(token, min?) | String helpers | |
| randomBytesSecure(n), randomHex(chars), randomBase64Url(bytes) | Random bytes and strings | |
| randomInteger(min, max), randomIntegerBelow(max), randomUuid() | Uniform integers and UUID v4 | max is excluded. |
| randomNumericCode(len?), randomAlphanumeric(len), randomFromAlphabet(len, alphabet), randomChoice(array), randomBoolean() | Random characters and picks |
Encoding, service, provider, errors, constants
| Name | What it does | Notes |
|---|---|---|
| encode(bytes, encoding), decode(string, encoding) | Convert between bytes and hex / base64 / base64url / utf8 | Also toHex, fromHex, toBase64Url, fromBase64Url, utf8Encode, utf8Decode. |
| createCryptoService({ provider? }), cryptoService | Method-style facade; wraps failures in CryptoError | Methods: generateKey, randomBytes, encrypt, decrypt, hash, hashHex, hashPassword, verifyPassword, deriveKey, generateToken, generateOtp, hashToken, verifyToken, encode, decode. |
| createCryptoFactory({ defaultKeyAlgorithm?, password?, encoding?, provider? }), cryptoFactory | Service with app-wide defaults | Methods: createKey, createToken, createApiKey, createSessionToken, createRefreshToken, createVerificationToken, createPasswordResetToken, createCsrfToken, createOtp, createPasswordHash, verifyPassword, encode, decode. |
| createNodeCryptoProvider(), getDefaultCryptoProvider(), setDefaultCryptoProvider(p), resetDefaultCryptoProvider() | Create or swap the backing provider | Implement CryptoProvider for a custom one. setDefaultCryptoProvider rejects one missing any of the twelve methods or the seven capability flags. |
| CryptoError, isCryptoError(value), CryptoOperation | Error class, guard and operation enum | Re-exported from @zudojs/errors. |
| CryptoAlgorithm, CryptoKeyUsage | Enums for algorithm and key-usage names | Use these, not string literals, where an enum is expected. |
| AES_GCM, PASSWORD_HASH, PASSWORD_POLICY, TOKEN, TOKEN_PREFIX, TOKEN_TTL | Frozen default parameters | e.g. PASSWORD_HASH.SCRYPT.COST is 16384. |
COMMON MISTAKES
- Hashing a password with
sha256. It runs millions of times per second, so a leaked table is cracked in minutes. Fix:hashPasswordandverifyPassword. - Using
new Uint8Array(32)as a real key. That is 32 zero bytes; everyone has that key. Fix:await randomBytesSecure(32)once, then load it from configuration. - Passing a 4-byte HMAC key.
hmacthrowsRangeError: HMAC key must be at least 16 bytes. Fix: use 16 to 64 random bytes. - Storing the raw API key. A database leak then hands out working credentials. Fix: store
hashToken(key)and look up withverifyTokenHash. - Passing
"aes-256-gcm"where an enum is expected.generateCryptoKey,deriveKeyandcreateCryptoFactorytakeCryptoAlgorithm; TypeScript rejects the string. Fix:CryptoAlgorithm.AES_256_GCM. - Feeding raw 32 bytes to
sign. Signing keys are PEM, DER orKeyObject, not random bytes. Fix:generateEd25519KeyPair()andexportPrivateKeyPem.
COMPLETE EXPORT INDEX
Every name @zudojs/crypto exports from its package root at v1.3.0 — 257 in total, generated from the package’s own entry point rather than written by hand. The sections above explain the ones you reach for most; this is the exhaustive list, so nothing shipped is undocumented. Names not covered above are typically internal helpers and supporting types.
Show all 257 exports
CryptoError CryptoFactory CryptoService NodeCryptoProviderarrayBufferToBytes assertBinaryEncoding assertCryptoProvider assertHashCapability assertHmacCapability assertKeyObject assertNewHashCost assertPassword assertPasswordHashingCapability assertProviderCapability assertRandomCapability bytesToArrayBuffer bytesToNumber cloneBytes concatBytes createCryptoError createCryptoFactory createCryptoKey createCryptoService createNodeCryptoProvider cryptoCipherError cryptoHashError cryptoKeyDerivationError cryptoKeyError cryptoKeysEqual cryptoKeyToPrivateKey cryptoSignatureError decode decodeBase64Url decodeDigest decodePasswordHash decrypt decryptEnvelope decryptString defaultKeyLength deriveKey derivePbkdf2 derivePublicKey deriveScrypt encode encodeDigest encodePasswordHash encrypt encryptEnvelope encryptString equalDigests expectedAsymmetricKeyType expectedKeyLength exportCryptoKey exportPrivateKeyPem exportPublicKeyPem fillRandomBytes fromBase64 fromBase64Url fromHex generateApiKey generateCryptoKey generateCsrfToken generateEd25519KeyPair generateEmailVerificationCode generateLoginCode generateOtp generatePasswordResetToken generateRefreshToken generateSalt generateSessionToken generateToken generateVerificationToken getCryptoFactory getCryptoKeyFingerprint getCryptoService getDefaultAesGcmConfig getDefaultCryptoProvider getDefaultPasswordHashConfig getDefaultPasswordHashOptions hash hashPassword hashToken hashTokenBase64Url hashTokenForStorage hasTokenPrefix hmac hmacSha256 hmacSha384 hmacSha512 isAeadAlgorithm isArrayBuffer isBase64 isBase64Url isBase64UrlString isBinaryEncoding isBytes isCryptoAlgorithm isCryptoEncoding isCryptoError isCryptoKey isHashAlgorithm isHashAlgorithmName isHex isHexString isHmacAlgorithmName isKeyDerivationAlgorithm isKeyObject isMacAlgorithm isPasswordHash isPbkdf2Digest isSignatureAlgorithm isSignatureAlgorithmName isSymmetricKeyAlgorithm isValidPassword isValidToken nodeSignatureAlgorithm normalizeText numberToBytes parseCryptoAlgorithm parsePositiveInteger pbkdf2PasswordAlgorithm randomAlphanumeric randomBase64 randomBase64Url randomBoolean randomBytesSecure randomChoice randomFromAlphabet randomHex randomInteger randomIntegerBelow randomNumericCode randomToken randomUuid removeTokenPrefix resetDefaultCryptoProvider secureEqual secureStringEqual setDefaultCryptoProvider sha256 sha3_256 sha3_384 sha3_512 sha384 sha512 sign signString sliceBytes timingSafeEqual timingSafeEqualEncoded timingSafeEqualString toBase64 toBase64Url toBytes toHex toPrivateKey toPublicKey utf8ByteLength utf8Decode utf8Encode validateParameters validatePbkdf2Options validatePbkdf2Parameters validateScryptOptions verify verifyPassword verifyString verifyTokenHash wipeCipherOptions CipherResult CryptoCapabilities CryptoErrorOptions CryptoFactoryOptions CryptoKey CryptoKeyOptions CryptoProvider CryptoServiceOptions DecryptOptions DerivedKeyResult DeriveKeyOptions EncryptedData EncryptionProvider EncryptOptions HashOptions HashPasswordOptions HashProvider HashResult HmacProvider KeyDerivationProvider PasswordHashOptions PasswordHashProviderOptions PasswordHashResult PasswordProvider Pbkdf2Options Pbkdf2PasswordHashParameters RandomProvider RandomTokenOptions ScryptOptions ScryptPasswordHashParameters SigningProvider SignOptions TokenOptions VerifyOptionsBinaryEncoding BinaryInput CryptoAlgorithmName CryptoEncoding CryptoEncodingName CryptoInput DerivedKeyAlgorithm EncodingFormat EncryptionAlgorithm HashAlgorithm HashEncoding HashInput HmacAlgorithm KeyDerivationAlgorithm KeyMaterial PasswordHashParameters Pbkdf2Digest Pbkdf2PasswordAlgorithm SignatureAlgorithm SignatureOptions TokenEncoding TokenPrefixAEAD_ALGORITHMS AES_GCM base64ToBytes base64UrlToBytes bytesToBase64 bytesToBase64Url bytesToHex CRYPTO_ALGORITHM CRYPTO_KEY_FINGERPRINT_LABEL CRYPTO_PROVIDER_METHODS CRYPTO_VERSION cryptoFactory cryptoService ENCODING HASH HASH_ALGORITHMS hexToBytes KEY_DERIVATION_ALGORITHMS KEY_SIZE MAC_ALGORITHMS PASSWORD_FORMAT_VERSION PASSWORD_HASH PASSWORD_POLICY RANDOM SIGNATURE_ALGORITHMS TOKEN TOKEN_PREFIX TOKEN_TTLCryptoAlgorithm CryptoKeyUsage CryptoOperation