Diffie-Hellman Layer: The Secret Handshake
Mental Model: The Secret Handshakeβ
Imagine you want to share a secret message with your friend in a crowded room. You can't just whisperβyou need to set up a secret channel first.
Diffie-Hellman is like doing a secret handshake in front of everyone, but nobody can figure out your secret!
Here's how it works:
- You and your friend agree on a "base" and "modulus" (like agreeing on a greeting and a special gesture)
- You each choose a private number (your secret move)
- You combine your secret with the base and modulus, showing the result to everyone
- You take your friend's displayed result, combine it with your secretβand BAM! You both have the same secret
- Eve watching from across the room? She can't figure out your private numbers from just the displayed results
It's public but private. Everyone sees the handshake; only you know the secret.
Why Use DH in Cascading Cipher?β
π― The Problemβ
In the foundations, we learned that AES requires a password. But for messaging, we can't exchange passwords every conversation! We need a way to:
- Generate a shared secret without sending it
- Establish that secret even if Eve is listening
- Refresh the secret periodically for forward secrecy
π‘οΈ The Diffie-Hellman Solutionβ
The DH layer uses ECDH-P256 (Elliptic Curve Diffie-Hellman). It:
- Generates key pairs for each user
- exchanges public keys to create a shared secret
- Uses that secret to derive encryption keys
- Can rotate to create new secrets (refresh the handshake)
Where It Fits in the Stackβ
βββββββββββββββββββββββββββββββββββ
β Signal/MLS Layer (Context) β β Ratchets, group state
βββββββββββββββββββββββββββββββββββ€
β π€ DH Layer (Key Exchange) β β Creates shared secret from nothing
βββββββββββββββββββββββββββββββββββ€
β AES Layer (Foundation) β β Encrypts with derived key
βββββββββββββββββββββββββββββββββββ
DH creates the foundationβthe shared encryption key that all other layers build upon.
How ECDH-P256 Worksβ
Step 1: Key Generationβ
Each user generates a key pair:
// Alice generates her keys
const aliceKeyPair = crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey", "deriveBits"]
)
const alicePublicKey = aliceKeyPair.publicKey
const alicePrivateKey = aliceKeyPair.privateKey // Alice keeps this secret!
// Bob generates his keys
const bobKeyPair = crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey", "deriveBits"]
)
const bobPublicKey = bobKeyPair.publicKey
const bobPrivateKey = bobKeyPair.privateKey // Bob keeps this secret!
Note: Private keys are NEVER shared. Only public keys are exchanged.
Step 2: Public Key Exchangeβ
Alice sends Bob her public key. Bob sends Alice his public key. Eve sees both, but that's fine!
Step 3: Shared Secret Derivationβ
Each side derives the same shared secret:
// Alice derives the shared secret using Bob's public key
const aliceSharedSecret = crypto.subtle.deriveBits(
{
name: "ECDH",
public: bobPublicKey
},
alicePrivateKey,
256
)
// Bob derives the same shared secret using Alice's public key
const bobSharedSecret = crypto.subtle.deriveBits(
{
name: "ECDH",
public: alicePublicKey
},
bobPrivateKey,
256
)
// aliceSharedSecret === bobSharedSecret!
// Both have the same secret key
Step 4: Encryption Key Derivationβ
The shared secret is used to derive the actual encryption key:
// Derive a 256-bit AES key from the shared secret
const encryptionKey = crypto.subtle.importKey(
"raw",
aliceSharedSecret,
"AES-GCM",
false,
["encrypt", "decrypt"]
)
Now both Alice and Bob have the same encryption key!
Visualizing the Handshakeβ
Why Can't Eve Figure It Out?β
The Math Magicβ
The security comes from the discrete logarithm problem:
Given:
- Public key: K = GβΏ mod p
- G (base point), n (private exponent), p (prime)
Can you find n?
For ECDH-P256:
- n is a 256-bit number (enormous!)
- The curve has ~2Β²β΅βΆ points
- No known algorithm can solve this faster than trial division
- Fastest known attack: ~2ΒΉβΈβ° operations (still impossible)
Back-of-the-Envelope Calculationβ
If Eve had a supercomputer that:
- Could try 10ΒΉΒ² combinations per second (1 trillion/second)
- And had 10βΉ such computers (1 billion)
Time to crack ECDH-P256:
2ΒΉΒ²βΈ / (10ΒΉΒ² Γ 10βΉ) = 2ΒΉΒ²βΈ / 10Β²ΒΉ
β 3 Γ 10ΒΉβ· seconds
β 10 billion years
The universe is ~13.8 billion years old. Eve would need the age of the universe to crack it.
Using the DH Layerβ
Basic Usageβ
import { DHCipherLayer } from '@cascading-cipher/dh-layer'
// Create a DH layer
const dhLayer = new DHCipherLayer()
Generating Your Keys (Once)β
// Generate your key pair (do this once!)
const myKeyPair = await crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey", "deriveBits"]
)
// Export your public key to share with the other person
const myPublicKey = await crypto.subtle.exportKey("spki", myKeyPair.publicKey)
// Share myPublicKey with Bob (base64 encoded)
Setting Up for a Contactβ
// Import Bob's public key he sent you
const bobPublicKey = await crypto.subtle.importKey(
"spki",
base64ToArrayBuffer(bobPublicKeyArray),
{ name: "ECDH", namedCurve: "P-256" },
true,
[]
)
// Configure the DH layer with your keys
await dhLayer.configure({
myPrivateKey: myKeyPair.privateKey,
theirPublicKey: bobPublicKey
})
Encrypting with DHβ
// Encrypt a message (DH derives the key automatically)
const message = "Hello from the secret channel!"
const encrypted = await dhLayer.encrypt(
new TextEncoder().encode(message)
)
console.log(new TextDecoder().decode(encrypted))
// "Encrypted with DH-derived key"
Decrypting with DHβ
// Decrypt using the same derived key
const decrypted = await dhLayer.decrypt(encrypted)
console.log(new TextDecoder().decode(decrypted))
// "Hello from the secret channel!"
Key Rotation (Refreshing the Handshake)β
// For forward secrecy, rotate keys periodically
const newKeyPair = await crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey", "deriveBits"]
)
// Update your private key and get their new public key
await dhLayer.configure({
myPrivateKey: newKeyPair.privateKey,
theirPublicKey: theirNewPublicKey
})
// Now you have a new shared secret!
Integration with CascadingCipherManagerβ
import { CascadingCipherManager } from '@cascading-cipher/manager'
import { AESCipherLayer } from '@cascading-cipher/aes-layer'
import { DHCipherLayer } from '@cascading-cipher/dh-layer'
// Add DH + AES layers
const manager = new CascadingCipherManager()
// DH creates the shared secret key
await manager.addLayer(new DHCipherLayer({
myPrivateKey: myPrivateKey,
theirPublicKey: theirPublicKey
}))
// AES encrypts with the DH-derived key
await manager.addLayer(new AESCipherLayer())
// Encrypt: DH derives key β AES encrypts with it
const encrypted = await manager.encrypt(
"Secure message over DH channel!"
)
// Decrypt: AES decrypts β DH key already matched
const decrypted = await manager.decrypt(encrypted)
// "Secure message over DH channel!"
Complete Example: Secure Messaging with DHβ
async function setupSecureMessaging() {
// 1. Generate key pairs for Alice and Bob
const aliceKeyPair = await crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey", "deriveBits"]
)
const bobKeyPair = await crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey", "deriveBits"]
)
// 2. Export public keys to share
const alicePublicKey = await crypto.subtle.exportKey("spki", aliceKeyPair.publicKey)
const bobPublicKey = await crypto.subtle.exportKey("spki", bobKeyPair.publicKey)
// 3. Set up Alice's manager
const aliceManager = new CascadingCipherManager()
await aliceManager.addLayer(new DHCipherLayer({
myPrivateKey: aliceKeyPair.privateKey,
theirPublicKey: bobKeyPair.publicKey
}))
await aliceManager.addLayer(new AESCipherLayer())
// 4. Set up Bob's manager (mirrored)
const bobManager = new CascadingCipherManager()
await bobManager.addLayer(new DHCipherLayer({
myPrivateKey: bobKeyPair.privateKey,
theirPublicKey: aliceKeyPair.publicKey
}))
await bobManager.addLayer(new AESCipherLayer())
// 5. Alice encrypts a message
const plaintext = "This is our secret channel!"
const encrypted = await aliceManager.encrypt(plaintext)
console.log('Encrypted:', encrypted)
// 6. Bob decrypts it
const decrypted = await bobManager.decrypt(encrypted)
console.log('Decrypted:', decrypted)
// "This is our secret channel!"
return { alicePublicKey, bobPublicKey }
}
Security Propertiesβ
β Forward Secrecyβ
When you rotate your key pair:
- New messages use the new shared secret
- Old messages are encrypted with the old secret
- If Eve learns your current private key, she can only decrypt new messages
- Old messages stay safe!
β Perfect Forward Secrecy (PFS)β
If you generate new key pairs for each message (which Signal/MLS layers do):
- Compromise of a single private key only reveals that one message
- Past and future messages remain safe
- This is what Signal and MLS achieve with ratcheting
β Deniabilityβ
Diffie-Hellman is symmetric:
- Alice proves nothing more than she can derive the secret
- Bob proves nothing more than he can derive the secret
- Neither can prove they had a conversation with the other
Quiz Time!β
π§ Why can Eve see the public keys but not derive the shared secret?
Because deriving the secret requires solving the discrete logarithm problem. Eve sees K = GβΏ mod p but needs to find n (the private exponent). There's no known efficient algorithm to do this for 256-bit elliptic curvesβbrute forcing it would take billions of years.
π§ What happens if Eve records Alice and Bob's public key exchange and later steals Alice's private key?
Eve can derive the current shared secret and decrypt any messages encrypted with that secret. However, if Alice and Bob have since rotated their keys, past and future secrets remain safe. This is forward secrecy!
π§ Why do we need DH if we already have AES?
AES encrypts with a key, but doesn't create one! DH solves the key distribution problemβhow two people can agree on a secret key without sending it over the network. DH creates the key that AES then uses to encrypt.
π§ What's the difference between shared secret and encryption key?
- Shared secret: Raw bits (from DH), random and unpredictable
- Encryption key: Derived from the shared secret, properly formatted for AES (256-bit, right length)
We derive the encryption key from the shared secret to ensure it's the right format and has good cryptographic properties for encryption.
Can You Explain to a 5-Year-Old?β
Imagine you and your friend want a secret language nobody else understands.
- You both pick a secret number (keep it hidden!)
- You use that number with a "magic math trick" to make a public number
- You trade public numbers (everyone sees them!)
- You use your secret number with their public number β both get the same secret!
- Use that secret to encode your messages!
It's like:
- Everyone sees you doing a magic trick
- Only you know how it actually works
- You can now talk secretly even though everyone watched you!
Key Takeawaysβ
β Diffie-Hellman: Creates shared secrets without sending keys
β Public but private: Everyone sees the handshake; only you know the secret
β ECDH-P256: Elliptic curve version, fast and secure
β Forward secrecy: Rotate keys β compromise reveals less
β Foundation layer: Provides the encryption key for all other layers
β Used by: Signal, MLS, WhatsApp, Telegram