Back to Blog
Node.jsCryptoSecurityEncryption

Node.js Crypto Module Guide

Master the Node.js crypto module for hashing, encryption, and secure random number generation.

B
Bootspring Team
Engineering
March 2, 2020
6 min read

The crypto module provides cryptographic functionality for hashing, encryption, and secure random numbers.

Hashing#

1const crypto = require('crypto'); 2 3// Create hash 4function hash(data, algorithm = 'sha256') { 5 return crypto 6 .createHash(algorithm) 7 .update(data) 8 .digest('hex'); 9} 10 11console.log(hash('hello world')); 12// a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e 13 14// Different algorithms 15console.log(hash('hello', 'md5')); // 5d41402abc4b2a76b9719d911017c592 16console.log(hash('hello', 'sha1')); // aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d 17console.log(hash('hello', 'sha512')); // 9b71d224bd62f... 18 19// Hash file 20const fs = require('fs'); 21 22function hashFile(filepath) { 23 return new Promise((resolve, reject) => { 24 const hash = crypto.createHash('sha256'); 25 const stream = fs.createReadStream(filepath); 26 27 stream.on('data', data => hash.update(data)); 28 stream.on('end', () => resolve(hash.digest('hex'))); 29 stream.on('error', reject); 30 }); 31} 32 33// Streaming hash 34async function hashLargeFile(filepath) { 35 const hash = crypto.createHash('sha256'); 36 37 for await (const chunk of fs.createReadStream(filepath)) { 38 hash.update(chunk); 39 } 40 41 return hash.digest('hex'); 42}

Password Hashing#

1const crypto = require('crypto'); 2 3// Using scrypt (recommended) 4function hashPassword(password) { 5 return new Promise((resolve, reject) => { 6 const salt = crypto.randomBytes(16).toString('hex'); 7 8 crypto.scrypt(password, salt, 64, (err, derivedKey) => { 9 if (err) reject(err); 10 resolve(`${salt}:${derivedKey.toString('hex')}`); 11 }); 12 }); 13} 14 15async function verifyPassword(password, storedHash) { 16 const [salt, hash] = storedHash.split(':'); 17 18 return new Promise((resolve, reject) => { 19 crypto.scrypt(password, salt, 64, (err, derivedKey) => { 20 if (err) reject(err); 21 resolve(derivedKey.toString('hex') === hash); 22 }); 23 }); 24} 25 26// Usage 27const hashed = await hashPassword('myPassword123'); 28console.log(hashed); // salt:hash 29 30const isValid = await verifyPassword('myPassword123', hashed); 31console.log(isValid); // true 32 33// Using pbkdf2 34function hashPasswordPbkdf2(password) { 35 return new Promise((resolve, reject) => { 36 const salt = crypto.randomBytes(16).toString('hex'); 37 38 crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, derivedKey) => { 39 if (err) reject(err); 40 resolve(`${salt}:${derivedKey.toString('hex')}`); 41 }); 42 }); 43}

Symmetric Encryption#

1const crypto = require('crypto'); 2 3// AES-256-GCM encryption 4function encrypt(text, key) { 5 const iv = crypto.randomBytes(16); 6 const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); 7 8 let encrypted = cipher.update(text, 'utf8', 'hex'); 9 encrypted += cipher.final('hex'); 10 11 const authTag = cipher.getAuthTag(); 12 13 return { 14 iv: iv.toString('hex'), 15 encrypted, 16 authTag: authTag.toString('hex'), 17 }; 18} 19 20function decrypt(encryptedData, key) { 21 const decipher = crypto.createDecipheriv( 22 'aes-256-gcm', 23 key, 24 Buffer.from(encryptedData.iv, 'hex') 25 ); 26 27 decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex')); 28 29 let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8'); 30 decrypted += decipher.final('utf8'); 31 32 return decrypted; 33} 34 35// Usage 36const key = crypto.randomBytes(32); // 256 bits 37const encrypted = encrypt('secret message', key); 38const decrypted = decrypt(encrypted, key); 39console.log(decrypted); // 'secret message' 40 41// AES-256-CBC (simpler but no authentication) 42function encryptCBC(text, key) { 43 const iv = crypto.randomBytes(16); 44 const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); 45 46 let encrypted = cipher.update(text, 'utf8', 'hex'); 47 encrypted += cipher.final('hex'); 48 49 return `${iv.toString('hex')}:${encrypted}`; 50} 51 52function decryptCBC(encryptedText, key) { 53 const [ivHex, encrypted] = encryptedText.split(':'); 54 const iv = Buffer.from(ivHex, 'hex'); 55 const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); 56 57 let decrypted = decipher.update(encrypted, 'hex', 'utf8'); 58 decrypted += decipher.final('utf8'); 59 60 return decrypted; 61}

HMAC#

1const crypto = require('crypto'); 2 3// Create HMAC 4function createHmac(data, secret) { 5 return crypto 6 .createHmac('sha256', secret) 7 .update(data) 8 .digest('hex'); 9} 10 11// Verify HMAC (timing-safe) 12function verifyHmac(data, secret, providedHmac) { 13 const calculated = createHmac(data, secret); 14 return crypto.timingSafeEqual( 15 Buffer.from(calculated, 'hex'), 16 Buffer.from(providedHmac, 'hex') 17 ); 18} 19 20// Usage: API request signing 21function signRequest(method, path, body, secret) { 22 const timestamp = Date.now().toString(); 23 const message = `${method}:${path}:${timestamp}:${JSON.stringify(body)}`; 24 25 return { 26 signature: createHmac(message, secret), 27 timestamp, 28 }; 29} 30 31function verifyRequest(method, path, body, timestamp, signature, secret) { 32 const message = `${method}:${path}:${timestamp}:${JSON.stringify(body)}`; 33 return verifyHmac(message, secret, signature); 34} 35 36// Webhook verification 37function verifyWebhook(payload, signature, secret) { 38 const expectedSignature = createHmac(payload, secret); 39 return crypto.timingSafeEqual( 40 Buffer.from(signature), 41 Buffer.from(expectedSignature) 42 ); 43}

Random Values#

1const crypto = require('crypto'); 2 3// Random bytes 4const randomBytes = crypto.randomBytes(32); 5console.log(randomBytes.toString('hex')); 6 7// Random hex string 8function randomHex(length) { 9 return crypto.randomBytes(length).toString('hex'); 10} 11 12// Random base64 string 13function randomBase64(length) { 14 return crypto.randomBytes(length).toString('base64'); 15} 16 17// Random URL-safe string 18function randomUrlSafe(length) { 19 return crypto 20 .randomBytes(length) 21 .toString('base64') 22 .replace(/\+/g, '-') 23 .replace(/\//g, '_') 24 .replace(/=/g, ''); 25} 26 27// Random integer in range 28function randomInt(min, max) { 29 return crypto.randomInt(min, max); 30} 31 32// Secure token generation 33function generateToken(length = 32) { 34 return crypto.randomBytes(length).toString('hex'); 35} 36 37// UUID v4 (use crypto.randomUUID in Node 14.17+) 38function generateUUID() { 39 if (crypto.randomUUID) { 40 return crypto.randomUUID(); 41 } 42 43 const bytes = crypto.randomBytes(16); 44 bytes[6] = (bytes[6] & 0x0f) | 0x40; 45 bytes[8] = (bytes[8] & 0x3f) | 0x80; 46 47 const hex = bytes.toString('hex'); 48 return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; 49}

Key Derivation#

1const crypto = require('crypto'); 2 3// Derive key from password 4function deriveKey(password, salt, keyLength = 32) { 5 return new Promise((resolve, reject) => { 6 crypto.scrypt(password, salt, keyLength, (err, derivedKey) => { 7 if (err) reject(err); 8 resolve(derivedKey); 9 }); 10 }); 11} 12 13// HKDF (Node.js 15+) 14async function deriveKeyHKDF(secret, salt, info, keyLength = 32) { 15 const key = await crypto.subtle.importKey( 16 'raw', 17 Buffer.from(secret), 18 'HKDF', 19 false, 20 ['deriveBits'] 21 ); 22 23 const derived = await crypto.subtle.deriveBits( 24 { 25 name: 'HKDF', 26 hash: 'SHA-256', 27 salt: Buffer.from(salt), 28 info: Buffer.from(info), 29 }, 30 key, 31 keyLength * 8 32 ); 33 34 return Buffer.from(derived); 35} 36 37// Key stretching for encryption 38async function createEncryptionKey(password) { 39 const salt = crypto.randomBytes(16); 40 const key = await deriveKey(password, salt, 32); 41 42 return { 43 key, 44 salt: salt.toString('hex'), 45 }; 46}

