Key exchange
Before Alice can send Bob an encrypted message, both sides need a shared secret — without transmitting that secret across the network. X25519 ECDH is the mathematical trick that makes this possible.
The problem
Symmetric encryption (like XSalsa20-Poly1305) requires both Alice and Bob to know the same key to encrypt and decrypt. But how do they agree on that key over a public network, where an eavesdropper can read every message they exchange?
Can't send the key
If Alice sends the secret key to Bob, anyone intercepting the message learns the key and can decrypt every future message.
Can't meet in person
Two strangers on the internet can't safely exchange a secret over a telephone or in person before they start chatting.
ECDH solves it
Elliptic Curve Diffie-Hellman lets two parties each contribute public values. Only someone with a private key can derive the shared secret.
How X25519 ECDH works
X25519 is a Diffie-Hellman function over the elliptic curve Curve25519. Each party generates a key pair — a random 32-byte private key and a corresponding 32-byte public key. The public key can be shared freely; the private key never leaves the device.
The core property of the DH function is commutative:
ℹ Why is it secure?
alice_pub and bob_pub (which the server has), computing S requires solving the Elliptic Curve Discrete Logarithm Problem — believed to require ~2128 operations on current hardware. There is no known classical algorithm that can do this in practical time.Visual walkthrough
relay
What the server sees
Encra's key server acts as a public directory — nothing more. It stores user IDs mapped to their public keys, so Alice can look up Bob's key before sending her first message. The server relays encrypted blobs and is mathematically incapable of reading them.
✓ What the server stores
✗ What the server never has
Multi-device
Each device generates its own key pair and registers its own public key under the same userId. When Bob sends Alice a message, Encra fetches all of Alice's registered public keys and encrypts one independent copy per device. Every device can decrypt its own copy; no device can decrypt another device's copy.
Code
The key exchange primitives are exposed directly from @encra/core for advanced use. In practice, useE2EChat and EncraClient handle key generation, registration, and exchange automatically.
import {
generateKeyPair,
exportKey,
importKey,
deriveSharedSecret,
} from '@encra/core'
// --- Alice's device: generate once, store in IndexedDB ---
const aliceKeys = await generateKeyPair()
// aliceKeys.publicKey → Uint8Array (32 bytes) — share this
// aliceKeys.privateKey → Uint8Array (32 bytes) — never share this
// Serialise for storage / transmission (URL-safe base64)
const alicePubB64 = await exportKey(aliceKeys.publicKey)
// --- Restore from storage ---
const alicePubRestored = await importKey(alicePubB64)
// --- Both sides: derive the same shared secret ---
// Alice
const sharedAlice = await deriveSharedSecret(
aliceKeys.privateKey,
bobPublicKey, // fetched from GET /v1/keys/bob
)
// Bob (independently — same result)
const sharedBob = await deriveSharedSecret(
bobKeys.privateKey,
alicePublicKey,
)
// sharedAlice === sharedBob — they never exchanged this value💡 Key fingerprints for verification
generateFingerprint(alicePub, bobPub) to produce a Signal-style safety number (BLAKE2b-256). Show it to both parties out-of-band (voice call, QR code) to confirm no MITM is in the path. See Cryptographic primitives.Security properties
| Property | Value | Notes |
|---|---|---|
| Key size | 256 bits (X25519) | 128-bit security level — equivalent to 3072-bit RSA |
| Algorithm | X25519 / Curve25519 | Constant-time; immune to timing side-channel attacks |
| Server knowledge | Public keys only | Server cannot derive shared secret from public keys alone |
| MITM resistance | Key fingerprints | generateFingerprint() for out-of-band verification |
| Post-quantum | Soon (ML-KEM-768) | PQXDH hybrid planned — classical + quantum-resistant |
| Key storage | IndexedDB (device-only) | Private keys never leave the device; not synced to server |
⚠ MITM window