Skip to main content

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:

  1. You and your friend agree on a "base" and "modulus" (like agreeing on a greeting and a special gesture)
  2. You each choose a private number (your secret move)
  3. You combine your secret with the base and modulus, showing the result to everyone
  4. You take your friend's displayed result, combine it with your secretβ€”and BAM! You both have the same secret
  5. 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:

  1. Generate a shared secret without sending it
  2. Establish that secret even if Eve is listening
  3. 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:

  1. New messages use the new shared secret
  2. Old messages are encrypted with the old secret
  3. If Eve learns your current private key, she can only decrypt new messages
  4. 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.

  1. You both pick a secret number (keep it hidden!)
  2. You use that number with a "magic math trick" to make a public number
  3. You trade public numbers (everyone sees them!)
  4. You use your secret number with their public number β†’ both get the same secret!
  5. 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