Chapter 31: Node.js Crypto Module
1. What is the crypto module? (the honest explanation)
node:crypto is Node.js’s built-in cryptographic toolkit.
It gives you access to:
- Hashing (SHA-256, SHA-512, MD5, etc.)
- HMAC (keyed hashing)
- Encryption / decryption (AES, ChaCha20, etc.)
- Key generation (random bytes, UUIDs, prime numbers)
- Digital signatures (RSA, ECDSA, Ed25519)
- Diffie-Hellman key exchange
- PBKDF2, scrypt (password hashing)
- Web Crypto API compatibility (crypto.webcrypto)
Very important mindset in 2025–2026:
Never write your own cryptography algorithms Never use MD5 / SHA-1 for security purposes Never roll your own password hashing Never trust user input when dealing with crypto
The crypto module is very well audited, very fast (mostly C++ under the hood), and actively maintained.
2. Modern import style (2025–2026)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Recommended way import crypto from 'node:crypto'; // Or named imports (very common) import { createHash, createHmac, randomBytes, createCipheriv, createDecipheriv, pbkdf2Sync, timingSafeEqual } from 'node:crypto'; |
Always use the node: prefix — it makes it crystal clear this is core Node, not an npm package.
3. Most important & frequently used features
Let’s go through the ones you will use 90% of the time in real projects.
3.1 Hashing – createHash (most common)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
function hashFile(content) { const hash = crypto.createHash('sha256'); hash.update(content); return hash.digest('hex'); // or 'base64', 'binary' } console.log(hashFile('Hello Hyderabad!')); // → 8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa3 |
Real-world pattern – file integrity check
|
0 1 2 3 4 5 6 7 8 9 10 11 |
import fs from 'node:fs/promises'; async function getFileHash(path) { const content = await fs.readFile(path); return crypto.createHash('sha256').update(content).digest('hex'); } |
3.2 HMAC – keyed hashing (very important for JWT, webhooks)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const secret = 'my-super-secret-key-2026'; function createHmacSignature(message) { return crypto .createHmac('sha256', secret) .update(message) .digest('hex'); } const payload = JSON.stringify({ userId: 123, action: 'login' }); const signature = createHmacSignature(payload); console.log('Signature:', signature); |
Real webhook verification pattern
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
function verifyWebhookSignature(payload, receivedSignature) { const expected = createHmacSignature(payload); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(receivedSignature) ); } |
Never use === for comparison — use timingSafeEqual to prevent timing attacks.
3.3 Password hashing – PBKDF2 or scrypt (must know)
Never store plain passwords or use md5/sha256 directly.
Modern recommendation 2026: use scrypt or argon2 (via npm)
But pbkdf2 is still very widely used and is built-in.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const salt = crypto.randomBytes(16); const iterations = 600000; // OWASP 2023+ recommendation const keylen = 32; const digest = 'sha256'; crypto.pbkdf2('myPassword123', salt, iterations, keylen, digest, (err, derivedKey) => { if (err) throw err; console.log('Stored hash:', derivedKey.toString('hex')); console.log('Stored salt:', salt.toString('hex')); }); |
Promise version (modern)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { promisify } from 'node:util'; const pbkdf2Async = promisify(crypto.pbkdf2); async function hashPassword(password) { const salt = crypto.randomBytes(16); const hash = await pbkdf2Async(password, salt, 600000, 32, 'sha256'); return { salt: salt.toString('hex'), hash: hash.toString('hex') }; } |
Very common storage format (used by many frameworks)
|
0 1 2 3 4 5 6 |
pbkdf2-sha256$600000$saltInHex$hashInHex |
3.4 Symmetric encryption – AES (very common)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const algorithm = 'aes-256-gcm'; const key = crypto.randomBytes(32); // 256 bits const iv = crypto.randomBytes(12); // GCM recommends 12 bytes const cipher = crypto.createCipheriv(algorithm, key, iv); let encrypted = cipher.update('My secret message', 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); console.log('IV:', iv.toString('hex')); console.log('Encrypted:', encrypted); console.log('Auth Tag:', authTag.toString('hex')); |
Full encrypt + decrypt round-trip
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
function encrypt(text, key) { const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return { iv: iv.toString('hex'), content: encrypted, tag: cipher.getAuthTag().toString('hex') }; } function decrypt(encryptedData, key) { const decipher = crypto.createDecipheriv( 'aes-256-gcm', key, Buffer.from(encryptedData.iv, 'hex') ); decipher.setAuthTag(Buffer.from(encryptedData.tag, 'hex')); let decrypted = decipher.update(encryptedData.content, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } |
5. Very common real-world patterns (2026)
Pattern 1 – Secure token / session ID generation
|
0 1 2 3 4 5 6 7 8 |
function generateSecureToken(length = 32) { return crypto.randomBytes(length).toString('hex'); } |
Pattern 2 – JWT signing (without jsonwebtoken library)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function signHS256(payload, secret) { const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url'); const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url'); const input = `${header}.${encodedPayload}`; const signature = crypto.createHmac('sha256', secret).update(input).digest('base64url'); return `${input}.${signature}`; } |
Pattern 3 – File integrity + authenticity (HMAC)
|
0 1 2 3 4 5 6 7 8 |
function signFile(fileBuffer, secret) { return crypto.createHmac('sha256', secret).update(fileBuffer).digest('hex'); } |
6. Security rules you must follow (2025–2026)
- Never use MD5 or SHA-1 for security purposes
- Never use crypto.randomBytes for keys if you can use crypto.generateKeyPair or crypto.subtle.generateKey
- Always use authenticated encryption modes (GCM, ChaCha20-Poly1305)
- Always use timingSafeEqual for comparing secrets / signatures
- Never log keys, IVs, auth tags, or full ciphertexts
- Never reuse IVs / nonces in AES-GCM
- Use scrypt or Argon2 (via npm) for password hashing if possible — PBKDF2 is acceptable but slower
Summary – Quick cheat sheet
| Task | Recommended function / class | Typical real-world example |
|---|---|---|
| Hash file / string | createHash(‘sha256’) | File integrity, caching keys |
| Keyed hash (webhooks, JWT) | createHmac(‘sha256’, secret) | Verify webhook signatures, JWT signing |
| Secure random bytes / tokens | randomBytes(size) | Session IDs, reset tokens, salts |
| Password hashing | pbkdf2 / scrypt (or Argon2 via npm) | User passwords |
| Symmetric encryption | createCipheriv(‘aes-256-gcm’, …) | Encrypt sensitive data at rest / in transit |
| Timing-safe comparison | timingSafeEqual(a, b) | Compare signatures, tokens, passwords |
| Generate UUID | crypto.randomUUID() | Database IDs, correlation IDs |
Would you like to go much deeper into any specific area?
- Full AES-GCM encrypt/decrypt round-trip with storage format
- JWT signing & verification using only core crypto
- Password hashing storage format & verification
- File signing + integrity check system
- Web Crypto API (crypto.subtle) vs legacy crypto
- Common crypto attacks & how Node.js protects you
Just tell me which direction you want — I’ll continue with detailed, production-ready examples. 😊
