Java 24 Quantum-Resistant Crypto: ML-KEM and ML-DSA
Introduction
The security of today's internet - HTTPS, SSH, signed software updates - rests mostly on two kinds of public-key cryptography: RSA and elliptic curve cryptography. Both rely on mathematical problems, factoring large numbers and computing discrete logarithms, which are infeasible for today's computers. A large enough quantum computer, however, could solve them using Shor's algorithm. No such computer exists yet, but encrypted data can be recorded now and decrypted later - a threat called "harvest now, decrypt later" - so the move to new algorithms has already begun. In August 2024, the US National Institute of Standards and Technology (NIST) published the first post-quantum standards, and Java 24Click to read tutorial: Overview of New Features in Java 24 implemented two of them: ML-KEM (JEP 496) for establishing shared keys, and ML-DSA (JEP 497) for digital signatures. This tutorial explains them, and shows how to use both through the standard Java APIs with runnable examples.
The two algorithms
Sharing a key with ML-KEM
OUTPUT of the above code
The flow is exactly the same as with the classic
OUTPUT of the above code
Again the standard API is unchanged. The trade-off shows in the sizes: an ML-DSA-65 signature is about 3.3 KB, against 64 bytes for Ed25519, and its public key is about 2 KB. That is acceptable for most uses, but matters for protocols which send many signatures, such as TLS certificate chains.
Using post-quantum cryptography in practice
- ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism, standardized as FIPS 203, formerly known as Kyber) lets two parties agree on a shared secret key over an insecure channel - the job Diffie-Hellman key exchange does today. It is a key encapsulation mechanism, used through the KEM API added in Java 21Click to read: KEM API added in Java 21.
- ML-DSA (Module-Lattice-Based Digital Signature Algorithm, FIPS 204, formerly Dilithium) signs and verifies data - the job of RSA, ECDSA and EdDSA signatures - through the standard
SignatureAPI.
Establishing a shared secret key with ML-KEM
//MlKemDemo.java
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.NamedParameterSpec;
import java.util.Arrays;
import javax.crypto.KEM;
import javax.crypto.SecretKey;
public class MlKemDemo {
public static void main(String[] args) throws Exception {
// The receiver creates an ML-KEM-768 key pair and publishes the public key
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM");
kpg.initialize(NamedParameterSpec.ML_KEM_768);
KeyPair receiver = kpg.generateKeyPair();
// The sender encapsulates a fresh secret key using the receiver's public key
KEM kem = KEM.getInstance("ML-KEM");
KEM.Encapsulated encapsulated = kem.newEncapsulator(receiver.getPublic()).encapsulate();
SecretKey senderKey = encapsulated.key();
byte[] ciphertext = encapsulated.encapsulation(); // sent to the receiver
// The receiver recovers the same key with its private key
SecretKey receiverKey = kem.newDecapsulator(receiver.getPrivate()).decapsulate(ciphertext);
System.out.println("Public key size : " + receiver.getPublic().getEncoded().length + " bytes (encoded)");
System.out.println("Encapsulation : " + ciphertext.length + " bytes");
System.out.println("Shared key : " + senderKey.getEncoded().length * 8 + " bits, algorithm " + senderKey.getAlgorithm());
System.out.println("Keys are equal : " + Arrays.equals(senderKey.getEncoded(), receiverKey.getEncoded()));
}
}
Public key size : 1206 bytes (encoded) Encapsulation : 1088 bytes Shared key : 256 bits, algorithm Generic Keys are equal : true
DHKEM - only the algorithm name changes, which is what the KEM API was designed for. The shared 256-bit key is then used with a symmetric cipher such as AES-GCM, which is already considered safe against quantum computers at that key size. Note the sizes: ML-KEM keys and ciphertexts are larger than elliptic curve ones, which have only 32 bytes.
Signing and verifying with ML-DSA
Signing and verifying data with ML-DSA
//MlDsaDemo.java
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
public class MlDsaDemo {
static byte[] sign(String algorithm, KeyPair keys, byte[] data) throws Exception {
Signature signer = Signature.getInstance(algorithm);
signer.initSign(keys.getPrivate());
signer.update(data);
return signer.sign();
}
static boolean verify(String algorithm, KeyPair keys, byte[] data, byte[] signature) throws Exception {
Signature verifier = Signature.getInstance(algorithm);
verifier.initVerify(keys.getPublic());
verifier.update(data);
return verifier.verify(signature);
}
public static void main(String[] args) throws Exception {
byte[] release = "release-1.4.2.jar sha256=ab12...".getBytes(StandardCharsets.UTF_8);
KeyPair mlDsa = KeyPairGenerator.getInstance("ML-DSA").generateKeyPair(); // default: ML-DSA-65
byte[] signature = sign("ML-DSA", mlDsa, release);
System.out.println("ML-DSA signature : " + signature.length + " bytes, valid = " + verify("ML-DSA", mlDsa, release, signature));
byte[] tampered = "release-1.4.3.jar sha256=ab12...".getBytes(StandardCharsets.UTF_8);
System.out.println("Tampered data : valid = " + verify("ML-DSA", mlDsa, tampered, signature));
KeyPair ed = KeyPairGenerator.getInstance("Ed25519").generateKeyPair(); // a classic signature, for comparison
System.out.println("Ed25519 signature: " + sign("Ed25519", ed, release).length + " bytes");
}
}
ML-DSA signature : 3309 bytes, valid = true Tampered data : valid = false Ed25519 signature: 64 bytes
- Hybrid schemes: during the transition, protocols usually combine a classic and a post-quantum algorithm, so that the result is secure as long as either one is unbroken. TLS 1.3 implementations, including browsers, already use a hybrid of X25519 and ML-KEM-768 for key exchange.
- Crypto-agility: because Java's security APIs select algorithms by name, code written against
KEM,SignatureandKeyPairGeneratorcan switch algorithms through configuration - which is worth designing for now. - Keys and certificates:
keytoolcan generate ML-DSA and ML-KEM key pairs, so post-quantum keys can be stored in key stores like any other. - Key derivation: to turn a shared secret into several keys, use the Key Derivation Function APIClick to read tutorial: Java 25 Key Derivation Function (KDF) API Tutorial, final in Java 25, which completes the toolkit for hybrid schemes such as HPKE.
KEM API, signed and verified data with ML-DSA through the Signature API, and compared their sizes with classic algorithms. Lastly, we looked at hybrid schemes and crypto-agility for using post-quantum cryptography in practice. Next, read the overview of new features in Java 24Click to read tutorial: Overview of New Features in Java 24.Continue Reading Java 22 to 25 Tutorials
Overview of New Features in Java 24Click to read tutorial: Overview of New Features in Java 24 Java 24 Stream Gatherers Tutorial with ExamplesClick to read tutorial: Java 24 Stream Gatherers Tutorial with Examples Java 24 Class-File API Tutorial with ExamplesClick to read tutorial: Java 24 Class-File API Tutorial with Examples AOT Cache in Java 24 and 25: Faster StartupClick to read tutorial: AOT Cache in Java 24 and 25: Faster Startup Java 24: Virtual Threads in synchronized Without PinningClick to read tutorial: Java 24: Virtual Threads in synchronized Without Pinning Overview of New Features in Java 25Click to read tutorial: Overview of New Features in Java 25 Overview of New Features in Java 22Click to read tutorial: Overview of New Features in Java 22 Overview of New Features in Java 23Click to read tutorial: Overview of New Features in Java 23 Java 22 Foreign Function & Memory API TutorialClick to read tutorial: Java 22 Foreign Function & Memory API Tutorial
Overview of New Features in Java 24Click to read tutorial: Overview of New Features in Java 24 Java 24 Stream Gatherers Tutorial with ExamplesClick to read tutorial: Java 24 Stream Gatherers Tutorial with Examples Java 24 Class-File API Tutorial with ExamplesClick to read tutorial: Java 24 Class-File API Tutorial with Examples AOT Cache in Java 24 and 25: Faster StartupClick to read tutorial: AOT Cache in Java 24 and 25: Faster Startup Java 24: Virtual Threads in synchronized Without PinningClick to read tutorial: Java 24: Virtual Threads in synchronized Without Pinning Overview of New Features in Java 25Click to read tutorial: Overview of New Features in Java 25 Overview of New Features in Java 22Click to read tutorial: Overview of New Features in Java 22 Overview of New Features in Java 23Click to read tutorial: Overview of New Features in Java 23 Java 22 Foreign Function & Memory API TutorialClick to read tutorial: Java 22 Foreign Function & Memory API Tutorial