Node.js Crypto Module
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);
Try it Yourself »
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
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);
Try it Yourself »
In this example:
createHash()
creates a hash object with the specified algorithmupdate()
updates the hash content with the given datadigest()
calculates the digest and outputs it in the specified format
Common Hash Algorithms
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);
Try it Yourself »
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
Run example »
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
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);
Try it Yourself »
HMAC for Message Verification
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);
}
Try it Yourself »
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
Algorithm | Key Size | Block Size | Notes |
---|---|---|---|
AES-256 | 256 bits | 128 bits | Current standard, widely used |
ChaCha20 | 256 bits | 512 bits | Faster in software, used in TLS 1.3 |
3DES | 168 bits | 64 bits | Legacy, not recommended for new systems |
Blowfish | 32-448 bits | 64 bits | Legacy, 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);
Run example »
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:
const crypto = require('crypto');
// List available cipher algorithms
console.log(crypto.getCiphers());
Try it Yourself »
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
Algorithm | Key Size | Security Level | Notes |
---|---|---|---|
RSA | 2048+ bits | High | Widely used, good compatibility |
ECDSA | 256-521 bits | High | Used in TLS 1.3, Bitcoin |
Ed25519 | 256 bits | Very High | Modern, 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);
Run example »
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
Run example »
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));
}
Run example »
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.