This error occurs when the crypto.createHmac() method receives a key argument that is not a valid type. Node.js requires the HMAC key to be a string, Buffer, TypedArray, DataView, or KeyObject, and passing undefined, null, or other invalid types will cause this error.
The HMAC key type error is a TypeError thrown by Node.js's crypto module when attempting to create an HMAC (Hash-based Message Authentication Code) with an invalid key parameter. The crypto.createHmac() function is strict about the data types it accepts for cryptographic operations to ensure security and prevent runtime errors. This error typically surfaces during authentication workflows, API signature generation, or webhook validation where HMAC signatures are used to verify message integrity. The crypto module expects the key to be provided in a format that can be processed for cryptographic hashing - either as a string, binary data (Buffer, TypedArray, DataView), or a KeyObject with type 'secret'. The most common cause is passing undefined or null to createHmac(), which usually happens when environment variables containing secrets are not properly loaded or when configuration objects are missing required properties. This error prevents the HMAC operation from starting rather than failing silently with incorrect signatures.
Check that your secret key environment variable is properly loaded before using it:
// Add validation before using the key
const secretKey = process.env.HMAC_SECRET;
if (!secretKey) {
throw new Error('HMAC_SECRET environment variable is not set');
}
const hmac = crypto.createHmac('sha256', secretKey);For applications using dotenv, ensure it's loaded at the entry point:
import dotenv from 'dotenv';
dotenv.config();
// Now environment variables are available
const hmac = crypto.createHmac('sha256', process.env.HMAC_SECRET);Check your .env file contains the key:
HMAC_SECRET=your-secret-key-hereUse the logical OR operator or nullish coalescing to provide fallback values (though defaults should only be used in development):
const secretKey = process.env.HMAC_SECRET || 'development-secret-only';
const hmac = crypto.createHmac('sha256', secretKey);For TypeScript projects with strict type checking:
const secretKey: string = process.env.HMAC_SECRET ?? '';
if (secretKey.length === 0) {
throw new Error('HMAC_SECRET must be configured');
}
const hmac = crypto.createHmac('sha256', secretKey);Warning: Never use default secrets in production - always fail fast if the secret is not configured.
If you're working with keys in different formats, ensure they're converted to Buffer:
import crypto from 'crypto';
// If key is in base64
const keyBase64 = process.env.HMAC_SECRET_BASE64;
const keyBuffer = Buffer.from(keyBase64, 'base64');
const hmac = crypto.createHmac('sha256', keyBuffer);
// If key is in hex
const keyHex = process.env.HMAC_SECRET_HEX;
const keyBuffer = Buffer.from(keyHex, 'hex');
const hmac = crypto.createHmac('sha256', keyBuffer);
// For binary key data
const keyBinary = new Uint8Array([/* your binary data */]);
const hmac = crypto.createHmac('sha256', keyBinary);This ensures the key is in a valid format regardless of how it's stored.
For more secure key handling, use the KeyObject API:
import crypto from 'crypto';
// Create a KeyObject from your secret
const secretKey = process.env.HMAC_SECRET;
const keyObject = crypto.createSecretKey(Buffer.from(secretKey, 'utf8'));
// Use the KeyObject with createHmac
const hmac = crypto.createHmac('sha256', keyObject);
hmac.update('data to sign');
const signature = hmac.digest('hex');KeyObject provides better security as it keeps the key material in internal representations rather than exposing it as plain strings or buffers throughout your code.
If using TypeScript, add type guards to catch undefined keys at compile time:
import crypto from 'crypto';
function createHmacSafe(algorithm: string, key: string | Buffer): crypto.Hmac {
if (!key || (typeof key === 'string' && key.length === 0)) {
throw new TypeError('HMAC key must be a non-empty string or Buffer');
}
return crypto.createHmac(algorithm, key);
}
// Usage
const secretKey = process.env.HMAC_SECRET;
if (!secretKey) {
throw new Error('HMAC_SECRET not configured');
}
const hmac = createHmacSafe('sha256', secretKey);This provides clear error messages and prevents the TypeError from reaching production.
Browser vs Node.js Crypto
The native Node.js crypto module (node:crypto) is not available in browser environments. If you see "crypto.createHmac is not a function" in a frontend application, you're trying to use Node.js APIs in the browser. Use the Web Crypto API instead:
// Browser-compatible HMAC
async function createHmacBrowser(algorithm, key, data) {
const encoder = new TextEncoder();
const keyBuffer = encoder.encode(key);
const dataBuffer = encoder.encode(data);
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyBuffer,
{ name: 'HMAC', hash: algorithm },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, dataBuffer);
return Buffer.from(signature).toString('hex');
}Crypto-JS vs Native Crypto
Don't confuse the npm package crypto-js with Node.js's native crypto module:
// WRONG - crypto-js (different API)
import crypto from 'crypto-js';
const hash = crypto.HmacSHA256('message', 'secret');
// CORRECT - Native Node.js crypto
import crypto from 'node:crypto';
const hmac = crypto.createHmac('sha256', 'secret');
hmac.update('message');
const hash = hmac.digest('hex');Async Configuration Loading
If you're loading configuration asynchronously, ensure it completes before creating HMAC instances:
// config.js
let config = null;
export async function loadConfig() {
const response = await fetch('/api/config');
config = await response.json();
}
export function getHmacKey() {
if (!config) {
throw new Error('Configuration not loaded - call loadConfig() first');
}
return config.hmacSecret;
}
// app.js
import { loadConfig, getHmacKey } from './config.js';
await loadConfig(); // Wait for config
const hmac = crypto.createHmac('sha256', getHmacKey());Webhook Signature Validation Pattern
A common pattern for webhook validation with proper error handling:
import crypto from 'crypto';
function validateWebhookSignature(payload, signature, secret) {
if (!secret) {
throw new Error('Webhook secret not configured');
}
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload));
const expectedSignature = hmac.digest('hex');
// Use timingSafeEqual to prevent timing attacks
const signatureBuffer = Buffer.from(signature);
const expectedBuffer = Buffer.from(expectedSignature);
if (signatureBuffer.length !== expectedBuffer.length) {
return false;
}
return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);
}KeyObject Benefits
Using KeyObject provides several advantages:
- Keys are stored in internal representations, reducing exposure
- Supports key export in various formats (PEM, DER, JWK)
- Compatible with all crypto APIs (createHmac, createSign, etc.)
- Better memory management for sensitive cryptographic material
- Type safety with explicit 'secret', 'public', or 'private' key types
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process