bugl
bugl
HomeLearnPatternsPathsSearch
HomeLearnPatternsPathsSearch

Loading lesson path

Learn/Node.js/Core Modules
Node.js•Core Modules

Node.js Crypto Module

Flash cards

Review the key moves

1/4
Core idea

What is the main idea behind Node.js Crypto Module?

Lesson checks

Practice each idea before moving on

Short Mimo-style checks built from this lesson's code, terms, and sequence.

1Quick choice

Which statement best captures the main point of this lesson?

2Fill blank

Complete the missing token from the example code.

___ crypto = require('crypto');
3Order

Put the learning moves in the order that makes the concept easiest to apply.

Common Hash Algorithms
Installing the Crypto Module
Getting Started with Crypto

What is the Crypto Module?

The Crypto module is a built-in Node.js module that provides cryptographic functionality including:

  • Hash functions (SHA-256, SHA-512, etc.)
  • HMAC (Hash-based Message Authentication Code)
  • Symmetric encryption (AES, DES, etc.)
  • Asymmetric encryption (RSA, ECDSA, etc.)
  • Digital signatures and verification
  • Secure random number generation

The Crypto module is essential for applications that need to handle sensitive information securely.

The Crypto module wraps the OpenSSL library, providing access to well-established and tested cryptographic algorithms.

This module is often used to handle sensitive data, such as:

  • User authentication and password storage
  • Secure data transmission
  • File encryption and decryption
  • Secure communication channels

Getting Started with Crypto

Here's a quick example of using the Crypto module to hash a string:

Basic Hashing Example

const crypto = require('crypto');
// Create a SHA-256 hash of a string
const hash = crypto.createHash('sha256') .update('Hello, Node.js!') .digest('hex');
console.log('SHA-256 Hash:', hash);

Installing the Crypto Module

The Crypto module is included in Node.js by default.

You can use it by requiring it in your script:

const crypto = require('crypto');

Hash Functions

Hashing is a one-way transformation of data into a fixed-length string of characters.

Hash functions have several important properties

  • Deterministic: Same input always produces the same output
  • Fixed Length: Output is always the same size regardless of input size
  • One-Way: Extremely difficult to reverse the process
  • Avalanche Effect: Small changes in input produce significant changes in output

Common use cases include

  • Password storage
  • Data integrity verification
  • Digital signatures
  • Content addressing (e.g., Git, IPFS)

Creating a Hash

Runnable example

const crypto = require('crypto');
// Create a hash object
const hash = crypto.createHash('sha256');
// Update the hash with data hash.update('Hello, World!'); // Get the digest in hexadecimal format
const digest = hash.digest('hex');
console.log(digest);

In this example

  • createHash() creates a hash object with the specified algorithm
  • update() updates the hash content with the given data
  • digest() calculates the digest and outputs it in the specified format

Common Hash Algorithms

Runnable example

const crypto = require('crypto');
const data = 'Hello, World!';
// MD5 (not recommended for security-critical applications)
const md5 = crypto.createHash('md5').update(data).digest('hex');
console.log('MD5:', md5);
// SHA-1 (not recommended for security-critical applications)
const sha1 = crypto.createHash('sha1').update(data).digest('hex');
console.log('SHA-1:', sha1);
// SHA-256
const sha256 = crypto.createHash('sha256').update(data).digest('hex');
console.log('SHA-256:', sha256);
// SHA-512
const sha512 = crypto.createHash('sha512').update(data).digest('hex');
console.log('SHA-512:', sha512);

Warning

MD5 and SHA-1 are considered cryptographically weak and should not be used for security-critical applications.

Use SHA-256, SHA-384, or SHA-512 instead.

Password Security

When handling passwords, it's crucial to use specialized password hashing functions that are designed to be computationally expensive to prevent brute-force attacks.

Here's why simple hashes are insufficient

Never store passwords in plain text or with simple hashes like MD5 or SHA-1.

These can be easily cracked using rainbow tables or brute-force attacks.

Key Concepts for Password Security

  • Salting: Add a unique random value to each password before hashing
  • Key Stretching: Make the hashing process intentionally slow to prevent brute-force attacks
  • Work Factor: Control how computationally intensive the hashing process is

