Key Encapsulation Mechanism API (JEP 452): Post-Quantum Cryptography in Java
Why Post-Quantum Cryptography Now?
Classical public-key cryptography (RSA, ECDH) relies on mathematical problems that are hard for classical computers — factoring large integers or solving the discrete logarithm problem. A sufficiently powerful quantum computer running Shor’s algorithm could solve these problems efficiently, breaking all existing RSA and ECC-based security.
Quantum computers capable of breaking 2048-bit RSA don’t exist yet. But “harvest now, decrypt later” attacks are real: adversaries intercept and store encrypted traffic today, planning to decrypt it once quantum computers mature. Data encrypted today with classical algorithms may be exposed in 10–20 years.
Java 21’s KEM API is the first step toward post-quantum security in the Java platform — a standard interface for Key Encapsulation Mechanisms, which are the building blocks of post-quantum key exchange.
What Is a Key Encapsulation Mechanism?
A KEM establishes a shared symmetric key between two parties using public-key cryptography, without transmitting the key itself:
sequenceDiagram
participant Sender
participant Receiver
Note over Receiver: Has key pair (publicKey, privateKey)
Receiver->>Sender: publicKey (can be shared openly)
Note over Sender: Runs KEM encapsulate(publicKey)
Note over Sender: Produces: sharedSecret + encapsulatedKey
Sender->>Receiver: encapsulatedKey (sent over network)
Note over Receiver: Runs KEM decapsulate(privateKey, encapsulatedKey)
Note over Receiver: Recovers: sharedSecret
Note over Sender,Receiver: Both have sharedSecret\nNeither transmitted it directly\nCan now use sharedSecret for AES encryption
The sharedSecret is used as a symmetric key (e.g., for AES-256-GCM) — this is called hybrid encryption: asymmetric KEM to establish the key, symmetric cipher for the data.
The KEM API — Core Classes
// javax.crypto.KEM — factory and entry point
// javax.crypto.KEM.Encapsulated — result of encapsulation
// javax.crypto.KEM.Encapsulator — performs encapsulation
// javax.crypto.KEM.Decapsulator — performs decapsulation
Full Hybrid Encryption Example
Receiver Side — Generate Key Pair
import javax.crypto.*;
import java.security.*;
// Generate a key pair (receiver does this once; publicKey is distributed)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X25519");
KeyPair keyPair = kpg.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
Sender Side — Encapsulate
// Sender has the receiver's publicKey
KEM kem = KEM.getInstance("DHKEM(X25519, HKDF-SHA256)");
KEM.Encapsulator encapsulator = kem.newEncapsulator(publicKey);
// Encapsulate: generates sharedSecret + encapsulatedKey in one step
KEM.Encapsulated encapsulated = encapsulator.encapsulate();
SecretKey sharedSecret = encapsulated.key(); // use as AES key
byte[] encapsulatedKey = encapsulated.encapsulation(); // send to receiver
byte[] params = encapsulated.params(); // optional KDF params
Sender — Encrypt Data with the Shared Secret
// Use the shared secret to encrypt data with AES-256-GCM
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] iv = new byte[12]; // 96-bit IV for GCM
new SecureRandom().nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, sharedSecret, new GCMParameterSpec(128, iv));
byte[] plaintext = "Order confirmed: #42".getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = cipher.doFinal(plaintext);
// Send to receiver: encapsulatedKey + iv + ciphertext
Receiver Side — Decapsulate and Decrypt
// Receiver has their privateKey and receives encapsulatedKey + iv + ciphertext
KEM kem = KEM.getInstance("DHKEM(X25519, HKDF-SHA256)");
KEM.Decapsulator decapsulator = kem.newDecapsulator(privateKey);
// Recover the same sharedSecret
SecretKey sharedSecret = decapsulator.decapsulate(
encapsulatedKey, 0, encapsulatedKey.length,
"AES" // desired algorithm for SecretKey
);
// Decrypt
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, sharedSecret, new GCMParameterSpec(128, iv));
byte[] plaintext = cipher.doFinal(ciphertext);
System.out.println(new String(plaintext)); // "Order confirmed: #42"
KEM Algorithms in Java 21
Java 21 ships with DHKEM algorithms from RFC 9180 (HPKE — Hybrid Public Key Encryption):
| Algorithm | Key type | KDF | Notes |
|---|---|---|---|
DHKEM(X25519, HKDF-SHA256) | X25519 | HKDF-SHA256 | Recommended — fast, modern |
DHKEM(X448, HKDF-SHA512) | X448 | HKDF-SHA512 | Larger keys, higher security margin |
DHKEM(P-256, HKDF-SHA256) | EC P-256 | HKDF-SHA256 | NIST curve, FIPS-compatible |
DHKEM(P-384, HKDF-SHA384) | EC P-384 | HKDF-SHA384 | Higher security NIST |
DHKEM(P-521, HKDF-SHA512) | EC P-521 | HKDF-SHA512 | Highest security NIST |
All of these are classical algorithms — they provide a clean, standardized KEM API. Post-quantum algorithms (ML-KEM, formerly CRYSTALS-Kyber) will be added as JDK providers evolve.
The JCA Provider Model
KEM follows the same provider architecture as other Java cryptography:
// Use the default provider (SunEC)
KEM kem = KEM.getInstance("DHKEM(X25519, HKDF-SHA256)");
// Specify a provider explicitly
KEM kem = KEM.getInstance("DHKEM(X25519, HKDF-SHA256)", "SunEC");
// Use a third-party post-quantum provider (e.g., Bouncy Castle)
Security.addProvider(new BouncyCastleProvider());
KEM kem = KEM.getInstance("KYBER512", "BC"); // ML-KEM via Bouncy Castle
This means once post-quantum algorithms like ML-KEM are standardized (NIST finalized in 2024), JDK providers will add them without changing your application code — just change the algorithm string.
Hybrid Encryption: Combining Classical and Post-Quantum
The recommended production approach is hybrid KEM — run both a classical KEM and a post-quantum KEM, XOR their shared secrets together:
// Hybrid: X25519 (classical) + ML-KEM-768 (post-quantum, via BC provider)
// If either is broken, the other still protects the secret
byte[] classicalSecret = classicalKemEncapsulate(publicKeyX25519);
byte[] pqSecret = postQuantumKemEncapsulate(publicKeyKyber);
// Combine secrets securely (XOR is fine when both are uniformly random)
byte[] combinedSecret = new byte[classicalSecret.length];
for (int i = 0; i < classicalSecret.length; i++) {
combinedSecret[i] = (byte) (classicalSecret[i] ^ pqSecret[i % pqSecret.length]);
}
// Use combinedSecret as AES key — protected against both classical and quantum attacks
KEM vs Traditional Key Exchange
| Mechanism | Java API | Quantum-safe? | Notes |
|---|---|---|---|
| RSA key transport | Cipher.getInstance("RSA") | No | Broken by Shor’s algorithm |
| ECDH | KeyAgreement.getInstance("ECDH") | No | Broken by Shor’s algorithm |
| DH | KeyAgreement.getInstance("DH") | No | Broken by Shor’s algorithm |
| DHKEM (Java 21) | KEM.getInstance(...) | No (classical) | Standardized, provider-extensible |
| ML-KEM (Kyber) | KEM.getInstance("KYBER", provider) | Yes | Via Bouncy Castle or future JDK |
Encapsulated Secret Key Specification
Control the exact type and size of the derived key:
// Request a specific key type and size
KEM.Encapsulated encapsulated = encapsulator.encapsulate(
0, // offset into key material
32, // length of key in bytes (256 bits)
"AES" // algorithm for SecretKey
);
SecretKey aes256Key = encapsulated.key();
// aes256Key.getAlgorithm() == "AES"
// aes256Key.getEncoded().length == 32
Security Considerations
Always use authenticated encryption — combine the shared secret with AES-GCM, not AES-CBC. AES-GCM detects tampering; AES-CBC does not.
Bind context to the KEM — include context information (sender ID, purpose, timestamp) in the KDF to prevent key material from being reused across different contexts:
// Include application context in key derivation
KEM.Encapsulator encapsulator = kem.newEncapsulator(
publicKey,
null, // params
"order-service:v1:encrypt".getBytes() // application context
);
Rotate key pairs — KEM key pairs should be rotated regularly. A compromised private key allows decapsulating all past encapsulated secrets.
Key Takeaways
- KEM establishes a shared symmetric key using public-key cryptography — neither party transmits the key
- Java 21’s
KEMAPI follows the JCA provider model —KEM.getInstance(algorithm), thennewEncapsulator(publicKey)/newDecapsulator(privateKey) - Java 21 ships DHKEM algorithms (RFC 9180/HPKE) based on X25519 and NIST curves — classical algorithms with a future-proof API
- Post-quantum algorithms (ML-KEM/Kyber) will be added via JDK providers — your code using
KEM.getInstance(...)won’t need to change - For maximum security today, use hybrid KEM: classical X25519 + post-quantum ML-KEM via Bouncy Castle
- Always pair KEM with AES-GCM (authenticated encryption) for the actual data encryption
Next: Unnamed Patterns and Variables (JEP 443) — use _ to express intent clearly when pattern components or variables are intentionally unused.