Overview
ML-DSA is a lattice-based digital signature scheme whose security is based on the hardness of finding short vectors in lattices.
Support for ML-DSA is now supported on IBM Semeru Java versions 17 and 21 on the IBMJCECCA security provider. IBMJCECCA currently offers the following implementations:
-
Pure ML-DSA (4,4)
-
Pure ML-DSA (6,5)
-
Pure ML-DSA (8,7)
-
Pre-Hash ML-DSA (4,4) with SHA-512
-
Pre-Hash ML-DSA (6,5) with SHA-512
-
Pre-Hash ML-DSA (8,7) with SHA-512
-
CRYSTALS-Dilithium (6,5) Round 2 (superseded by ML-DSA)
-
CRYSTALS-Dilithium (8,7) Round 2 (superseded by ML-DSA)
-
CRYSTALS-Dilithium (6,5) Round 3 (superseded by ML-DSA)
-
CRYSTALS-Dilithium (8,7) Round 3 (superseded by ML-DSA)
The strength of an ML-DSA key is represented by the size of its matrix of polynomials. For example, ML-DSA (6,5) has a matrix size of 6x5. The bigger the matrix size, the stronger the key. ML-DSA keys can only be used for Digital Signature Generation and Verification.
This tutorial will demonstrate how to integrate ML-DSA into a Java application. Before you continue reading, please ensure your environment is properly configured by reading PQC on Java: Configuring your IBM Z system to use Quantum-safe algorithms.
Step 1: Create ML-DSA key pair
The first step is to create a ML-DSA key pair using the KeyPairGenerator and MLDSAKeyParameterSpec classes.
The MLDSAKeyParameterSpec class accepts the following values based on the desired implementation outlined in the Overview section:
-
dilithium65r2
-
dilithium87r2
-
dilithium65r3
-
dilithium87r3
-
puremldsa44
-
puremldsa65
-
puremldsa87
-
prehashmldsa44
-
prehashmldsa65
-
prehashmldsa87
KeyPairGenerator kpg =
KeyPairGenerator.getInstance("ML-DSA", "IBMJCECCA");
MLDSAKeyParameterSpec kps =
new MLDSAKeyParameterSpec("puremldsa65");
kpg.initialize(kps, null);
KeyPair keyPair = kpg.generateKeyPair();
Step 2: Get public and private keys
Now that we have the key pair, we can separate the keys into their own variables.
MLDSAPrivateKey privKey = (MLDSAPrivateKey) keyPair.getPrivate();
MLDSAPublicKey pubKey = (MLDSAPublicKey) keyPair.getPublic();
Step 3: Create Signature object
If you're already experienced with the JCE framework, the remainder of the steps may look familiar with other Signature algorithms. Use the Signature class and specify ML-DSA and IBMJCECCA.
Signature sign = Signature.getInstance("ML-DSA", "IBMJCECCA");
Step 4: Initialize Signature for signing
Use the ML-DSA private key to initialize the Signature object.
sign.initSign(privKey);
Step 5: Add bytes to Signature
Pass the data to be signend to the update() method of the Signature object.
byte[] bytes = "IBM - Let's create".getBytes();
sign.update(bytes);
Step 6: Perform signature
Finally, use the sign() method to get the signature bytes of the updated data.
byte[] sigBytes = sign.sign();
Step 7: Initialize the Signature for verification
Now, we can initialize the Signature object to verify the signature bytes in the next step.
sign.initVerify(pubKey);
sign.update(bytes);
Step 8: Verify
Verify the signature.
boolean verified = sign.verify(sigBytes);
if (verified) {
System.out.println("Signature verified");
} else {
System.out.println("Signature could NOT be verified");
}
Example
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import com.ibm.crypto.hdwrCCA.provider.MLDSAKeyParameterSpec;
import com.ibm.crypto.hdwrCCA.provider.MLDSAPrivateKey;
import com.ibm.crypto.hdwrCCA.provider.MLDSAPublicKey;
public class MLDSAExample {
public static void main(String[] args) throws Exception {
// STEP 1: Creat key pair
KeyPairGenerator kpg =
KeyPairGenerator.getInstance("ML-DSA", "IBMJCECCA");
MLDSAKeyParameterSpec kps =
new MLDSAKeyParameterSpec("puremldsa65");
kpg.initialize(kps, null);
KeyPair keyPair = kpg.generateKeyPair();
// STEP 2: Get public and private keys
MLDSAPrivateKey privKey = (MLDSAPrivateKey) keyPair.getPrivate();
MLDSAPublicKey pubKey = (MLDSAPublicKey) keyPair.getPublic();
// STEP 3: Create Signature object
Signature sign = Signature.getInstance("ML-DSA", "IBMJCECCA");
// STEP 4: Initialize Signature for signing
sign.initSign(privKey);
// STEP 5: Add bytes to Signature
byte[] bytes = "IBM - Let's create".getBytes();
sign.update(bytes);
// STEP 6: Perform signature
byte[] sigBytes = sign.sign();
// STEP 7: Initialize the Signature for verification
sign.initVerify(pubKey);
sign.update(bytes);
// STEP 8: Verify
boolean verified = sign.verify(sigBytes);
if (verified) {
System.out.println("Signature verified");
} else {
System.out.println("Signature could NOT be verified");
}
}
}
Output
The previous Example should produce the following output:
Signature verified
Conclusion
In this tutorial, we learned how to use the ML-DSA PQC algorithm with IBMJCECCA to create keys and use a Signature to sign & verify bytes. Thanks for reading!
If you have additional questions, please email me at Emily.Popovic1@ibm.com
References
-
PQC on Java: Configuring your IBM Z system to use Post-Quantum Cryptography
-
ML-DSA, CRYSTALS-Dilithium Digital Signature Algorithm
-
z17 IBM Semeru Runtimes: PQC Cryptography Enhancements