Public-key cryptography

Agree on shared keys

Use an established authenticated key-agreement protocol to derive session keys without sending the final shared key across the network.

Key agreement lets two parties derive shared secret material over a public channel, without ever sending the secret itself. Diffie-Hellman is the classic construction; X25519 is the modern elliptic-curve version you will actually meet.

Each side generates a key pair, they exchange public keys, and each combines its own private key with the other’s public key. Both arrive at the same secret. An observer who saw both public keys cannot compute it.

import { generateKeyPairSync, diffieHellman } from 'node:crypto'

const alice = generateKeyPairSync('x25519')
const bob = generateKeyPairSync('x25519')

const aliceSecret = diffieHellman({ privateKey: alice.privateKey, publicKey: bob.publicKey })
const bobSecret = diffieHellman({ privateKey: bob.privateKey, publicKey: alice.publicKey })

aliceSecret.equals(bobSecret) // true

The part the math does not cover

Key agreement does not automatically prove who the other party is. You derived a secret with someone. The math never says with whom.

A client and server perform raw Diffie-Hellman over an untrusted network. An active attacker intercepts the exchange and establishes one shared key with the client and another with the server, reading and changing traffic between them. Both victims see a working encrypted channel. This is the textbook man-in-the-middle attack, and unauthenticated key agreement walks straight into it.

Real protocols combine ephemeral key agreement with signatures, certificates, or pre-established identity. In TLS 1.3, the server signs the handshake transcript with the private key its certificate vouches for, so the client knows the agreement happened with the certificate’s owner. “Ephemeral” means fresh agreement keys per connection, which gives forward secrecy: a later key compromise does not decrypt recorded traffic.

Derive, never use raw

The raw shared secret is not your encryption key. Feed the result through the protocol’s key derivation and use separate keys by purpose:

import { hkdfSync } from 'node:crypto'

const clientKey = Buffer.from(hkdfSync('sha256', aliceSecret, salt, 'client write key', 32))
const serverKey = Buffer.from(hkdfSync('sha256', aliceSecret, salt, 'server write key', 32))

HKDF stretches one secret into independent keys, each bound to a purpose label.

Do not assemble a handshake from raw primitives. Key agreement needs authenticated identities and a defined key schedule, and getting either subtly wrong is invisible until someone exploits it. Use a protocol such as current TLS instead of composing raw agreement, signatures, and derivation yourself.

Practice

Trace a TLS connection and save where the ephemeral agreement, certificate identity, transcript authentication, and derived traffic keys appear conceptually. Verify the connection with the expected hostname. Then connect through a test endpoint with the wrong identity and capture the authentication failure.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →