This error occurs when Node.js crypto module cannot parse a public key because it is in an incorrect format, has encoding issues, or lacks proper structure. The crypto.createPublicKey() function expects keys in specific formats like PEM or DER with correct headers and encoding.
This error is thrown by Node.js's crypto module when attempting to create or use a public key that cannot be successfully parsed. The crypto functions like createPublicKey(), publicEncrypt(), or publicDecrypt() require keys to follow strict formatting standards defined by cryptographic specifications. The most common scenario is when working with PEM-encoded keys that lack proper headers (-----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY-----), have incorrect line breaks, or contain corrupted data. The error can also occur when there's a mismatch between the declared format (PEM, DER, JWK) and the actual key data structure. Node.js uses OpenSSL under the hood for cryptographic operations, and this error typically indicates that the underlying OpenSSL library cannot interpret the key data according to the specified format.
If using PEM format, ensure your key has the correct headers and structure:
// Correct PEM public key format
const validPEM = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`;
const crypto = require('crypto');
const publicKey = crypto.createPublicKey(validPEM);Check that:
- Headers are present and exactly match the format above
- There are no extra spaces before or after the headers
- The base64 content is between the headers
- Line breaks are preserved (typically 64 characters per line)
Use OpenSSL to verify the key is valid before using it in Node.js:
# Validate and display public key
openssl pkey -in public-key.pem -pubin -text -noout
# Convert DER to PEM if needed
openssl pkey -inform DER -in public-key.der -pubin -outform PEM -out public-key.pemIf OpenSSL reports errors, the key file is corrupted or in an unexpected format.
When creating a public key, explicitly specify the format to avoid ambiguity:
const crypto = require('crypto');
const fs = require('fs');
// For PEM format (string)
const pemKey = fs.readFileSync('public-key.pem', 'utf8');
const publicKey = crypto.createPublicKey({
key: pemKey,
format: 'pem',
type: 'spki' // SubjectPublicKeyInfo
});
// For DER format (buffer)
const derKey = fs.readFileSync('public-key.der');
const publicKey2 = crypto.createPublicKey({
key: derKey,
format: 'der',
type: 'spki'
});
// For JWK format (object, not string)
const jwkKey = {
kty: 'RSA',
n: 'base64url_encoded_modulus...',
e: 'AQAB'
};
const publicKey3 = crypto.createPublicKey({
key: jwkKey,
format: 'jwk'
});If the key is base64-encoded (common when received from APIs), decode it first:
const crypto = require('crypto');
// Key received as base64 string from API
const base64Key = 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0...';
// Decode base64 to get actual PEM string
const pemKey = Buffer.from(base64Key, 'base64').toString('utf8');
// Now create the public key
const publicKey = crypto.createPublicKey(pemKey);Common mistake: Passing the base64 string directly without decoding.
Clean the key string to remove common formatting issues:
function normalizeKeyFormat(keyString) {
// Remove leading/trailing whitespace
let normalized = keyString.trim();
// Ensure consistent line breaks (LF instead of CRLF)
normalized = normalized.replace(/\r\n/g, '\n');
// Remove any extra spaces within the key
normalized = normalized.replace(/ +/g, ' ');
return normalized;
}
const rawKey = ` -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY----- `;
const cleanKey = normalizeKeyFormat(rawKey);
const publicKey = crypto.createPublicKey(cleanKey);If you only have a private key but need the public key, extract it:
const crypto = require('crypto');
const fs = require('fs');
// Load private key
const privateKey = crypto.createPrivateKey(
fs.readFileSync('private-key.pem', 'utf8')
);
// Extract public key from private key
const publicKey = crypto.createPublicKey(privateKey);
// Export in desired format
const publicKeyPEM = publicKey.export({
type: 'spki',
format: 'pem'
});
console.log(publicKeyPEM);Format Compatibility Matrix:
- PEM + X.509 certificate: Supported for createPublicKey()
- DER + X.509 certificate: NOT supported (use PEM instead)
- JWK format: Key parameter must be a JavaScript object, not a stringified JSON
- PKCS#1 vs SPKI: Use type 'spki' for public keys, 'pkcs1' is for RSA-specific format
JWK Format Gotchas:
When using JWK format with createPublicKey(), there have been documented robustness issues in Node.js versions before v18. If you encounter errors with JWK, consider converting to PEM format as a workaround.
Performance Considerations:
Starting with Node.js v17, there was a documented performance regression in createPublicKey() and createPrivateKey() APIs. If parsing many keys in a loop, consider caching the KeyObject instances rather than recreating them.
Debugging Techniques:
To see exactly what Node.js receives:
console.log('Key type:', typeof key);
console.log('Key length:', key.length);
console.log('First 50 chars:', key.substring(0, 50));
console.log('Last 50 chars:', key.substring(key.length - 50));Common Third-Party Integration Issues:
When importing keys from cloud providers (AWS KMS, Azure Key Vault, GCP KMS), the keys are often returned in JWK format or base64-encoded PEM. Always check the provider's documentation for the exact format returned and apply the appropriate decoding/transformation.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams