DiffieHellman
Secure key exchange protocol
The Diffie-Hellman module implements the Diffie-Hellman key exchange protocol, allowing two parties to establish a shared secret over an insecure channel without ever transmitting the secret itself. This is fundamental to secure communication protocols like TLS, SSH, and VPNs.
Real-World Applications
TLS/SSL handshakes, VPN connections (IPsec, WireGuard), secure messaging protocols (Signal, WhatsApp), SSH key exchange, and P2P encrypted file sharing.
Table of Contents
- Theory
- Class: DiffieHellman
- Module Methods
- Standard Groups
- Real-World Examples
- Security Considerations
Theory
The Diffie-Hellman protocol allows two parties (Alice and Bob) to agree on a shared secret even when all their communication is being monitored:
- Public Parameters: Both parties agree on a large prime number and a generator
- Private Keys: Alice picks random secret , Bob picks random secret (never shared)
- Public Keys: Alice computes , Bob computes
- Exchange: Alice sends to Bob, Bob sends to Alice (safe even if intercepted!)
- Shared Secret: Both compute the same secret :
The security relies on the Discrete Logarithm Problem: Given g, p, and g^a, it's computationally infeasible to find a.
Example
Think of it like mixing paint: Alice and Bob each start with a common color (yellow), add their secret color (Alice adds red, Bob adds blue), exchange the mixed colors publicly, then add their secret color again. Both end up with the same final color (brown), but an observer can't recreate it without knowing the secret colors.
Class: DiffieHellman
The DiffieHellman class implements the Diffie-Hellman key agreement protocol. Instances are created using getDiffieHellman() for standard groups or createDiffieHellman() for custom parameters.
dh.generateKeys([encoding])
Generates private and public Diffie-Hellman key values. Must be called before computeSecret().
Parameters:
| Name | Type | Description |
|---|---|---|
encoding | string | Optional encoding for the return value: 'hex', 'base64', or 'base64url'. If omitted, returns Buffer |
Returns: Buffer or string - The public key
Examples:
import { getDiffieHellman } from 'react-native-quick-crypto';
const dh = getDiffieHellman('modp15');
// Generate keys and get public key as Buffer
const publicKey = dh.generateKeys();
console.log('Public key:', publicKey.toString('hex'));
// Generate keys and get public key as hex string
const publicKeyHex = dh.generateKeys('hex');
// Generate keys and get public key as base64
const publicKeyB64 = dh.generateKeys('base64');Important: Each call to generateKeys() creates a new key pair. Call it only once per DH instance.
dh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])
Computes the shared secret using otherPublicKey as the other party's public key.
Parameters:
| Name | Type | Description |
|---|---|---|
otherPublicKey | string | Buffer | TypedArray | DataView | The other party's public key |
inputEncoding | string | Encoding of otherPublicKey if it's a string: 'hex', 'base64', or 'base64url' |
outputEncoding | string | Encoding for the return value: 'hex', 'base64', or 'base64url'. If omitted, returns Buffer |
Returns: Buffer or string - The computed shared secret
Important: The shared secret should be hashed before use as an encryption key to remove structural weaknesses.
Examples:
import { getDiffieHellman } from 'react-native-quick-crypto';
// Alice's side
const alice = getDiffieHellman('modp15');
const alicePublicKey = alice.generateKeys();
// Bob's side
const bob = getDiffieHellman('modp15');
const bobPublicKey = bob.generateKeys();
// Compute shared secret (both get the same value!)
const aliceSecret = alice.computeSecret(bobPublicKey);
const bobSecret = bob.computeSecret(alicePublicKey);
console.log(aliceSecret.equals(bobSecret)); // true
// With hex encoding
const aliceSecretHex = alice.computeSecret(bobPublicKey.toString('hex'), 'hex', 'hex');dh.getPublicKey([encoding])
Returns the Diffie-Hellman public key.
Parameters:
| Name | Type | Description |
|---|---|---|
encoding | string | Optional encoding: 'hex', 'base64', or 'base64url' |
Returns: Buffer or string
Examples:
const dh = getDiffieHellman('modp14');
dh.generateKeys();
const publicKey = dh.getPublicKey(); // Buffer
const publicKeyHex = dh.getPublicKey('hex'); // stringdh.getPrivateKey([encoding])
Returns the Diffie-Hellman private key. Never transmit this value!
Parameters:
| Name | Type | Description |
|---|---|---|
encoding | string | Optional encoding: 'hex', 'base64', or 'base64url' |
Returns: Buffer or string
Security Warning: The private key must be kept secret. Anyone who obtains it can compute the shared secret.
dh.getPrime([encoding])
Returns the Diffie-Hellman prime.
Parameters:
| Name | Type | Description |
|---|---|---|
encoding | string | Optional encoding: 'hex', 'base64', or 'base64url' |
Returns: Buffer or string
dh.getGenerator([encoding])
Returns the Diffie-Hellman generator.
Parameters:
| Name | Type | Description |
|---|---|---|
encoding | string | Optional encoding: 'hex', 'base64', or base64url' |
Returns: Buffer or string
dh.setPublicKey(publicKey[, encoding])
Sets the Diffie-Hellman public key. Useful for restoring a DH instance from saved state.
Parameters:
| Name | Type | Description |
|---|---|---|
publicKey | string | Buffer | The public key to set |
encoding | string | Encoding of publicKey if it's a string |
dh.setPrivateKey(privateKey[, encoding])
Sets the Diffie-Hellman private key. Useful for restoring a DH instance from saved state.
Parameters:
| Name | Type | Description |
|---|---|---|
privateKey | string | Buffer | The private key to set |
encoding | string | Encoding of privateKey if it's a string |
Module Methods
getDiffieHellman(groupName)
Creates a DiffieHellman instance using a standardized, well-known group. Recommended for most applications.
Parameters:
| Name | Type | Description |
|---|---|---|
groupName | string | Name of the standard group (see Standard Groups) |
Returns: DiffieHellman instance
Examples:
import { getDiffieHellman } from 'react-native-quick-crypto';
// 2048-bit group (minimum for modern use)
const dh2048 = getDiffieHellman('modp14');
// 3072-bit group (recommended)
const dh3072 = getDiffieHellman('modp15');
// 4096-bit group (high security)
const dh4096 = getDiffieHellman('modp16');createDiffieHellman(prime[, primeEncoding][, generator][, generatorEncoding])
Creates a DiffieHellman instance with custom parameters. Advanced use only - use standard groups unless you have specific requirements.
Parameters:
| Name | Type | Description |
|---|---|---|
prime | number | string | Buffer | Prime number (if number, generates prime of that bit length) |
primeEncoding | string | Encoding of prime if it's a string: 'hex', 'base64' |
generator | number | string | Buffer | Generator (default: 2) |
generatorEncoding | string | Encoding of generator if it's a string |
Returns: DiffieHellman instance
Examples:
import { createDiffieHellman } from 'react-native-quick-crypto';
// Generate new 2048-bit prime (slow! - do once and save)
const dh = createDiffieHellman(2048);
const prime = dh.getPrime('hex');
const generator = dh.getGenerator('hex');
// Later, reuse the same parameters
const dh2 = createDiffieHellman(prime, 'hex', generator, 'hex');Warning: Generating custom primes is very slow and requires cryptographic expertise to avoid weak parameters. Use standard groups instead.
Standard Groups
Standardized Diffie-Hellman groups from RFC 3526 and RFC 5114. These are well-tested, secure, and widely compatible.
| Group | Bits | Security Level | Use Case |
|---|---|---|---|
modp14 | 2048 | ~112-bit | Minimum for modern applications |
modp15 | 3072 | ~128-bit | Recommended general purpose |
modp16 | 4096 | ~152-bit | High security applications |
modp17 | 6144 | ~176-bit | Very high security |
modp18 | 8192 | ~192-bit | Maximum security |
Recommendation: Use modp15 (3072-bit) or higher for new applications.
import { getDiffieHellman } from 'react-native-quick-crypto';
// ✅ Good - Modern security
const dh = getDiffieHellman('modp15');
// ✅ Better - High security
const dh = getDiffieHellman('modp16');
// ⚠️ Acceptable but minimum - Upgrade if possible
const dh = getDiffieHellman('modp14');Real-World Examples
Example 1: Secure Chat Application
End-to-end encrypted messaging using ephemeral Diffie-Hellman:
import {
getDiffieHellman,
createHash,
createCipheriv,
createDecipheriv,
randomBytes
} from 'react-native-quick-crypto';
class SecureChatSession {
private dh: any;
private sessionKey?: Buffer;
constructor(curveName: string = 'modp15') {
// Create new DH instance for this session (Perfect Forward Secrecy)
this.dh = getDiffieHellman('modp15');
this.dh.generateKeys();
}
getPublicKey(): string {
return this.dh.getPublicKey('base64');
}
establishSession(peerPublicKeyB64: string): void {
const peerPublicKey = Buffer.from(peerPublicKeyB64, 'base64');
const sharedSecret = this.dh.computeSecret(peerPublicKey);
// Derive session key using SHA-256
const hash = createHash('sha256');
hash.update(sharedSecret);
hash.update('chat-session-key'); // Application-specific salt
this.sessionKey = hash.digest();
console.log('Secure session established!');
}
encryptMessage(message: string): { iv: string; ciphertext: string } {
if (!this.sessionKey) throw new Error('Session not established');
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-cbc', this.sessionKey, iv);
let ciphertext = cipher.update(message, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
iv: iv.toString('base64'),
ciphertext
};
}
decryptMessage(encrypted: { iv: string; ciphertext: string }): string {
if (!this.sessionKey) throw new Error('Session not established');
const iv = Buffer.from(encrypted.iv, 'base64');
const decipher = createDecipheriv('aes-256-cbc', this.sessionKey, iv);
let message = decipher.update(encrypted.ciphertext, 'base64', 'utf8');
message += decipher.final('utf8');
return message;
}
}
// Usage
const alice = new SecureChatSession();
const bob = new SecureChatSession();
// Exchange public keys
const alicePubKey = alice.getPublicKey();
const bobPubKey = bob.getPublicKey();
// Establish sessions
alice.establishSession(bobPubKey);
bob.establishSession(alicePubKey);
// Send encrypted message
const encrypted = alice.encryptMessage('Hello Bob!');
const decrypted = bob.decryptMessage(encrypted);
console.log(decrypted); // "Hello Bob!"Example 2: Perfect Forward Secrecy
Implement Perfect Forward Secrecy by using ephemeral (one-time) DH keys:
import { getDiffieHellman, createHash } from 'react-native-quick-crypto';
class SecureConnection {
private longTermPublicKey: Buffer;
private longTermPrivateKey: Buffer;
constructor() {
// Long-term identity keys (saved, reused)
const identity = getDiffieHellman('modp15');
identity.generateKeys();
this.longTermPublicKey = identity.getPublicKey();
this.longTermPrivateKey = identity.getPrivateKey();
}
createEphemeralSession(peerLongTermPublicKey: Buffer): {
ephemeralPublicKey: Buffer;
sessionKey: Buffer;
} {
// Generate NEW ephemeral DH keys for this session only
const ephemeral = getDiffieHellman('modp15');
const ephemeralPublicKey = ephemeral.generateKeys();
// Compute shared secret using ephemeral keys
const ephemeralSecret = ephemeral.computeSecret(peerLongTermPublicKey);
// Derive session key
const hash = createHash('sha256');
hash.update(ephemeralSecret);
hash.update(this.longTermPublicKey); // Mix in identity
hash.update(peerLongTermPublicKey);
const sessionKey = hash.digest();
// Discard ephemeral private key after use
// Even if long-term keys are later compromised,
// this session cannot be decrypted!
return {
ephemeralPublicKey,
sessionKey
};
}
}
// Usage
const alice = new SecureConnection();
const bob = new SecureConnection();
// Alice creates session
const aliceSession = alice.createEphemeralSession(bob.longTermPublicKey);
// Each session uses different ephemeral keys
// Past sessions remain secure even if current keys are compromisedExample 3: Authenticated Diffie-Hellman
Prevent Man-in-the-Middle attacks by signing public keys:
import {
getDiffieHellman,
createSign,
createVerify,
createHash,
generateKeyPairSync
} from 'react-native-quick-crypto';
class AuthenticatedDH {
private dh: any;
private signingKeys: { publicKey: any; privateKey: any };
constructor() {
// DH for key exchange
this.dh = getDiffieHellman('modp15');
this.dh.generateKeys();
// RSA for authentication
this.signingKeys = generateKeyPairSync('rsa', {
modulusLength: 2048
});
}
getSignedPublicKey(): {
dhPublicKey: string;
signature: string;
signingPublicKey: any;
} {
const dhPublicKey = this.dh.getPublicKey('base64');
// Sign DH public key with RSA key
const sign = createSign('SHA256');
sign.update(dhPublicKey);
const signature = sign.sign(this.signingKeys.privateKey, 'base64');
return {
dhPublicKey,
signature,
signingPublicKey: this.signingKeys.publicKey
};
}
verifyAndEstablishSecret(peerData: {
dhPublicKey: string;
signature: string;
signingPublicKey: any;
}): Buffer {
// Verify peer's signature
const verify = createVerify('SHA256');
verify.update(peerData.dhPublicKey);
const isValid = verify.verify(
peerData.signingPublicKey,
peerData.signature,
'base64'
);
if (!isValid) {
throw new Error('Peer authentication failed! MITM attack detected.');
}
// Compute shared secret
const peerDHKey = Buffer.from(peerData.dhPublicKey, 'base64');
const sharedSecret = this.dh.computeSecret(peerDHKey);
// Hash the secret
const hash = createHash('sha256');
hash.update(sharedSecret);
return hash.digest();
}
}
// Usage
const alice = new AuthenticatedDH();
const bob = new AuthenticatedDH();
// Exchange signed public keys
const aliceData = alice.getSignedPublicKey();
const bobData = bob.getSignedPublicKey();
// Establish secrets (verified!)
try {
const aliceSecret = alice.verifyAndEstablishSecret(bobData);
const bobSecret = bob.verifyAndEstablishSecret(aliceData);
console.log(aliceSecret.equals(bobSecret)); // true
console.log('Authenticated secure channel established!');
} catch (error) {
console.error('Authentication failed:', error.message);
}Example 4: Multi-Party Key Agreement
Three-party Diffie-Hellman for group messaging:
import { getDiffieHellman, createHash } from 'react-native-quick-crypto';
// Simple 3-party DH using pairwise secrets
class ThreePartyDH {
private dh: any;
private name: string;
constructor(name: string) {
this.name = name;
this.dh = getDiffieHellman('modp14');
this.dh.generateKeys();
}
getPublicKey(): Buffer {
return this.dh.getPublicKey();
}
computeGroupKey(
peer1PublicKey: Buffer,
peer2PublicKey: Buffer
): Buffer {
// Compute pairwise secrets
const secret1 = this.dh.computeSecret(peer1PublicKey);
const secret2 = this.dh.computeSecret(peer2PublicKey);
// Combine secrets deterministically
const combined = Buffer.concat([
secret1.length < secret2.length ? secret1 : secret2,
secret1.length < secret2.length ? secret2 : secret1
]);
// Derive group key
const hash = createHash('sha256');
hash.update(combined);
hash.update('group-key-v1');
return hash.digest();
}
}
// Usage: Alice, Bob, and Charlie all compute the same group key
const alice = new ThreePartyDH('Alice');
const bob = new ThreePartyDH('Bob');
const charlie = new ThreePartyDH('Charlie');
const alicePub = alice.getPublicKey();
const bobPub = bob.getPublicKey();
const charliePub = charlie.getPublicKey();
const aliceGroupKey = alice.computeGroupKey(bobPub, charliePub);
const bobGroupKey = bob.computeGroupKey(alicePub, charliePub);
const charlieGroupKey = charlie.computeGroupKey(alicePub, bobPub);
// All three compute the same group key!
console.log(aliceGroupKey.equals(bobGroupKey)); // true
console.log(bobGroupKey.equals(charlieGroupKey)); // trueSecurity Considerations
Critical Security Rules
- Use standard groups -
modp15or higher for new applications - Hash the shared secret - Never use raw DH output as encryption key
- Authenticate peers - Vanilla DH is vulnerable to MITM attacks
- Use ephemeral keys - Generate new DH keys per session (Perfect Forward Secrecy)
- Minimum 2048 bits - Smaller groups are vulnerable to pre-computation attacks
Best Practices
1. Group Selection:
// ✅ Good - Modern security
const dh = getDiffieHellman('modp15');
// ✅ Better - High security
const dh = getDiffieHellman('modp16');
// ❌ Bad - Too small
const dh = createDiffieHellman(1024); // Vulnerable!2. Secret Derivation:
// ✅ Good - Hash the shared secret
const sharedSecret = dh.computeSecret(peerPublicKey);
const hash = createHash('sha256');
hash.update(sharedSecret);
const encryptionKey = hash.digest();
// ❌ Bad - Use raw secret
const sharedSecret = dh.computeSecret(peerPublicKey);
const iv = Buffer.alloc(16); // Example IV
const cipher = createCipheriv('aes-256-cbc', sharedSecret, iv); // Weak!3. Authentication:
// ✅ Good: Sign public keys with long-term identity
// const signature = signWithIdentityKey(d hPublicKey);
// send({ dhPublicKey, signature });
// ❌ Bad: No authentication (vulnerable to MITM)
// send({ dhPublicKey });4. Perfect Forward Secrecy:
// ✅ Good - New DH instance per session
function newSession() {
const dh = getDiffieHellman('modp15');
dh.generateKeys();
return dh;
}
// ❌ Bad - Reuse same DH instance
const dh = getDiffieHellman('modp15');
dh.generateKeys();
// ... reuse for multiple sessions4. Public Key Validation:
// ✅ Good - Validate received public keys
const { createECDH } = require('react-native-quick-crypto');
const ecdh = createECDH('prime256v1');
ecdh.generateKeys();
const receivedPublicKey = Buffer.from('...', 'hex'); // From peer
try {
const secret = ecdh.computeSecret(receivedPublicKey);
} catch (error) {
console.error('Invalid public key:', (error as Error).message);
// Reject the key exchange
}
// ❌ Bad - No validation
const untrustedPublicKey = Buffer.from('...', 'hex');
// const secret = ecdh.computeSecret(untrustedPublicKey); // Could throw!Man-in-the-Middle (MITM) Vulnerability
Basic Diffie-Hellman doesn't authenticate the parties. An attacker can perform two separate key exchanges:
Alice ← Attacker → Bob
↓ ↓ ↓
K1 K1 + K2 K2Protection: Combine with authentication (certificates, pre-shared keys, or signed DH public keys).
Common Errors
Different shared secrets
Cause: Both parties must use the same group and exchange keys correctly.
// ❌ Wrong - Different groups
const alice = getDiffieHellman('modp14');
const bob = getDiffieHellman('modp15'); // Different!
// ✅ Correct - Same group
const group = 'modp15';
const alice = getDiffieHellman(group);
const bob = getDiffieHellman(group);Cause: Using own public key instead of peer's.
// ❌ Wrong: Using own public key
// const secret = alice.computeSecret(alice.getPublicKey()); // Wrong!
// ✅ Correct: Using peer's public key
const alice = getDiffieHellman('modp15');
const bob = getDiffieHellman('modp15');
alice.generateKeys();
bob.generateKeys();
const secret = alice.computeSecret(bob.getPublicKey());Error: Supplied key is too small
Cause: Using a group with insufficient bit length.
Solution: Use modp14 (2048-bit) or larger:
// ❌ Wrong - Too small
const dh = createDiffieHellman(1024);
// ✅ Correct - Adequate size
const dh = getDiffieHellman('modp14'); // 2048-bitError: ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY
Cause: The provided public key is invalid or corrupted.
Solutions:
- Verify encoding matches (
'hex','base64', etc.) - Check for transmission errors
- Ensure both parties use the same group
// ✅ Correct - Matching encodings
const publicKey = dh.getPublicKey('base64');
const secret = peer.computeSecret(publicKey, 'base64');Performance Notes
Key Generation Performance (modp15/3072-bit, typical mobile device):
- Key generation: ~50-100ms
- Secret computation: ~10-20ms
Recommendations:
- Generate keys once per session, not per message
- Use smaller groups for low-power devices (but minimum modp14)
- Consider ECDH for better performance - 10-100× faster than DH
- Pre-generate DH instances if you know you'll need them
// Example: Background key generation
import { getDiffieHellman } from 'react-native-quick-crypto';
async function prepareSecureSession(): Promise<any> {
return new Promise((resolve) => {
setTimeout(() => {
const dh = getDiffieHellman('modp15');
dh.generateKeys();
resolve(dh);
}, 0);
});
}
// Usage
const dh = await prepareSecureSession();
// DH instance ready for immediate usePerformance Comparison
| Group | Key Gen | Compute | Total | Security |
|---|---|---|---|---|
| modp14 (2048) | ~30ms | ~8ms | ~38ms | Minimum |
| modp15 (3072) | ~80ms | ~15ms | ~95ms | Recommended |
| modp16 (4096) | ~200ms | ~30ms | ~230ms | High |
| ECDH P-256 | ~5ms | ~2ms | ~7ms | Equivalent to modp15 |
Conclusion: For mobile applications with frequent rekeying, consider using ECDH instead of traditional DH for better performance.