Here's how to properly hash passwords in Node.js:

What is a salt? A salt is a random string that is unique to each user.

It's combined with the password before hashing to ensure that even if two users have the same password, their hashes will be different.

This prevents attackers from using precomputed tables (like rainbow tables) to crack multiple passwords at once.

const crypto = require('crypto');
// Function to hash a password
function hashPassword(password) {
 // Generate a random salt (16 bytes)
 const salt = crypto.randomBytes(16).toString('hex');
 // Use scrypt for password hashing (recommended)
 const hash = crypto.scryptSync(password, salt, 64).toString('hex');
 // Return both salt and hash for storage
 return { salt, hash };
}
// Function to verify a password
function verifyPassword(password, salt, hash) {
 const hashedPassword = crypto.scryptSync(password, salt, 64).toString('hex');
 return hashedPassword === hash;
}
// Example usage
const password = 'mySecurePassword';
// Hash the password for storage
const { salt, hash } = hashPassword(password);
console.log('Salt:', salt);
console.log('Hash:', hash);
// Verify a login attempt
const isValid = verifyPassword(password, salt, hash);
console.log('Password valid:', isValid); // true
const isInvalid = verifyPassword('wrongPassword', salt, hash);
console.log('Wrong password valid:', isInvalid); // false

Note

For password hashing in a production environment, consider using a dedicated library like bcrypt or argon2 that is specifically designed for secure password handling.

HMAC (Hash-based Message Authentication Code)

HMAC is a specific type of message authentication code (MAC) involving a cryptographic hash function and a secret cryptographic key.

It provides both data integrity and authentication.

When to Use HMAC

  • API request verification
  • Secure cookies and sessions
  • Data integrity checks
  • Webhook verification

HMAC Security Properties

  • Message Integrity: Any change to the message will produce a different HMAC
  • Authenticity: Only parties with the secret key can generate valid HMACs
  • No Encryption: HMAC doesn't encrypt the message, only verifies its integrity

Runnable example

const crypto = require('crypto');
// Secret key
const secretKey = 'mySecretKey';
// Create an HMAC
const hmac = crypto.createHmac('sha256', secretKey);
// Update with data hmac.update('Hello, World!'); // Get the digest
const hmacDigest = hmac.digest('hex');
console.log('HMAC:', hmacDigest);

HMAC for Message Verification

Runnable example

const crypto = require('crypto');
// Function to create an HMAC for a message function createSignature(message, key) {
const hmac = crypto.createHmac('sha256', key);
hmac.update(message);
return hmac.digest('hex');
}
// Function to verify a message's signature function verifySignature(message, signature, key) { const expectedSignature = createSignature(message, key); return crypto.timingSafeEqual( Buffer.from(signature, 'hex'), Buffer.from(expectedSignature, 'hex') ); } // Example usage const secretKey = 'verySecretKey'; const message = 'Important message to verify'; // Sender creates a signature const signature = createSignature(message, secretKey); console.log('Message:', message); console.log('Signature:', signature); // Receiver verifies the signature try { const isValid = verifySignature(message, signature, secretKey); console.log('Signature valid:', isValid); // true // Try with a tampered message const isInvalid = verifySignature('Tampered message', signature, secretKey); console.log('Tampered message valid:', isInvalid); // false } catch (error) { console.error('Verification error:', error.message); }

Note

Always use timingSafeEqual() for cryptographic comparisons to prevent timing attacks.

Symmetric Encryption

Symmetric encryption uses the same key for both encryption and decryption.

It's generally faster than asymmetric encryption and is ideal for:

  • Bulk data encryption
  • Database encryption
  • Filesystem encryption
  • Secure messaging (combined with key exchange)

Common Symmetric Algorithms

AlgorithmKey SizeBlock SizeNotes
AES-256256 bits128 bitsCurrent standard, widely used
ChaCha20256 bits512 bitsFaster in software, used in TLS 1.3
3DES168 bits64 bitsLegacy, not recommended for new systems
Blowfish32-448 bits64 bitsLegacy, use Twofish or AES instead

Note

Always use authenticated encryption modes like AES-GCM or AES-CCM when possible, as they provide both confidentiality and authenticity.

