import { decapsulate, encapsulate, generateKeyPairSync, timingSafeEqual } from "node:crypto"; // Receiver: a key pair; only the encapsulation (public) key is handed outconst { publicKey, privateKey } = generateKeyPairSync("ml-kem-768"); // Sender: a fresh 32-byte shared secret and the ciphertext that carries itconst { sharedKey, ciphertext } = encapsulate(publicKey); // Receiver: the same secret from the ciphertextconst received = decapsulate(privateKey, ciphertext); console.log("encapsulation key", publicKey.export({ type: "spki", format: "der" }).length, "bytes SPKI,", "ciphertext", ciphertext.length, "bytes, shared secret", sharedKey.length, "bytes");console.log("shared secret agreed:", timingSafeEqual(received, sharedKey)); How it works
generateKeyPairSync("ml-kem-768")returns twoKeyObjects; the public key exports as a 1206-byte SPKI.encapsulate(publicKey)returns{ sharedKey, ciphertext }— a 32-byte secret and a 1088-byte ciphertext.decapsulate(privateKey, ciphertext)returns the same 32 bytes; compare secrets withtimingSafeEqual, never===on strings.createPublicKeyandcreatePrivateKeyread OpenSSL's files in every form (seed-only or seed plus expanded key); the run decapsulates OpenSSL's ciphertext to the shared secretdee35810…30f9, and OpenSSL decapsulates the ciphertext Node.js made.
Gotchas
- A tampered ciphertext does not throw:
decapsulatereturns a different secret (implicit rejection). Only the next step — decrypting or authenticating with a key derived from the secret — reveals the mismatch. - A ciphertext of the wrong length throws
ERR_CRYPTO_OPERATION_FAILED. export({ type: "pkcs8", format: "der" })writes the 86-byte seed-only form, which every library on this page reads — even when the key was loaded from OpenSSL's larger default file.crypto.subtlehas the same operation (encapsulateBits/decapsulateBits), but Node.js 24 marks it experimental and prints anExperimentalWarning;node:cryptodoes not.
Related recipes
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 in WebCrypto
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 with OpenSSL
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 in Python
- How to sign and verify with ML-DSA-65 in Node.js
- How to derive a shared key with ECDH and HKDF in Node.js