Digital Signatures#

1const crypto = require('crypto'); 2 3// Generate key pair 4function generateKeyPair() { 5 return crypto.generateKeyPairSync('rsa', { 6 modulusLength: 2048, 7 publicKeyEncoding: { 8 type: 'spki', 9 format: 'pem', 10 }, 11 privateKeyEncoding: { 12 type: 'pkcs8', 13 format: 'pem', 14 }, 15 }); 16} 17 18// Sign data 19function sign(data, privateKey) { 20 const signer = crypto.createSign('RSA-SHA256'); 21 signer.update(data); 22 return signer.sign(privateKey, 'hex'); 23} 24 25// Verify signature 26function verify(data, signature, publicKey) { 27 const verifier = crypto.createVerify('RSA-SHA256'); 28 verifier.update(data); 29 return verifier.verify(publicKey, signature, 'hex'); 30} 31 32// Usage 33const { publicKey, privateKey } = generateKeyPair(); 34const message = 'Important message'; 35const signature = sign(message, privateKey); 36const isValid = verify(message, signature, publicKey); 37console.log(isValid); // true 38 39// Ed25519 (faster, shorter keys) 40function generateEd25519KeyPair() { 41 return crypto.generateKeyPairSync('ed25519', { 42 publicKeyEncoding: { type: 'spki', format: 'pem' }, 43 privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, 44 }); 45}

Practical Examples#

1const crypto = require('crypto'); 2 3// Session token 4function createSessionToken(userId) { 5 const payload = JSON.stringify({ 6 userId, 7 created: Date.now(), 8 }); 9 10 const token = crypto.randomBytes(32).toString('hex'); 11 const hash = crypto 12 .createHash('sha256') 13 .update(payload + token) 14 .digest('hex'); 15 16 return `${Buffer.from(payload).toString('base64')}.${hash}`; 17} 18 19// Password reset token 20function createResetToken() { 21 const token = crypto.randomBytes(32).toString('hex'); 22 const hash = crypto.createHash('sha256').update(token).digest('hex'); 23 24 return { 25 token, // Send to user 26 hash, // Store in database 27 expires: Date.now() + 3600000, // 1 hour 28 }; 29} 30 31// Checksum for file integrity 32async function createChecksum(filepath) { 33 const hash = crypto.createHash('sha256'); 34 35 for await (const chunk of require('fs').createReadStream(filepath)) { 36 hash.update(chunk); 37 } 38 39 return hash.digest('hex'); 40} 41 42// Encrypt configuration 43function encryptConfig(config, masterKey) { 44 const key = crypto.scryptSync(masterKey, 'salt', 32); 45 const iv = crypto.randomBytes(16); 46 const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); 47 48 const encrypted = Buffer.concat([ 49 cipher.update(JSON.stringify(config), 'utf8'), 50 cipher.final(), 51 ]); 52 53 return { 54 encrypted: encrypted.toString('hex'), 55 iv: iv.toString('hex'), 56 authTag: cipher.getAuthTag().toString('hex'), 57 }; 58}

Best Practices#

Algorithms: ✓ Use scrypt or Argon2 for passwords ✓ Use AES-256-GCM for encryption ✓ Use SHA-256 or SHA-512 for hashing ✓ Use HMAC for message authentication Security: ✓ Use crypto.randomBytes for tokens ✓ Use timing-safe comparison ✓ Generate unique IVs/salts ✓ Store salts with hashes Key Management: ✓ Use environment variables ✓ Rotate keys periodically ✓ Use key derivation functions ✓ Never hardcode secrets Avoid: ✗ MD5 or SHA1 for security ✗ ECB mode for encryption ✗ Predictable IVs or salts ✗ Rolling your own crypto

Conclusion#

The Node.js crypto module provides comprehensive cryptographic functionality. Use scrypt for password hashing, AES-GCM for encryption, and HMAC for authentication. Always use secure random generation for tokens and keys. Follow best practices for algorithm selection and key management.

Share this article

Help spread the word about Bootspring