AES (Advanced Encryption Standard)

const crypto = require('crypto');
// Function to encrypt data
function encrypt(text, key) {
 // Generate a random initialization vector
 const iv = crypto.randomBytes(16);
 // Create cipher with AES-256-CBC
 const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
 // Encrypt the data
 let encrypted = cipher.update(text, 'utf8', 'hex');
 encrypted += cipher.final('hex');
 // Return both the encrypted data and the IV
 return {
 iv: iv.toString('hex'),
 encryptedData: encrypted
 };
}
// Function to decrypt data
function decrypt(encryptedData, iv, key) {
 // Create decipher
 const decipher = crypto.createDecipheriv(
 'aes-256-cbc',
 key,
 Buffer.from(iv, 'hex')
);
// Decrypt the data
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Example usage
// Note: In a real application, use a properly generated and securely stored key
const key = crypto.scryptSync('secretPassword', 'salt', 32); // 32 bytes = 256 bits
const message = 'This is a secret message';
// Encrypt
const { iv, encryptedData } = encrypt(message, key);
console.log('Original:', message);
console.log('Encrypted:', encryptedData);
console.log('IV:', iv);
// Decrypt
const decrypted = decrypt(encryptedData, iv, key);
console.log('Decrypted:', decrypted);

Warning

Never reuse the same initialization vector (IV) with the same key.

Always generate a new random IV for each encryption operation.

Other Symmetric Algorithms

The Crypto module supports various symmetric encryption algorithms.

You can see the available ciphers with:

Runnable example

const crypto = require('crypto');
// List available cipher algorithms console.log(crypto.getCiphers());

Asymmetric Encryption

Asymmetric encryption (public-key cryptography) uses a pair of mathematically related keys:

  • Public Key: Can be shared publicly, used for encryption
  • Private Key: Must be kept secret, used for decryption

Common Use Cases

  • Secure key exchange (e.g., TLS/SSL handshake)
  • Digital signatures
  • Email encryption (PGP/GPG)
  • Blockchain and cryptocurrencies

Common Asymmetric Algorithms

AlgorithmKey SizeSecurity LevelNotes
RSA2048+ bitsHighWidely used, good compatibility
ECDSA256-521 bitsHighUsed in TLS 1.3, Bitcoin
Ed25519256 bitsVery HighModern, efficient, used in SSH

Performance Note: Asymmetric encryption is much slower than symmetric encryption.

For encrypting large amounts of data, use a hybrid approach:

  • Generate a random symmetric key
  • Encrypt your data with the symmetric key
  • Encrypt the symmetric key with the recipient's public key
  • Send both the encrypted data and encrypted key

RSA (Rivest-Shamir-Adleman)

const crypto = require('crypto');
// Generate RSA key pair
function generateKeyPair() {
 return crypto.generateKeyPairSync('rsa', {
 modulusLength: 2048, // Key size in bits
 publicKeyEncoding: {
 type: 'spki',
 format: 'pem'
 },
 privateKeyEncoding: {
 type: 'pkcs8',
 format: 'pem'
 }
 });
}
// Encrypt with public key
function encryptWithPublicKey(text, publicKey) {
 const buffer = Buffer.from(text, 'utf8');
 const encrypted = crypto.publicEncrypt(
 {
 key: publicKey,
 padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
 },
 buffer
);
return encrypted.toString('base64');
}
// Decrypt with private key
function decryptWithPrivateKey(encryptedText, privateKey) {
 const buffer = Buffer.from(encryptedText, 'base64');
 const decrypted = crypto.privateDecrypt(
 {
 key: privateKey,
 padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
 },
 buffer
);
return decrypted.toString('utf8');
}
// Generate keys
const { publicKey, privateKey } = generateKeyPair();
console.log('Public Key:', publicKey.substring(0, 50) + '...');
console.log('Private Key:', privateKey.substring(0, 50) + '...');
// Example usage
const message = 'This message is encrypted with RSA';
const encrypted = encryptWithPublicKey(message, publicKey);
console.log('Encrypted:', encrypted.substring(0, 50) + '...');
const decrypted = decryptWithPrivateKey(encrypted, privateKey);
console.log('Decrypted:', decrypted);

Note

RSA is typically used for encrypting small amounts of data (like encryption keys) due to performance constraints.

For larger data, use a hybrid approach: encrypt the data with a symmetric algorithm (like AES) and encrypt the symmetric key with RSA.

Digital Signatures

Digital signatures provide a way to verify the authenticity and integrity of messages, software, or digital documents.

const crypto = require('crypto');
// Generate RSA key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
 modulusLength: 2048,
 publicKeyEncoding: {
 type: 'spki',
 format: 'pem'
 },
 privateKeyEncoding: {
 type: 'pkcs8',
 format: 'pem'
 }
});
// Function to sign a message
function signMessage(message, privateKey) {
 const signer = crypto.createSign('sha256');
 signer.update(message);
 return signer.sign(privateKey, 'base64');
}
// Function to verify a signature
function verifySignature(message, signature, publicKey) {
 const verifier = crypto.createVerify('sha256');
 verifier.update(message);
 return verifier.verify(publicKey, signature, 'base64');
}
// Example usage
const message = 'This message needs to be signed';
const signature = signMessage(message, privateKey);
console.log('Message:', message);
console.log('Signature:', signature.substring(0, 50) + '...');
// Verify the signature
const isValid = verifySignature(message, signature, publicKey);
console.log('Signature valid:', isValid); // true
// Verify with a modified message
const isInvalid = verifySignature('Modified message', signature, publicKey);
console.log('Modified message valid:', isInvalid); // false

Random Data Generation

Generating secure random data is important for many cryptographic operations, such as creating keys, salts, and initialization vectors.

const crypto = require('crypto');
// Generate random bytes
const randomBytes = crypto.randomBytes(16);
console.log('Random bytes:', randomBytes.toString('hex'));
// Generate a random string (Base64)
const randomString = crypto.randomBytes(32).toString('base64');
console.log('Random string:', randomString);
// Generate a random number between 1 and 100
function secureRandomNumber(min, max) {
 // Ensure we have enough randomness
 const range = max - min + 1;
 const bytesNeeded = Math.ceil(Math.log2(range) / 8);
 const maxValue = 256 ** bytesNeeded;
 // Generate random bytes and convert to a number
 const randomBytes = crypto.randomBytes(bytesNeeded);
 const randomValue = randomBytes.reduce((acc, byte, i) => {
 return acc + byte * (256 ** i);
 }, 0);
 // Scale to our range and shift by min
 return min + Math.floor((randomValue * range) / maxValue);
}
// Example: Generate 5 random numbers
for (let i = 0; i < 5; i++) {
 console.log(`Random number ${i+1}:`, secureRandomNumber(1, 100));
}

Security Best Practices

When using the Crypto module, keep these best practices in mind:

  • Use modern algorithms: Avoid MD5, SHA-1, and other outdated algorithms
  • Secure key management: Store keys securely, rotate them regularly, and never hardcode them
  • Use random IVs: Generate a new random IV for each encryption operation
  • Add authentication: Use authenticated encryption modes like GCM when possible
  • Constant-time comparisons: Always use crypto.timingSafeEqual() for comparing security-critical values
  • Key derivation: Use appropriate key derivation functions like scrypt, bcrypt, or PBKDF2 for password-based keys
  • Stay updated: Keep Node.js updated to get security fixes and support for newer algorithms
  • Follow standards: Adhere to established cryptographic standards and protocols

Warning

Cryptography is complex, and mistakes can lead to serious security vulnerabilities.

When implementing critical security features, consider consulting a security specialist or using well-established libraries designed for specific cryptographic tasks.

Summary

The Node.js Crypto module provides a wide range of cryptographic functionality:

  • Hash functions for data integrity and fingerprinting
  • HMAC for authentication and integrity checks
  • Symmetric encryption for securing data with shared keys
  • Asymmetric encryption for secure communication and digital signatures
  • Random data generation for cryptographic operations
  • Digital signatures for authenticity verification

By understanding and properly implementing these cryptographic concepts, you can build secure applications that protect sensitive data and communications.

Previous

Node.js Buffer Module

Next

Node.js Timers Module