iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

crypto

The node:crypto module gives you hashes, HMACs, symmetric and asymmetric encryption, key derivation, signatures, and a CSPRNG — everything you need for password hashing, token generation, and message integrity. Stay with the modern APIs (webcrypto, crypto.subtle) when possible.

random, hash, hmac, scrypt, AES-GCM

EXAMPLE
import crypto from 'node:crypto';

// 1) Random bytes — CSPRNG
const token = crypto.randomBytes(32).toString('base64url');     // session token / nonce
const uuid  = crypto.randomUUID();                              // RFC 4122 v4
const id    = crypto.randomBytes(16).toString('hex');            // request id

// 2) Hashing — fast, NOT for passwords
const sha256 = crypto.createHash('sha256').update('hello').digest('hex');
const sha512 = crypto.createHash('sha512').update(Buffer.from('hi')).digest('hex');

// One-shot helper (Node 21+)
const h = crypto.hash('sha256', 'hello', 'hex');

// File hashing
import fs from 'node:fs';
const hash = crypto.createHash('sha256');
fs.createReadStream('large.bin').on('data', (c) => hash.update(c)).on('end', () => console.log(hash.digest('hex')));

// 3) HMAC — keyed hash; use for signing webhooks / API keys
const sig = crypto.createHmac('sha256', SECRET).update(payload).digest('hex');

function verifySig(payload, expected, secret) {
    const actual = crypto.createHmac('sha256', secret).update(payload).digest();
    return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), actual);
}

// 4) Password hashing — scrypt (built-in)
import { scrypt, randomBytes, timingSafeEqual } from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);

async function hashPassword(pw) {
    const salt = randomBytes(16);
    const key  = await scryptAsync(pw, salt, 64, { N: 2 ** 16, r: 8, p: 1 });
    return `scrypt$${salt.toString('base64')}$${key.toString('base64')}`;
}

async function verifyPassword(pw, stored) {
    const [, saltB64, keyB64] = stored.split('$');
    const salt = Buffer.from(saltB64, 'base64');
    const key  = Buffer.from(keyB64,  'base64');
    const derived = await scryptAsync(pw, salt, key.length, { N: 2 ** 16, r: 8, p: 1 });
    return timingSafeEqual(derived, key);
}

// Or use argon2 (npm) — modern recommendation:
//   import argon2 from 'argon2';
//   await argon2.hash(pw); await argon2.verify(hash, pw);

// 5) Symmetric encryption — AES-256-GCM (authenticated)
function encrypt(plaintext, key) {
    const iv = crypto.randomBytes(12);
    const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
    const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
    const tag = cipher.getAuthTag();
    return Buffer.concat([iv, tag, ciphertext]).toString('base64');
}

function decrypt(blob, key) {
    const buf = Buffer.from(blob, 'base64');
    const iv  = buf.subarray(0, 12);
    const tag = buf.subarray(12, 28);
    const ct  = buf.subarray(28);
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
    decipher.setAuthTag(tag);
    return Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf8');
}

// Key: 32 random bytes (must be SECRET; store in vault)
const key = crypto.randomBytes(32);
const c = encrypt('hello', key);
console.log(decrypt(c, key));

// 6) HKDF — derive multiple keys from one master
import { hkdfSync } from 'node:crypto';
const master = crypto.randomBytes(32);
const encKey = Buffer.from(hkdfSync('sha256', master, Buffer.alloc(0), Buffer.from('aead:v1'), 32));
const macKey = Buffer.from(hkdfSync('sha256', master, Buffer.alloc(0), Buffer.from('mac:v1'), 32));

// 7) Asymmetric — Ed25519 signatures
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
const signature = crypto.sign(null, Buffer.from('message'), privateKey);
const ok = crypto.verify(null, Buffer.from('message'), publicKey, signature);

// Use Ed25519 for new code: small (32-byte keys), fast, deterministic.

// 8) JWT signing/verification — use 'jose' library (built on crypto)
// import { SignJWT, jwtVerify } from 'jose';
// const jwt = await new SignJWT({ sub: '42' })
//     .setProtectedHeader({ alg: 'EdDSA' })
//     .setExpirationTime('15m')
//     .sign(privateKey);

// 9) Web Crypto API — modern + portable (works in browser, Node, Cloudflare Workers)
const keyData = new Uint8Array(32);
crypto.getRandomValues(keyData);
const webKey = await crypto.subtle.importKey('raw', keyData, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) }, webKey, new TextEncoder().encode('hello'));

// 10) Constant-time comparison — essential for HMAC + token checks
function safeEqual(a, b) {
    if (a.length !== b.length) return false;
    return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
}

// 11) Common bugs
// • Using SHA-256 for password hashing → use scrypt / argon2 / bcrypt
// • CBC mode without authentication → use GCM
// • Reused IV with same key in GCM → CATASTROPHIC; always random IV per message
// • String comparison of HMACs (==) → timing attack; use timingSafeEqual
// • Storing keys in code/env without secret manager → leak risk
// • createHash without algorithm name as lowercase → some Node versions case-insensitive but be explicit
// • Using Math.random() for tokens → predictable; always crypto.randomBytes
// • Hardcoded salt for passwords → defeats purpose; per-record salt
// • Confusing key sizes — AES-128 needs 16 bytes, AES-256 needs 32

Why it matters

Use crypto.randomBytes/randomUUID for tokens, AES-GCM for symmetric encryption, scrypt or argon2 for passwords, Ed25519 for new signing keys, HKDF for sub-key derivation. Always use timingSafeEqual for HMAC and token verification, never reuse IVs in GCM, and prefer the Web Crypto API when your code needs to run in browsers or Workers.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { randomBytes, createHash } from 'node:crypto';
const token = randomBytes(16).toString('hex');
const hash = createHash('sha256').update('secret').digest('hex');
Try it Yourself »

Discussion

Loading…