import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.MessageDigest;import javax.crypto.KEM;import javax.crypto.SecretKey; public class MlKem { public static void main(String[] args) throws Exception { // Receiver: a key pair (JDK 24+); only the public key is handed out KeyPair keys = KeyPairGenerator.getInstance("ML-KEM-768").generateKeyPair(); KEM kem = KEM.getInstance("ML-KEM"); // Sender: a fresh shared secret and the ciphertext ("encapsulation") that carries it KEM.Encapsulated sent = kem.newEncapsulator(keys.getPublic()).encapsulate(); SecretKey senderKey = sent.key(); byte[] ciphertext = sent.encapsulation(); // Receiver: the same secret from the ciphertext SecretKey receiverKey = kem.newDecapsulator(keys.getPrivate()).decapsulate(ciphertext); System.out.println("public key " + keys.getPublic().getEncoded().length + " bytes (" + keys.getPublic().getFormat() + "), private key " + keys.getPrivate().getEncoded().length + " bytes (" + keys.getPrivate().getFormat() + "), ciphertext " + ciphertext.length + " bytes"); System.out.println("shared secret agreed: " + MessageDigest.isEqual(senderKey.getEncoded(), receiverKey.getEncoded())); }} How it works
KeyPairGenerator.getInstance("ML-KEM-768")creates the pair; the public key encodes as a 1206-byte SPKI (X.509), the private key as an 86-byte seed-only PKCS#8.newEncapsulator(publicKey).encapsulate()returns aKEM.Encapsulated:key()is the 32-byte shared secret,encapsulation()the 1088-byte ciphertext.newDecapsulator(privateKey).decapsulate(ciphertext)returns the same secret as aSecretKeywith algorithmGeneric;decapsulate(ciphertext, 0, 32, "AES")returns it as an AES key directly.KeyFactory.getInstance("ML-KEM")reads OpenSSL's SPKI and seed-only PKCS#8; the run decapsulates OpenSSL's ciphertext to the shared secretdee35810…30f9.
Gotchas
- A tampered ciphertext throws nothing and yields a different key (implicit rejection); the error only shows when that key fails to decrypt or authenticate data.
- A ciphertext of the wrong length throws
DecapsulateException: Invalid key encapsulation message length. - Compare the encoded secrets with
MessageDigest.isEqual, notArrays.equals, if you compare them at all — in a real protocol each side simply uses its key.
Related recipes
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 in Kotlin
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 in C# (.NET)
- How to encapsulate and decapsulate a shared secret with ML-KEM-768 with OpenSSL
- How to sign and verify with ML-DSA-65 in Java
- How to derive a shared key with ECDH and HKDF in Java