[dependencies]ml-kem = { version = "=0.3.3", features = ["getrandom", "pem"] }pkcs8 = { version = "=0.11.0", features = ["pem"] } use ml_kem::MlKem768;use ml_kem::kem::{Decapsulate, Encapsulate, Kem}; pub fn agree() { // Receiver: a key pair from OS randomness ("getrandom" feature) let (decapsulation_key, encapsulation_key) = MlKem768::generate_keypair(); // Sender: a fresh shared secret and the ciphertext that carries it let (ciphertext, sender_secret) = encapsulation_key.encapsulate(); // Receiver: the same secret from the ciphertext let receiver_secret = decapsulation_key.decapsulate(&ciphertext); println!("ciphertext {} bytes, shared secret {} bytes", ciphertext.len(), sender_secret.len()); println!("shared secret agreed: {}", sender_secret == receiver_secret);} How it works
MlKem768::generate_keypair()(from thekem::Kemtrait) returns(decapsulation_key, encapsulation_key).encapsulation_key.encapsulate()returns(ciphertext, shared_secret)— 1088 and 32 bytes, in that order, which is the reverse of Python and Go.decapsulation_key.decapsulate(&ciphertext)returns the same secret; with a byte slice from the network usedecapsulate_slice, which checks the length.EncapsulationKey768::from_public_key_pemandDecapsulationKey768::from_pkcs8_pemread OpenSSL's files; the run decapsulates OpenSSL's ciphertext todee35810…30f9, and OpenSSL decapsulates the ciphertext Rust made.
Gotchas
- Unlike ml-dsa, ml-kem has no randomness by default:
generate_keypairandencapsulateneed thegetrandomfeature, as in theCargo.tomlabove. - A tampered ciphertext still decapsulates (
Ok) to a different secret — implicit rejection. Only the length is checked: a 1000-byte slice givesErr(TryFromSliceError(())). - Secrets compare with
==on the returned arrays; in a real protocol you do not compare them at all — each side derives keys from its own copy.
Related recipes
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 in Go
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 in Python
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 with OpenSSL
- How to sign and verify with ML-DSA-65 in Rust
- How to derive a shared key with ECDH and HKDF in Rust