Skip to Content
@exortek/cryptoOverview

@exortek/crypto

Zero-dependency cryptographic primitives for Node.js — hash, HMAC, KDFs, authenticated encryption, asymmetric signatures, timing-safe compare, sealed tokens, CSPRNG helpers, and encoders. Everything is built on node:crypto. Nothing calls the network. Nothing pulls a runtime dep.

Useful on its own — if you need a webhook signature verifier, a signed cookie, a password-reset ticket, or a stable object fingerprint, @exortek/crypto is a one-line install away.

What problem does this solve?

Node’s crypto module is powerful but full of foot-guns:

  • Nonce reuse. Reusing an AES-GCM nonce with the same key destroys the security of every message under that key. Node doesn’t stop you.
  • Non-timing-safe compare. === on an HMAC leaks the correct signature a character at a time. crypto.timingSafeEqual exists but requires equal-length Buffers or it throws.
  • Signature encoding mismatches. Node’s ECDSA output is DER by default. JOSE / JWT expects IEEE-P1363 (raw R‖S). Get this wrong and your token library says “invalid signature” without a hint why.
  • KDF parameter drift. OWASP publishes minimum PBKDF2 iterations each year. Everybody forgets to update them.
  • Encoding accidents. Base32 for TOTP secrets, base64url for JWT, Crockford for ULID, hex for debugging — each has quirks; getting them wrong silently corrupts data.

@exortek/crypto ships the correct default for each of these, and refuses the wrong one. random.pin(6) never returns '000000'. hash.hmac is timing-safe by construction. sign.sign('es256') emits IEEE-P1363 out of the box. hash.pbkdf2 defaults to OWASP 2023 minimums. Every encoder rejects out-of-alphabet characters with an INVALID_ENCODING error.

Is it safe to trust?

Short answer: yes for the primitives, with the caveats every crypto library has.

  • Every primitive is a thin wrapper around node:crypto. There is no hand-rolled AES, HMAC, or curve math in this codebase — Node delegates to OpenSSL, which is what Node itself is built on top of. The code you’re trusting is OpenSSL, not this package.
  • The algorithm choices come from RFC / OWASP / JOSE. SHA-256 default; AES-256-GCM for authenticated encryption; PBKDF2 iteration counts from the OWASP cheat sheet; IEEE-P1363 for ECDSA in JOSE contexts; base64url without padding for URL-safe transport.
  • The compare paths are timing-safe. Anywhere the library compares a secret to a candidate, it uses crypto.timingSafeEqual on equal-length Buffers.
  • Every input is validated at the boundary. Wrong-type arguments raise CryptoError(INVALID_ARGUMENT) before reaching Node; you don’t get a cryptic OpenSSL error thrown from three layers down.
  • Tests: 448 currently passing on Node 22 and 24. RFC test vectors are covered where they exist (HMAC-SHA256 RFC 4231, HKDF RFC 5869, PBKDF2 RFC 6070, Base32 RFC 4648, UUID RFC 9562).
  • No supply chain. Zero runtime dependencies. What you audit is what you run.

What we don’t claim: side-channel resistance beyond what node:crypto gives you; formal verification; suitability for regulated environments that require FIPS-validated modules (though the underlying OpenSSL can be FIPS-configured in your Node build).

Do not roll your own protocol. The primitives are safe. The composition is where auth libraries get broken. If you’re combining them into a new flow — session tokens, magic links, backup codes — read the module page carefully and reuse the sealed-token or signed-value helpers rather than assembling a bespoke layout.

Install

npm install @exortek/crypto yarn add @exortek/crypto pnpm add @exortek/crypto

Requires Node.js 22 or newer.

Modules at a glance

Every module has a dedicated page with full API reference and worked examples.

ModulePurposeKey exports
randomCSPRNG helpers — bytes, IDs, tokensbytes · hex · base64url · base58 · crockford · alphanumeric · numeric · pin · code · serial · token · uuid4 · uuid5 · uuid7 · ulid
hashdigests, MAC, KDFs, signed valueshash · hmac · compare · verifyHmac · pbkdf2 · hkdf · scrypt · signValue · unsignValue · fingerprint
cipherencryption + sealed tokensgenerateKey · generateKeyPair · encryptSymmetric · decryptSymmetric · encryptAsymmetric · decryptAsymmetric · encryptHybrid · deriveSharedSecret · encryptWithPassphrase · decryptWithPassphrase · seal · unseal
signasymmetric signaturessign · verify · generateSignKeyPair · thumbprint
encodecodec pairsbase64url · base64 · base32 · base58 · crockford · hex
binarybyte utilitiesconcat · xor · wipe · equal
errorstyped error surfaceCryptoError · ErrorCode

Common use cases

Which module do you need? Start here.

I want to…Reach for
Generate a session ID / OTP / verification coderandom.uuid4, random.pin, random.token
Verify a Stripe / GitHub / Slack webhookhash.verifyHmac
Sign a cookie / URL parameterhash.signValue
Derive an encryption key from a passphrasehash.pbkdf2, hash.scrypt, or cipher.encryptWithPassphrase
Encrypt a value into a tokencipher.encryptToString
Encrypt something with a human passphrasecipher.encryptWithPassphrase
Mint a 1-hour password-reset ticketcipher.seal
Produce a stable cache key from an objecthash.fingerprint
Sign / verify a JWT (asymmetric)sign.sign, sign.verify, sign.thumbprint
Establish a shared secret between two partiescipher.deriveSharedSecret
Derive multiple keys from one secrethash.hkdf
Encode / decode bytesencode.*
Wipe a secret Buffer after usebinary.wipe

Import styles

Three styles, all supported, all tree-shakable.

import { hmac, cipher, uuid4, seal } from '@exortek/crypto'

Most ergonomic — one import line for the whole surface.

Errors

Every entry point throws CryptoError with a stable code. Branch on code, not on the message — see the errors page for the full enum.

import { CryptoError, ErrorCode } from '@exortek/crypto' try { const { payload } = cipher.unseal(req.query.t, RESET_SECRET) } catch (err) { if (!(err instanceof CryptoError)) throw err if (err.code === ErrorCode.TOKEN_EXPIRED) return res.render('expired') if (err.code === ErrorCode.TOKEN_TAMPERED) return res.status(404).end() throw err }

Node version, platforms, ESM/CJS

  • Node 22 LTS or newer. Uses crypto.hkdfSync, crypto.scrypt, Buffer.readBigUInt64BE, and BLAKE2 / SHA-3 hashes — all stable on 22+.
  • Pure ESM source with a matching CJS output. import { hmac } from '@exortek/crypto' works; so does const { hmac } = require('@exortek/crypto').
  • Types. .d.ts is generated from JSDoc at build time. Strict TypeScript projects consume it cleanly.
  • Platforms. Runs on any Node 22+ target — Linux, macOS, Windows, Docker, AWS Lambda (Node 22 runtime), Vercel functions, Cloudflare Workers with nodejs_compat.

What this package is not

  • Not a password hasher. PBKDF2 / scrypt are here for deriving cipher key material from a passphrase, not for storing user password verifiers. Use a memory-hard password hasher (Argon2id / bcrypt) for login flows.
  • Not a full JWT library. sign.sign / sign.verify are the primitives — bytes in, signature bytes out. JWT header / claim validation (exp, nbf, iss, aud, algorithm-confusion defence) is up to the caller.
  • Not a session store. cipher.seal mints stateless tokens; anything server-side you need to invalidate, rotate, or attach data to belongs in a session layer you build.
  • Not a WebAuthn / OAuth / passkey implementation. Those are their own protocols — this package provides the crypto primitives they’d build on, not the flows themselves.

License

MIT — see the LICENSE file .

Last updated on