The built-in node:crypto module provides battle-tested cryptographic primitives powered by OpenSSL, with zero third-party dependencies required. You can generate unpredictable session tokens, hash data with SHA-256, verify webhook HMAC signatures, and encrypt secrets with AES-256-GCM.
Never use Math.random() for tokens, API keys, or reset links because its output is predictable. Use crypto.randomBytes() or crypto.randomUUID() instead. When verifying HMAC webhook signatures, compare digests with crypto.timingSafeEqual() so subtle timing differences never leak signature bytes.
import crypto from 'node:crypto';
// 1. Cryptographically secure token & UUID
const resetToken = crypto.randomBytes(32).toString('hex');
const requestId = crypto.randomUUID();
// 2. SHA-256 digest and HMAC-SHA256 signature verification
const payload = JSON.stringify({ orderId: 912, paid: true });
const webhookSecret = 'demo_webhook_signing_secret';
const signature = crypto.createHmac('sha256', webhookSecret).update(payload).digest('hex');
const expected = crypto.createHmac('sha256', webhookSecret).update(payload).digest('hex');
const isValid = crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);
console.log({ requestId, tokenLength: resetToken.length, isValid });
Do not use plain sha256 for user passwords—GPUs compute billions of fast SHA-256 hashes per second. Use slow, salted password-hashing functions like crypto.scrypt() (built into node:crypto) or argon2 so brute-force attacks remain infeasible.
crypto.randomBytes(12)) on every aes-256-gcm encryption and store it alongside the ciphertext and auth tag.== or === to check API secrets or HMACs: Plain string comparison exits early on the first mismatched byte. Always use crypto.timingSafeEqual() with equal-length Buffers.crypto.timingSafeEqual() throw if buffer lengths differ?Yes. Always verify bufA.length === bufB.length first before calling timingSafeEqual(), or hash both values to fixed-length 32-byte SHA-256 digests first.
crypto.createHash('sha256')?No. Always use crypto.scrypt() with a random 16-byte per-user salt (or Argon2id) so attackers cannot crack leaked hashes quickly.
Prefer AES-256-GCM because it provides authenticated encryption, verifying both secrecy and that ciphertext was not tampered with.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With
Experiment with the code from this lesson in our interactive playground.