crypto
node:crypto provides hashing, HMAC, random bytes, and ciphers for tokens, webhook verification, and encryption - use built-in APIs before adding npm crypto wrappers.
Recipe
import { createHmac, randomBytes, timingSafeEqual, randomUUID } from 'node:crypto';
const token = randomBytes(32).toString('base64url');
const id = randomUUID();
const sig = createHmac('sha256', secret).update(body).digest('hex');When to reach for this:
- Signing Stripe/Twilio webhooks
- Generating session tokens and CSRF secrets
- Password hashing (prefer
scrypt/argon2via crypto or dedicated lib) - TLS cert operations outside scope - use platform/KMS for keys
Working Example
import { createHmac, randomBytes, scrypt, timingSafeEqual } from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);
export function signPayload(body: Buffer, secret: string): string {
return createHmac('sha256', secret).update(body).digest('hex');
}
export function verifySignature(body: Buffer, secret: string, providedHex: string): boolean {
const expected = Buffer.from(signPayload(body, secret), 'hex');
const provided = Buffer.from(providedHex, 'hex');
if (expected.length !== provided.length) return false;
return timingSafeEqual(expected, provided);
}
export async function hashPassword(password: string, salt?: Buffer): Promise<{ salt: Buffer; hash: Buffer }> {
const s = salt ?? randomBytes(16);
const hash = (await scryptAsync(password, s, 64)) as Buffer;
return { salt: s, hash };
}import { webcrypto } from 'node:crypto';
const key = await webcrypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt'],
);What this demonstrates:
- HMAC-SHA256 pattern for webhooks - compare with
timingSafeEqualon digests scryptvia promisify for password storage with per-user salt- Web Crypto
subtlefor AES-GCM when interoperating with browser crypto - Never log secrets or full signatures in errors
Deep Dive
How It Works
- Hash (sha256) - one-way fingerprint - not for passwords alone.
- HMAC - keyed hash for integrity + authenticity of message.
- CSPRNG -
randomBytes,randomUUIDfor tokens. - scrypt/pbkdf2 - password stretching - tune cost params with hardware baseline.
Algorithm Pick
| Task | API |
|---|---|
| Session token | randomBytes |
| Webhook verify | HMAC + timingSafeEqual |
| Password storage | scrypt/argon2 |
| AES encrypt | createCipheriv or subtle |
TypeScript Notes
import { createHash } from 'node:crypto';
export function sha256Hex(input: string): string {
return createHash('sha256').update(input, 'utf8').digest('hex');
}Gotchas
- SHA256 alone for passwords - too fast - brute force. Fix: scrypt/argon2 with salt.
- Timing compare on strings with
===- leak. Fix: compare HMAC digests withtimingSafeEqual. - Reused IV/nonce in AES-GCM - catastrophic crypto failure. Fix: random 12-byte nonce per message.
- Hard-coded secrets in source - leak via repo. Fix: env/KMS + rotation.
- Math.random for tokens - predictable. Fix:
randomBytesonly.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| bcrypt npm | Team standard bcrypt | scrypt native enough |
| KMS envelope encryption | Master keys in cloud | Local dev tokens |
| JWT library | Signed claims with exp | Raw HMAC suffices for webhooks |
| TLS only | Data in transit | Need at-rest field encryption |
FAQs
randomUUID vs randomBytes?
UUID v4 for identifiers; randomBytes for custom-length secrets.
base64url for tokens?
URL-safe encoding without +/ - toString('base64url') on Buffer.
pbkdf2 vs scrypt?
Both in crypto - scrypt memory-hard; pick per security review and param tuning.
Where does TLS live?
Node HTTPS server uses tls module - certs from platform or cert-manager.
Can subtle encrypt large files?
Stream with createCipheriv for large payloads - subtle for smaller chunks/keys.
Rotate HMAC secrets?
Support two secrets during rotation window - verify against both.
crypto on worker_threads?
Generally OK - avoid sharing keys across threads without clear ownership.
FIPS compliance?
Special Node builds - consult org security - not default generic image.
Hashing PII?
Pseudonymize with salted hash for logs - not reversible identification.
relation to buffer security?
Buffer Security - wipe and compare patterns.
Stripe signature header?
See Webhook Verification - HMAC pattern.
deprecated createCipher?
Use createCipheriv with explicit IV - legacy APIs insecure.
Related
- Buffer Security - secret handling
- Webhook Verification - HMAC webhooks
- Security Basics - broader posture
- Prototype Pollution - not crypto but security neighbor
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.