Loading lesson path
Concept visual
Start from A
The ECDH (Elliptic Curve Diffie-Hellman) class is part of Node.js's crypto module. It implements the Elliptic Curve Diffie-Hellman key exchange protocol, which allows two parties to securely establish a shared secret over an insecure channel using elliptic curve cryptography. ECDH offers advantages over traditional Diffie-Hellman key exchange, including smaller key sizes, faster computation, and equivalent security strength.
// Import the crypto module const crypto = require('crypto');
// Create an ECDH instance with a specific curve const ecdh = crypto.createECDH('prime256v1'); // Also known as P-256 or secp256r1Description ecdh.generateKeys([encoding[, format]])
Generates private and public EC Diffie-Hellman key values. If encoding is provided, a string is returned; otherwise, a Buffer is returned. The format argument specifies point encoding and can be 'compressed', 'uncompressed', or 'hybrid'.
ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])
Computes the shared secret using the other party's public key. If inputEncoding is provided, otherPublicKey is expected to be a string; otherwise, a Buffer, TypedArray, or DataView. If outputEncoding is provided, a string is returned; otherwise, a Buffer is returned.
ecdh.getPrivateKey([encoding])
Returns the EC Diffie-Hellman private key. If encoding is provided, a string is returned; otherwise, a Buffer is returned.
ecdh.getPublicKey([encoding][, format])
Returns the EC Diffie-Hellman public key. If encoding is provided, a string is returned; otherwise, a Buffer is returned. The format argument specifies point encoding and can be 'compressed', 'uncompressed', or 'hybrid'.
ecdh.setPrivateKey(privateKey[, encoding])
Sets the EC Diffie-Hellman private key. If encoding is provided, privateKey is expected to be a string; otherwise, a Buffer, TypedArray, or DataView.Node.js supports various elliptic curves for ECDH. You can get a list of all supported curves with:
const crypto = require('crypto');
// Get all supported elliptic curves console.log(crypto.getCurves());Formula
P - 256, secp256r1256 bits 128 bits secp384r1
Formula
P - 384384 bits 192 bits secp521r1
Formula
P - 521521 bits 256 bits secp256k1 (Bitcoin curve) 256 bits 128 bits ed25519 curve25519 255 bits 128 bits
The following example demonstrates the basic ECDH key exchange between two parties (Alice and Bob):
const crypto = require('crypto');
// Alice creates an ECDH instance and generates keys console.log('Alice: Creating ECDH instance...');
const alice = crypto.createECDH('prime256v1');
alice.generateKeys();
// Bob creates an ECDH instance and generates keys console.log('Bob: Creating ECDH instance...');
const bob = crypto.createECDH('prime256v1');
bob.generateKeys();
// Exchange public keys (over an insecure channel)
console.log('Exchanging public keys...');
const alicePublicKey = alice.getPublicKey();
const bobPublicKey = bob.getPublicKey();
// Alice computes the shared secret using Bob's public key console.log('Alice: Computing shared secret...');
const aliceSecret = alice.computeSecret(bobPublicKey);
// Bob computes the shared secret using Alice's public key console.log('Bob: Computing shared secret...');
const bobSecret = bob.computeSecret(alicePublicKey);
// Both secrets should be the same console.log('Alice\'s secret:', aliceSecret.toString('hex'));
console.log('Bob\'s secret:', bobSecret.toString('hex'));
console.log('Do they match?', aliceSecret.equals(bobSecret));// This shared secret can now be used as a key for symmetric encryption
ECDH supports different public key encoding formats:
const crypto = require('crypto');
// Create an ECDH instance const ecdh = crypto.createECDH('prime256v1');
ecdh.generateKeys();
// Get public key in different formats const uncompressedKey = ecdh.getPublicKey('hex', 'uncompressed');
const compressedKey = ecdh.getPublicKey('hex', 'compressed');
const hybridKey = ecdh.getPublicKey('hex', 'hybrid');
console.log('Uncompressed public key:', uncompressedKey);
console.log('Compressed public key:', compressedKey);
console.log('Hybrid public key:', hybridKey);
// Get key length in each format console.log('\nKey lengths:');
console.log('Uncompressed:', Buffer.from(uncompressedKey, 'hex').length, 'bytes');
console.log('Compressed:', Buffer.from(compressedKey, 'hex').length, 'bytes');
console.log('Hybrid:', Buffer.from(hybridKey, 'hex').length, 'bytes');
// Use a public key in different formats const otherEcdh = crypto.createECDH('prime256v1');
otherEcdh.generateKeys();
// Another party can use any format to compute the same secret const secret1 = otherEcdh.computeSecret(
Buffer.from(uncompressedKey, 'hex')
);
const secret2 = otherEcdh.computeSecret(
Buffer.from(compressedKey, 'hex')
);
console.log('\nSame secret computed from different formats?', secret1.equals(secret2));This example shows a complete scenario of using ECDH to establish a shared key for AES encryption:
const crypto = require('crypto');
// Create ECDH instances for Alice and Bob const alice = crypto.createECDH('prime256v1');
alice.generateKeys();
const bob = crypto.createECDH('prime256v1');
bob.generateKeys();
// Exchange public keys const alicePublicKey = alice.getPublicKey();
const bobPublicKey = bob.getPublicKey();
// Compute shared secrets const aliceSecret = alice.computeSecret(bobPublicKey);
const bobSecret = bob.computeSecret(alicePublicKey);// Use the shared secret as a key for encryption
// First, derive a suitable key using a hash function function deriveKey(secret, salt, keyLength) {
return crypto.pbkdf2Sync(secret, salt, 1000, keyLength, 'sha256');
}
// Alice sends an encrypted message to Bob function encrypt(text, secret) {
// Create a salt and derive a key const salt = crypto.randomBytes(16);
const key = deriveKey(secret, salt, 32); // 32 bytes for AES-256 const iv = crypto.randomBytes(16);
// Encrypt the message const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
// Return everything Bob needs to decrypt return {
salt: salt.toString('hex'), iv: iv.toString('hex'), encrypted
};
}
// Bob decrypts the message from Alice function decrypt(encryptedInfo, secret) {
// Parse values const salt = Buffer.from(encryptedInfo.salt, 'hex');
const iv = Buffer.from(encryptedInfo.iv, 'hex');
const encrypted = encryptedInfo.encrypted;
// Derive the same key const key = deriveKey(secret, salt, 32);
// Decrypt the message const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Alice encrypts a message using the shared secret const message = 'Hello Bob, this is a secret message from Alice using ECDH!';
console.log('Original message:', message);
const encryptedMessage = encrypt(message, aliceSecret);
console.log('Encrypted message:', encryptedMessage);
// Bob decrypts the message using his shared secret const decryptedMessage = decrypt(encryptedMessage, bobSecret);
console.log('Decrypted message:', decryptedMessage);
// Verify the result console.log('Decryption successful:', message === decryptedMessage);You can manually set a private key instead of generating one:
const crypto = require('crypto');
// Create an ECDH instance const ecdh = crypto.createECDH('prime256v1');
// Generate a random private key (32 bytes for prime256v1)
const privateKey = crypto.randomBytes(32);
console.log('Private key (hex):', privateKey.toString('hex'));
// Set the private key ecdh.setPrivateKey(privateKey);
// Derive the public key from the private key const publicKey = ecdh.getPublicKey('hex', 'uncompressed');
console.log('Public key (hex):', publicKey);
// You can also set the private key from a hex string const ecdh2 = crypto.createECDH('prime256v1');
ecdh2.setPrivateKey(privateKey.toString('hex'), 'hex');
// Check if both instances generate the same public key const publicKey2 = ecdh2.getPublicKey('hex', 'uncompressed');
console.log('Same public keys:', publicKey === publicKey2);// This is useful for deterministic key generation or when loading keys from storage
This example shows how to use different elliptic curves with ECDH:
const crypto = require('crypto');
// Function to perform ECDH key exchange with a specific curve function testCurve(curveName) {
console.log(`\nTesting curve: ${curveName}`);
try {
// Create ECDH instances const alice = crypto.createECDH(curveName);
alice.generateKeys();
const bob = crypto.createECDH(curveName);
bob.generateKeys();
// Exchange public keys const alicePublicKey = alice.getPublicKey();
const bobPublicKey = bob.getPublicKey();
// Compute shared secrets const aliceSecret = alice.computeSecret(bobPublicKey);
const bobSecret = bob.computeSecret(alicePublicKey);
// Check if secrets match const match = aliceSecret.equals(bobSecret);
// Output results console.log(`Public key size: ${alicePublicKey.length} bytes`);
console.log(`Shared secret size: ${aliceSecret.length} bytes`);
console.log(`Secrets match: ${match}`);
return match;
} catch (error) {
console.error(`Error with curve ${curveName}: ${error.message}`);
return false;
}
}
// Test different curves const curves = [Formula
'prime256v1', // P - 256 / secp256r1
'secp384r1', // P - 384
'secp521r1', // P - 521'secp256k1', // Bitcoin curve 'curve25519' // Ed25519 curve (if supported)
];
curves.forEach(curve => {
testCurve(curve);
});// Note: Not all curves may be supported in your Node.js version
Formula
This example compares the performance of ECDH with traditional Diffie - Hellman:const crypto = require('crypto');
const { performance } = require('perf_hooks');
// Function to measure DH key generation time function measureDH(bits) {
const startTime = performance.now();
const dh = crypto.createDiffieHellman(bits);
dh.generateKeys();
const endTime = performance.now();
return endTime - startTime;
}
// Function to measure ECDH key generation time function measureECDH(curve) {
const startTime = performance.now();
const ecdh = crypto.createECDH(curve);
ecdh.generateKeys();
const endTime = performance.now();
return endTime - startTime;
}
// Function to measure secret computation time function measureSecretComputation(type, params) {
let alice, bob;
// Create instances and generate keys if (type === 'DH') {
alice = crypto.createDiffieHellman(params);
alice.generateKeys();
bob = crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator());
bob.generateKeys();
} else {
alice = crypto.createECDH(params);
alice.generateKeys();
bob = crypto.createECDH(params);
bob.generateKeys();
}
// Exchange public keys const alicePublicKey = alice.getPublicKey();
const bobPublicKey = bob.getPublicKey();
// Measure time for computing secrets const startTime = performance.now();
alice.computeSecret(bobPublicKey);
bob.computeSecret(alicePublicKey);
const endTime = performance.now();
return endTime - startTime;
}
// Run performance tests console.log('Key Generation Performance:');
console.log(`DH (1024 bits): ${measureDH(1024).toFixed(2)} ms`);
console.log(`DH (2048 bits): ${measureDH(2048).toFixed(2)} ms`);
console.log(`ECDH (P-256): ${measureECDH('prime256v1').toFixed(2)} ms`);
console.log(`ECDH (P-384): ${measureECDH('secp384r1').toFixed(2)} ms`);
console.log(`ECDH (P-521): ${measureECDH('secp521r1').toFixed(2)} ms`);
console.log('\nSecret Computation Performance:');
console.log(`DH (1024 bits): ${measureSecretComputation('DH', 1024).toFixed(2)} ms`);
console.log(`DH (2048 bits): ${measureSecretComputation('DH', 2048).toFixed(2)} ms`);
console.log(`ECDH (P-256): ${measureSecretComputation('ECDH', 'prime256v1').toFixed(2)} ms`);
console.log(`ECDH (P-384): ${measureSecretComputation('ECDH', 'secp384r1').toFixed(2)} ms`);
console.log(`ECDH (P-521): ${measureSecretComputation('ECDH', 'secp521r1').toFixed(2)} ms`);This example shows how to generate ECDH key pairs for use with TLS:
const crypto = require('crypto');
const fs = require('fs');
// Function to generate and save ECDH keys for TLS function generateEcdhKeysForTLS(curveName, keyFilePrefix) {
// Create ECDH instance const ecdh = crypto.createECDH(curveName);
// Generate keys ecdh.generateKeys();
// Get keys in PEM format const privateKey = ecdh.getPrivateKey('hex');
const publicKey = ecdh.getPublicKey('hex', 'uncompressed');
// Save keys to files fs.writeFileSync(`${keyFilePrefix}_private.hex`, privateKey);
fs.writeFileSync(`${keyFilePrefix}_public.hex`, publicKey);
console.log(`Generated ECDH key pair using ${curveName}`);
console.log(`Private key saved to ${keyFilePrefix}_private.hex`);
console.log(`Public key saved to ${keyFilePrefix}_public.hex`);
return {curve: curveName, privateKey, publicKey
};
}
// Generate keys for different curves generateEcdhKeysForTLS('prime256v1', 'ecdh_p256');
generateEcdhKeysForTLS('secp384r1', 'ecdh_p384');
console.log("\nThese keys can be used for ECDHE (Ephemeral ECDH) in TLS connections.");
console.log("In a real application, you would use these with the TLS module or a library like Node.js's tls module.");When using ECDH key exchange, consider these security best practices:
: For most applications, P-256 (prime256v1) provides a good balance of security and performance. For higher security requirements, consider P-384 or P-521.
: Some curves are known to have weaknesses. Always use standard curves recommended by security authorities.
: Generate new ECDH key pairs for each session to provide forward secrecy.
: Pure ECDH (like DH) is vulnerable to man-in-the-middle attacks. Consider using authenticated key exchange protocols like ECDHE with digital signatures.
: Never expose private keys in logs, debugging output, or client-side code.: Don't use the shared secret directly as an encryption key. Use a key derivation function (KDF) like HKDF or PBKDF2.