Created
November 11, 2025 04:04
-
-
Save nichoth/ea858f33a110e6e0c64b9962f16b999d to your computer and use it in GitHub Desktop.
Perform Diffie-Hellman Key Exchange with the web crypto API (in a browser)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Generate X25519 key pairs | |
| async function generateX25519KeyPair(): Promise<CryptoKeyPair> { | |
| return await crypto.subtle.generateKey( | |
| { | |
| name: "X25519" | |
| }, | |
| true, // extractable | |
| ["deriveKey", "deriveBits"] | |
| ); | |
| } | |
| // Derive a shared secret using X25519 | |
| async function deriveSharedSecret( | |
| privateKey: CryptoKey, | |
| publicKey: CryptoKey | |
| ): Promise<CryptoKey> { | |
| return await crypto.subtle.deriveKey( | |
| { | |
| name: "X25519", | |
| public: publicKey | |
| }, | |
| privateKey, | |
| { | |
| name: "AES-GCM", | |
| length: 256 | |
| }, | |
| true, // extractable | |
| ["encrypt", "decrypt"] | |
| ); | |
| } | |
| // Complete X25519 example | |
| async function x25519Example(): Promise<{ | |
| aliceKeyPair: CryptoKeyPair; | |
| bobKeyPair: CryptoKeyPair; | |
| aliceSharedSecret: CryptoKey; | |
| bobSharedSecret: CryptoKey; | |
| }> { | |
| // Alice generates her X25519 key pair | |
| const aliceKeyPair = await generateX25519KeyPair(); | |
| // Bob generates his X25519 key pair | |
| const bobKeyPair = await generateX25519KeyPair(); | |
| // Alice derives shared secret using her private key and Bob's public key | |
| const aliceSharedSecret = await deriveSharedSecret( | |
| aliceKeyPair.privateKey, | |
| bobKeyPair.publicKey | |
| ); | |
| // Bob derives shared secret using his private key and Alice's public key | |
| const bobSharedSecret = await deriveSharedSecret( | |
| bobKeyPair.privateKey, | |
| aliceKeyPair.publicKey | |
| ); | |
| // Export keys to verify they match | |
| const aliceSecret = await crypto.subtle.exportKey("raw", aliceSharedSecret); | |
| const bobSecret = await crypto.subtle.exportKey("raw", bobSharedSecret); | |
| console.log("Alice's shared secret:", new Uint8Array(aliceSecret)); | |
| console.log("Bob's shared secret:", new Uint8Array(bobSecret)); | |
| console.log("Secrets match:", | |
| new Uint8Array(aliceSecret).toString() === new Uint8Array(bobSecret).toString() | |
| ); | |
| return { aliceKeyPair, bobKeyPair, aliceSharedSecret, bobSharedSecret }; | |
| } | |
| // Run the example | |
| x25519Example(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
double ratchet
The Signal protocol
The Two Ratchets
1. The DHKE
On each new message, the message contains a new public key. You use your private key with the given public key to derive a new secret.
2. Symmetric-key Ratchet (KDF Chain)
After each message, both sides advance their sending or receiving chain using a key derivation function (KDF).
Each message gets its own unique symmetric encryption key (message_key_n), which is deleted after use.
Each message is encrypted with a specific symmetric key. The message key comes from a hierarchy of derived keys. The root key and chain keys are intermediate secrets used to securely derive that per-message key.
three key levels in the Double Ratchet