Loading lesson path
Concept visual
Start from A
The Hmac class is part of Node.js's crypto module. It provides a way to create cryptographic HMAC (Hash-based Message Authentication Code) digests. HMAC instances are created using the crypto.createHmac() method. HMAC combines a cryptographic hash function with a secret key to produce a message authentication code, providing both data integrity and authentication.
// Import the crypto module const crypto = require('crypto');
// Create an Hmac object const hmac = crypto.createHmac('sha256', 'your-secret-key');Description hmac.update(data[, inputEncoding])
Updates the Hmac content with the given data. If inputEncoding is provided, data is a string using the specified encoding; otherwise, data is a Buffer, TypedArray, or DataView. This method can be called multiple times with new data.
hmac.digest([encoding])
Calculates the HMAC digest of all the data passed to the Hmac using hmac.update(). If encoding is provided, a string is returned; otherwise, a Buffer is returned. After this method is called, the Hmac object can no longer be used.The following example demonstrates how to create an HMAC digest of a string:
const crypto = require('crypto');
// Data to authenticate const data = 'Hello, World!';
// Secret key const secretKey = 'my-secret-key';
// Create an Hmac object const hmac = crypto.createHmac('sha256', secretKey);
// Update the hmac with data hmac.update(data);
// Get the digest in hex format const digest = hmac.digest('hex');
console.log('Data:', data);
console.log('Secret Key:', secretKey);
console.log('HMAC-SHA256:', digest);This example compares different hash algorithms with HMAC:
const crypto = require('crypto');
// Data to authenticate const data = 'Node.js Crypto HMAC Example';
// Secret key const secretKey = 'my-secret-key';
// Function to create HMAC with different algorithms function createHmacWithAlgorithm(algorithm, data, key) {
const hmac = crypto.createHmac(algorithm, key);
hmac.update(data);
return hmac.digest('hex');
}
// Test various HMAC algorithms const algorithms = ['md5', 'sha1', 'sha256', 'sha512', 'sha3-256', 'sha3-512'];
console.log(`Data: "${data}"`);
console.log(`Secret Key: "${secretKey}"`);
console.log('------------------------------------');
algorithms.forEach(algorithm => {
try {
const digest = createHmacWithAlgorithm(algorithm, data, secretKey);
console.log(`HMAC-${algorithm}: ${digest}`);
console.log(`Length: ${digest.length / 2} bytes (${digest.length * 4} bits)`);
console.log('------------------------------------');
} catch (error) {
console.log(`HMAC-${algorithm}: Not supported - ${error.message}`);
console.log('------------------------------------');
}
});You can update an HMAC with multiple pieces of data before calculating the digest:
const crypto = require('crypto');
// Secret key const secretKey = 'my-secret-key';
// Create an Hmac object const hmac = crypto.createHmac('sha256', secretKey);
// Update the hmac with multiple pieces of data hmac.update('First part of the data.');
hmac.update(' Second part of the data.');
hmac.update(' Third part of the data.');
// Calculate the final digest const digest = hmac.digest('hex');
console.log('Combined data: First part of the data. Second part of the data. Third part of the data.');
console.log('Secret Key:', secretKey);
console.log('HMAC-SHA256:', digest);
// You can achieve the same result with a single update const singleHmac = crypto.createHmac('sha256', secretKey);
singleHmac.update('First part of the data. Second part of the data. Third part of the data.');
const singleDigest = singleHmac.digest('hex');
console.log('Single update HMAC matches multiple updates?', singleDigest === digest);You can get an HMAC digest in different encodings:
const crypto = require('crypto');
// Data to authenticate const data = 'Hello, Node.js!';
// Secret key const secretKey = 'my-secret-key';
// Function to create HMAC and get digest in different encodings function createHmacWithEncoding(algorithm, data, key, encoding) {
const hmac = crypto.createHmac(algorithm, key);
hmac.update(data);
return hmac.digest(encoding);
}Formula
// Create HMAC with SHA - 256 and display in different encodings console.log(`Data: "${data}"`);console.log(`Secret Key: "${secretKey}"`);
console.log(`HMAC-SHA256 (hex): ${createHmacWithEncoding('sha256', data, secretKey, 'hex')}`);
console.log(`HMAC-SHA256 (base64): ${createHmacWithEncoding('sha256', data, secretKey, 'base64')}`);
console.log(`HMAC-SHA256 (base64url): ${createHmacWithEncoding('sha256', data, secretKey, 'base64url')}`);
console.log(`HMAC-SHA256 (binary): ${createHmacWithEncoding('sha256', data, secretKey, 'binary')}`);
// Get the digest as a Buffer (no encoding)
const hmac = crypto.createHmac('sha256', secretKey);
hmac.update(data);
const buffer = hmac.digest();
console.log('HMAC-SHA256 (Buffer):', buffer);
console.log('Buffer length:', buffer.length, 'bytes');You can create an HMAC digest of a file's contents:
const crypto = require('crypto');
const fs = require('fs');
// Function to create HMAC for a file using streams function createHmacForFile(filePath, algorithm, key) {
return new Promise((resolve, reject) => {
// Create Hmac object const hmac = crypto.createHmac(algorithm, key);
// Create read stream const stream = fs.createReadStream(filePath);
// Handle stream events stream.on('data', (data) => {
hmac.update(data);
});
stream.on('end', () => {
const digest = hmac.digest('hex');
resolve(digest);
});
stream.on('error', (error) => {
reject(error);
});
});
}
// Secret key const secretKey = 'file-authentication-key';
// Example usage (adjust file path as needed)
const filePath = 'example.txt';
// Create a test file if it doesn't exist if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, 'This is a test file for HMAC authentication.\n'.repeat(100));
console.log(`Created test file: ${filePath}`);
}// Create HMAC for the file with different algorithms Promise.all([ createHmacForFile(filePath, 'md5', secretKey), createHmacForFile(filePath, 'sha1', secretKey), createHmacForFile(filePath, 'sha256', secretKey)
]).then(([md5Digest, sha1Digest, sha256Digest]) => {
console.log(`File: ${filePath}`);
console.log(`Secret Key: ${secretKey}`);
console.log(`HMAC-MD5: ${md5Digest}`);
console.log(`HMAC-SHA1: ${sha1Digest}`);
console.log(`HMAC-SHA256: ${sha256Digest}`);
// Store the HMAC for later verification fs.writeFileSync(`${filePath}.hmac`, sha256Digest);
console.log(`HMAC stored in: ${filePath}.hmac`);
}).catch(error => {
console.error('Error creating HMAC for file:', error.message);
});This example demonstrates how to verify a file's integrity using a previously generated HMAC:
const crypto = require('crypto');
const fs = require('fs');
// Function to create HMAC for a file function createHmacForFile(filePath, algorithm, key) {
return new Promise((resolve, reject) => {
const hmac = crypto.createHmac(algorithm, key);
const stream = fs.createReadStream(filePath);
stream.on('data', (data) => {
hmac.update(data);
});
stream.on('end', () => {
const digest = hmac.digest('hex');
resolve(digest);
});
stream.on('error', (error) => {
reject(error);
});
});
}
// Function to verify file integrity async function verifyFileIntegrity(filePath, storedHmacPath, algorithm, key) {
try {// Read the stored HMAC
const storedHmac = fs.readFileSync(storedHmacPath, 'utf8').trim();// Calculate the current HMAC
const currentHmac = await createHmacForFile(filePath, algorithm, key);
// Compare the HMACs const isValid = currentHmac === storedHmac;
return {isValid, storedHmac, currentHmac
};
} catch (error) {
throw new Error(`Verification failed: ${error.message}`);
}
}
// Secret key (must be the same as used to create the original HMAC)
const secretKey = 'file-authentication-key';
// Example usage const filePath = 'example.txt';
const hmacPath = `${filePath}.hmac`;
// Verify the file integrity verifyFileIntegrity(filePath, hmacPath, 'sha256', secretKey).then(result => {
console.log(`File: ${filePath}`);
console.log(`HMAC file: ${hmacPath}`);
console.log(`Integrity verified: ${result.isValid}`);
if (!result.isValid) {
console.log('Stored HMAC:', result.storedHmac);
console.log('Current HMAC:', result.currentHmac);
console.log('The file has been modified!');
} else {
console.log('The file is intact and has not been tampered with.');
}
}).catch(error => {
console.error('Error:', error.message);
});HMAC can work with different types of keys:
const crypto = require('crypto');
// Data to authenticate const data = 'Data to authenticate with HMAC';
// Function to create HMAC with different key types function createHmacWithKey(algorithm, data, key, keyType) {
const hmac = crypto.createHmac(algorithm, key);
hmac.update(data);
return {
keyType, hmac: hmac.digest('hex')
};
}
console.log(`Data: "${data}"`);
console.log('------------------------------------');
// 1. String key const stringKey = 'my-secret-key';
console.log(createHmacWithKey('sha256', data, stringKey, 'String key'));
// 2. Buffer key const bufferKey = Buffer.from('buffer-secret-key');
console.log(createHmacWithKey('sha256', data, bufferKey, 'Buffer key'));
// 3. TypedArray key const uint8ArrayKey = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in ASCII
console.log(createHmacWithKey('sha256', data, uint8ArrayKey, 'Uint8Array key'));
// 4. DataView key const arrayBuffer = new ArrayBuffer(5);
const dataView = new DataView(arrayBuffer);
dataView.setUint8(0, 72); // H
dataView.setUint8(1, 101); // e dataView.setUint8(2, 108); // l dataView.setUint8(3, 108); // l dataView.setUint8(4, 111); // o console.log(createHmacWithKey('sha256', data, dataView, 'DataView key'));
// 5. KeyObject (recommended for sensitive keys)
const keyObject = crypto.createSecretKey(Buffer.from('key-object-secret'));
console.log(createHmacWithKey('sha256', data, keyObject, 'KeyObject'));HMAC is commonly used for API authentication, where the server and client share a secret key:
const crypto = require('crypto');
// Simulated API request function createApiRequest(apiKey, secretKey, method, path, queryParams, body, timestamp) {
// Create the string to sign const stringToSign = [
method.toUpperCase(), path, new URLSearchParams(queryParams).toString(), typeof body === 'string' ? body : JSON.stringify(body || {}), timestamp
].join('\n');
// Create HMAC signature const hmac = crypto.createHmac('sha256', secretKey);
hmac.update(stringToSign);
const signature = hmac.digest('hex');
// Return the request with authentication headers return {
url: `https://api.example.com${path}?${new URLSearchParams(queryParams)}`, method, headers: {Formula
'Content - Type': 'application/json',
'X - Api - Key': apiKey,
'X - Timestamp': timestamp,
'X - Signature': signature}, body: body || {},Formula
// For debugging/verification stringToSign};
}
// Simulate API server verification function verifyApiRequest(apiKey, secretKey, method, path, queryParams, body, timestamp, signature) {
// Recreate the string that was signed const stringToSign = [
method.toUpperCase(), path, new URLSearchParams(queryParams).toString(), typeof body === 'string' ? body : JSON.stringify(body || {}), timestamp
].join('\n');
// Verify HMAC signature const hmac = crypto.createHmac('sha256', secretKey);
hmac.update(stringToSign);
const expectedSignature = hmac.digest('hex');
return {
isValid: crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')), expectedSignature
};
}
// API credentials const apiKey = 'user123';
const secretKey = 'very-secret-api-key';
// Create a request const timestamp = new Date().toISOString();
const request = createApiRequest(apiKey, secretKey, 'POST',
Formula
'/api/v1/users',{ filter: 'active' },
{ name: 'John Doe', email: 'john@example.com' }, timestamp
);
console.log('API Request:');
console.log(`URL: ${request.url}`);
console.log(`Method: ${request.method}`);
console.log('Headers:', request.headers);
console.log('Body:', request.body);
console.log('\nString that was signed:');
console.log(request.stringToSign);
// Server verifies the request const verification = verifyApiRequest(apiKey, secretKey, 'POST',
Formula
'/api/v1/users',{ filter: 'active' },
{ name: 'John Doe', email: 'john@example.com' }, timestamp, request.headers['X-Signature']
);
console.log('\nVerification result:');
console.log(`Is signature valid? ${verification.isValid}`);
// Try with tampered data const tamperedVerification = verifyApiRequest(apiKey, secretKey, 'POST',
Formula
'/api/v1/users',{ filter: 'active' },
{ name: 'Jane Doe', email: 'jane@example.com' }, // Changed body timestamp, request.headers['X-Signature']
);
console.log('\nTampered verification result:');
console.log(`Is signature valid? ${tamperedVerification.isValid}`);This example demonstrates the difference between a plain hash and an HMAC:
const crypto = require('crypto');
// Data and keys const data = 'Message to authenticate';
const key1 = 'secret-key-1';
const key2 = 'secret-key-2';
// Plain SHA-256 hash (no key) function createHash(data) {
const hash = crypto.createHash('sha256');
hash.update(data);
return hash.digest('hex');
}
// HMAC-SHA-256 (with key) function createHmac(data, key) {
const hmac = crypto.createHmac('sha256', key);
hmac.update(data);
return hmac.digest('hex');
}
// Compare results console.log(`Data: "${data}"`);
console.log('\nPlain SHA-256 (no key):');
console.log(createHash(data));
console.log('\nHMAC-SHA-256 with key1:');
console.log(createHmac(data, key1));
console.log('\nHMAC-SHA-256 with key2:');
console.log(createHmac(data, key2));// Demonstrate hash extension attack vulnerability // This is a simplified illustration - actual extension attacks are more complex console.log('\nHash Extension Attack Vulnerability:');
const originalData = 'original-message';
const originalHash = createHash(originalData);
console.log(`Original data: "${originalData}"`);
console.log(`Original SHA-256: ${originalHash}`);
// Attacker doesn't know the original data, but knows its hash
// and wants to append malicious data const appendedData = 'malicious-appendage';
const combinedData = `${originalData}${appendedData}`;
const combinedHash = createHash(combinedData);
console.log(`Appended data: "${appendedData}"`);
console.log(`Combined data: "${combinedData}"`);
console.log(`Combined SHA-256: ${combinedHash}`);
console.log('With plain hash, an attacker who knows the hash of original data can compute valid hash for combined data without knowing the original data');
// HMAC is not vulnerable to extension attacks console.log('\nHMAC Protection:');
const originalHmac = createHmac(originalData, key1);
const combinedHmac = createHmac(combinedData, key1);
console.log(`Original HMAC: ${originalHmac}`);
console.log(`Combined HMAC: ${combinedHmac}`);
console.log('With HMAC, an attacker cannot compute a valid HMAC for combined data without knowing the secret key');When using HMAC, consider these security best practices:
Formula
: Prefer SHA - 256, SHA - 384, SHA - 512, or SHA - 3 over MD5 and SHA - 1.Use a strong, random key
Formula
: The key should be at least as long as the hash output (e.g., 32 bytes for SHA - 256).: The security of HMAC depends on the secrecy of the key.
: When verifying HMAC values, use crypto.timingSafeEqual() to avoid timing attacks.
: Consider using the KeyObject API or a key management service for sensitive keys.
: HMAC provides data integrity and authentication, not confidentiality. For encryption, combine HMAC with encryption algorithms.
: Signing API requests to verify the sender's identity and data integrity.
: Ensuring messages haven't been tampered with during transmission.
: Creating and verifying signed cookies or tokens in web applications.
: Checking that files haven't been modified or corrupted.