diff --git a/src/main/java/com/ibm/crypto/plus/provider/DefaultProviderAttrs.java b/src/main/java/com/ibm/crypto/plus/provider/DefaultProviderAttrs.java
index 08fa8ad95..4e4021e61 100644
--- a/src/main/java/com/ibm/crypto/plus/provider/DefaultProviderAttrs.java
+++ b/src/main/java/com/ibm/crypto/plus/provider/DefaultProviderAttrs.java
@@ -147,9 +147,10 @@ class DefaultProviderAttrs {
+ " # PQC key factories\n"
+ " # =======================================================================\n"
+ " #\n"
+ + "Service.KeyFactory.ML-KEM = com.ibm.crypto.plus.provider.PQCKeyFactory$MLKEM\n"
+ "KeyFactory.ML-KEM-512.alias.add = ML_KEM_512, MLKEM512, OID.2.16.840.1.101.3.4.4.1, 2.16.840.1.101.3.4.4.1\n"
+ "Service.KeyFactory.ML-KEM-512 = com.ibm.crypto.plus.provider.PQCKeyFactory$MLKEM512\n"
- + "KeyFactory.ML-KEM-768.alias.add = ML-KEM, ML_KEM_768, MLKEM768, OID.2.16.840.1.101.3.4.4.2, 2.16.840.1.101.3.4.4.2\n"
+ + "KeyFactory.ML-KEM-768.alias.add = ML_KEM_768, MLKEM768, OID.2.16.840.1.101.3.4.4.2, 2.16.840.1.101.3.4.4.2\n"
+ "Service.KeyFactory.ML-KEM-768 = com.ibm.crypto.plus.provider.PQCKeyFactory$MLKEM768\n"
+ "KeyFactory.ML-KEM-1024.alias.add = ML_KEM_1024, MLKEM1024, OID.2.16.840.1.101.3.4.4.3, 2.16.840.1.101.3.4.4.3\n"
+ "Service.KeyFactory.ML-KEM-1024 = com.ibm.crypto.plus.provider.PQCKeyFactory$MLKEM1024\n"
@@ -315,9 +316,10 @@ class DefaultProviderAttrs {
+ " # PQC key encapsulation mechanisms\n"
+ " # =======================================================================\n"
+ " #\n"
+ + "Service.KEM.ML-KEM = com.ibm.crypto.plus.provider.MLKEMImpl$MLKEM\n"
+ "KEM.ML-KEM-512.alias.add = ML_KEM_512, MLKEM512, OID.2.16.840.1.101.3.4.4.1, 2.16.840.1.101.3.4.4.1\n"
+ "Service.KEM.ML-KEM-512 = com.ibm.crypto.plus.provider.MLKEMImpl$MLKEM512\n"
- + "KEM.ML-KEM-768.alias.add = ML-KEM, ML_KEM_768, MLKEM768, OID.2.16.840.1.101.3.4.4.2, 2.16.840.1.101.3.4.4.2\n"
+ + "KEM.ML-KEM-768.alias.add = ML_KEM_768, MLKEM768, OID.2.16.840.1.101.3.4.4.2, 2.16.840.1.101.3.4.4.2\n"
+ "Service.KEM.ML-KEM-768 = com.ibm.crypto.plus.provider.MLKEMImpl$MLKEM768\n"
+ "KEM.ML-KEM-1024.alias.add = ML_KEM_1024, MLKEM1024, OID.2.16.840.1.101.3.4.4.3, 2.16.840.1.101.3.4.4.3\n"
diff --git a/src/main/java/com/ibm/crypto/plus/provider/MLKEMImpl.java b/src/main/java/com/ibm/crypto/plus/provider/MLKEMImpl.java
index ea231af41..d71dd4ae3 100644
--- a/src/main/java/com/ibm/crypto/plus/provider/MLKEMImpl.java
+++ b/src/main/java/com/ibm/crypto/plus/provider/MLKEMImpl.java
@@ -38,18 +38,22 @@ public MLKEMImpl(OpenJCEPlusProvider provider, String alg) {
this.alg = alg;
}
- private int getEncapsulationLength() {
+ private int getEncapsulationLength(String algorithm) {
int size = 0;
- switch (this.alg) {
+ switch (algorithm) {
case "ML-KEM-512":
size = 768;
break;
case "ML-KEM-768":
size = 1088;
break;
- default:
+ case "ML-KEM-1024":
size = 1568;
+ break;
+ default:
+ // If algorithm is generic "ML-KEM", default to ML-KEM-768
+ size = 1088;
}
return size;
}
@@ -72,8 +76,15 @@ public KEMSpi.EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey,
if (!(pubKey instanceof PQCPublicKey)) {
// Try and convert this key to a usage PQCPublicKey
+ // First verify it's an ML-KEM key
+ String keyAlgorithm = publicKey.getAlgorithm();
+ if (keyAlgorithm == null || !keyAlgorithm.startsWith("ML-KEM")) {
+ throw new InvalidKeyException("unsupported key");
+ }
+
+ // Use the key's actual algorithm, not the generic "ML-KEM"
try {
- KeyFactory kf = KeyFactory.getInstance(this.alg, this.provider.getName());
+ KeyFactory kf = KeyFactory.getInstance(keyAlgorithm, this.provider.getName());
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
pubKey = kf.generatePublic(publicKeySpec);
@@ -105,7 +116,9 @@ class MLKEMEncapsulator implements KEMSpi.EncapsulatorSpi {
@Override
public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) {
- int encapLen = getEncapsulationLength();
+ // Get the actual algorithm from the public key
+ String keyAlgorithm = publicKey.getAlgorithm();
+ int encapLen = getEncapsulationLength(keyAlgorithm);
byte[] encapsulation = new byte[encapLen];
byte[] secret = new byte[SECRETSIZE];
@@ -130,7 +143,8 @@ public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) {
@Override
public int engineEncapsulationSize() {
- return getEncapsulationLength();
+ String keyAlgorithm = publicKey.getAlgorithm();
+ return getEncapsulationLength(keyAlgorithm);
}
@Override
@@ -155,9 +169,16 @@ public KEMSpi.DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey,
if (!(privKey instanceof PQCPrivateKey)) {
// Try and convert this key to a usage PQCPrivateKey
+ // First verify it's an ML-KEM key
+ String keyAlgorithm = privateKey.getAlgorithm();
+ if (keyAlgorithm == null || !keyAlgorithm.startsWith("ML-KEM")) {
+ throw new InvalidKeyException("unsupported key");
+ }
+
+ // Use the key's actual algorithm, not the generic "ML-KEM"
byte[] encoding = null;
try {
- KeyFactory kf = KeyFactory.getInstance(this.alg, this.provider.getName());
+ KeyFactory kf = KeyFactory.getInstance(keyAlgorithm, this.provider.getName());
encoding = privateKey.getEncoded();
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encoding);
privKey = kf.generatePrivate(privateKeySpec);
@@ -197,6 +218,17 @@ public SecretKey engineDecapsulate(byte[] cipherText, int from, int to, String a
if (algorithm == null || cipherText == null) {
throw new NullPointerException();
}
+
+ // Validate encapsulation length matches the key's algorithm
+ String keyAlgorithm = privateKey.getAlgorithm();
+ int expectedEncapLen = getEncapsulationLength(keyAlgorithm);
+ if (cipherText.length != expectedEncapLen) {
+ throw new DecapsulateException(
+ "Invalid key encapsulation message length: expected " +
+ expectedEncapLen + " bytes for " + keyAlgorithm +
+ ", but got " + cipherText.length + " bytes");
+ }
+
try {
secret = OJPKEM.KEM_decapsulate(((PQCPrivateKey) this.privateKey).getPQCKey().getPKeyId(),
cipherText, provider);
@@ -210,8 +242,8 @@ public SecretKey engineDecapsulate(byte[] cipherText, int from, int to, String a
@Override
public int engineEncapsulationSize() {
-
- return getEncapsulationLength();
+ String keyAlgorithm = privateKey.getAlgorithm();
+ return getEncapsulationLength(keyAlgorithm);
}
@Override
@@ -222,6 +254,13 @@ public int engineSecretSize() {
}
+ public static final class MLKEM extends MLKEMImpl {
+
+ public MLKEM(OpenJCEPlusProvider provider) {
+ super(provider, "ML-KEM");
+ }
+ }
+
public static final class MLKEM512 extends MLKEMImpl {
public MLKEM512(OpenJCEPlusProvider provider) {
diff --git a/src/main/java/com/ibm/crypto/plus/provider/PQCKeyFactory.java b/src/main/java/com/ibm/crypto/plus/provider/PQCKeyFactory.java
index be54055ca..12e9bc983 100644
--- a/src/main/java/com/ibm/crypto/plus/provider/PQCKeyFactory.java
+++ b/src/main/java/com/ibm/crypto/plus/provider/PQCKeyFactory.java
@@ -188,8 +188,20 @@ private void checkKeyAlgo(Key key) throws InvalidKeyException {
String keyAlg = key.getAlgorithm();
if (keyAlg == null) {
throw new InvalidKeyException("Algorithm associate with key is null.");
- } else if (!(key.getAlgorithm().equalsIgnoreCase(this.algName) ||
- (PQCKnownOIDs.findMatch(key.getAlgorithm()).stdName().equalsIgnoreCase(this.algName)))) {
+ }
+
+ // Check if algorithms match exactly or via OID lookup
+ boolean matches = key.getAlgorithm().equalsIgnoreCase(this.algName) ||
+ (PQCKnownOIDs.findMatch(key.getAlgorithm()).stdName().equalsIgnoreCase(this.algName));
+
+ // Special case for generic ML-KEM: Allow any ML-KEM parameter set variant
+ // (ML-KEM-512, ML-KEM-768, ML-KEM-1024) when using the generic "ML-KEM" KeyFactory.
+ // This enables interoperability with KEM.getInstance("ML-KEM", ...).
+ if (!matches && "ML-KEM".equals(this.algName) && keyAlg.startsWith("ML-KEM")) {
+ matches = true;
+ }
+
+ if (!matches) {
throw new InvalidKeyException("Expected a " + this.algName + " key, but got " + keyAlg);
}
@@ -217,6 +229,13 @@ private boolean checkEncoded(byte[] key, boolean pub) {
}
}
+ public static final class MLKEM extends PQCKeyFactory {
+
+ public MLKEM(OpenJCEPlusProvider provider) {
+ super(provider, "ML-KEM");
+ }
+ }
+
public static final class MLKEM512 extends PQCKeyFactory {
public MLKEM512(OpenJCEPlusProvider provider) {
diff --git a/src/test/ProviderDefAttrs.config b/src/test/ProviderDefAttrs.config
index 53e614f15..e66335a7f 100644
--- a/src/test/ProviderDefAttrs.config
+++ b/src/test/ProviderDefAttrs.config
@@ -228,10 +228,12 @@ Service.KeyFactory.RSAPSS = com.ibm.crypto.plus.provider.RSAKeyFactory$PSS
# PQC key factories
# =======================================================================
#
+Service.KeyFactory.ML-KEM = com.ibm.crypto.plus.provider.PQCKeyFactory$MLKEM
+
KeyFactory.ML-KEM-512.alias.add = ML_KEM_512, MLKEM512, OID.2.16.840.1.101.3.4.4.1, 2.16.840.1.101.3.4.4.1
Service.KeyFactory.ML-KEM-512 = com.ibm.crypto.plus.provider.PQCKeyFactory$MLKEM512
-KeyFactory.ML-KEM-768.alias.add = ML-KEM, ML_KEM_768, MLKEM768, OID.2.16.840.1.101.3.4.4.2, 2.16.840.1.101.3.4.4.2
+KeyFactory.ML-KEM-768.alias.add = ML_KEM_768, MLKEM768, OID.2.16.840.1.101.3.4.4.2, 2.16.840.1.101.3.4.4.2
Service.KeyFactory.ML-KEM-768 = com.ibm.crypto.plus.provider.PQCKeyFactory$MLKEM768
KeyFactory.ML-KEM-1024.alias.add = ML_KEM_1024, MLKEM1024, OID.2.16.840.1.101.3.4.4.3, 2.16.840.1.101.3.4.4.3
@@ -464,10 +466,12 @@ Service.MessageDigest.SHA3-512 = com.ibm.crypto.plus.provider.MessageDigest$SHA3
# PQC key encapsulation mechanisms
# =======================================================================
#
+Service.KEM.ML-KEM = com.ibm.crypto.plus.provider.MLKEMImpl$MLKEM
+
KEM.ML-KEM-512.alias.add = ML_KEM_512, MLKEM512, OID.2.16.840.1.101.3.4.4.1, 2.16.840.1.101.3.4.4.1
Service.KEM.ML-KEM-512 = com.ibm.crypto.plus.provider.MLKEMImpl$MLKEM512
-KEM.ML-KEM-768.alias.add = ML-KEM, ML_KEM_768, MLKEM768, OID.2.16.840.1.101.3.4.4.2, 2.16.840.1.101.3.4.4.2
+KEM.ML-KEM-768.alias.add = ML_KEM_768, MLKEM768, OID.2.16.840.1.101.3.4.4.2, 2.16.840.1.101.3.4.4.2
Service.KEM.ML-KEM-768 = com.ibm.crypto.plus.provider.MLKEMImpl$MLKEM768
KEM.ML-KEM-1024.alias.add = ML_KEM_1024, MLKEM1024, OID.2.16.840.1.101.3.4.4.3, 2.16.840.1.101.3.4.4.3
diff --git a/src/test/java/ibm/jceplus/junit/base/BaseTestKEM.java b/src/test/java/ibm/jceplus/junit/base/BaseTestKEM.java
index 70587460a..73ea09617 100644
--- a/src/test/java/ibm/jceplus/junit/base/BaseTestKEM.java
+++ b/src/test/java/ibm/jceplus/junit/base/BaseTestKEM.java
@@ -210,6 +210,57 @@ public void testKEMKeys(String Algorithm) throws Exception {
}
}
+ /**
+ * Tests that decapsulation fails with a DecapsulateException when attempting to decapsulate
+ * an encapsulation message that was created with a different ML-KEM algorithm variant.
+ *
+ *
This test verifies that the KEM implementation properly validates the encapsulation
+ * message length during decapsulation. Each ML-KEM variant (ML-KEM-512, ML-KEM-768, ML-KEM-1024)
+ * produces encapsulation messages of different lengths. When a decapsulator receives an
+ * encapsulation message with an incorrect length (from a different variant), it should
+ * reject it with a DecapsulateException containing an appropriate error message.
+ *
+ *
Test procedure:
+ *
+ * - Generate a key pair using the first algorithm (keyAlgorithm)
+ * - Generate a different key pair using a second algorithm (wrongAlgorithm)
+ * - Create an encapsulation using the second key pair (wrong length for first algorithm)
+ * - Attempt to decapsulate using the first key pair's private key
+ * - Verify that a DecapsulateException is thrown with the expected error message
+ *
+ *
+ * @param keyAlgorithm the ML-KEM algorithm variant to use for the decapsulation key pair
+ * @param wrongAlgorithm the ML-KEM algorithm variant to use for creating the encapsulation
+ * (produces wrong length for keyAlgorithm)
+ * @throws Exception if an unexpected error occurs during test execution
+ */
+ @ParameterizedTest
+ @CsvSource({"ML-KEM-512,ML-KEM-768", "ML-KEM-768,ML-KEM-1024", "ML-KEM-1024,ML-KEM-512"})
+ public void testKEMInvalidEncapsulationLength(String keyAlgorithm, String wrongAlgorithm) throws Exception {
+ // Generate a key pair with one algorithm
+ KeyPair keyPair = generateKeyPair(keyAlgorithm);
+
+ // Create encapsulation with a different algorithm (wrong length)
+ KEM kemWrong = KEM.getInstance(wrongAlgorithm, getProviderName());
+ KeyPair wrongKeyPair = generateKeyPair(wrongAlgorithm);
+ KEM.Encapsulator encapsulator = kemWrong.newEncapsulator(wrongKeyPair.getPublic());
+ KEM.Encapsulated encapsulated = encapsulator.encapsulate(0, 32, "AES");
+
+ // Try to decapsulate with the original key (wrong length)
+ KEM kem = KEM.getInstance(keyAlgorithm, getProviderName());
+ KEM.Decapsulator decapsulator = kem.newDecapsulator(keyPair.getPrivate());
+
+ try {
+ decapsulator.decapsulate(encapsulated.encapsulation(), 0, 32, "AES");
+ fail("testKEMInvalidEncapsulationLength failed - Invalid encapsulation length did not cause a DecapsulateException for " + keyAlgorithm + " with " + wrongAlgorithm + " encapsulation");
+ } catch (javax.crypto.DecapsulateException de) {
+ assertTrue(de.getMessage().contains("Invalid key encapsulation message length"),
+ "Expected error message about invalid encapsulation length, but got: " + de.getMessage());
+ assertTrue(de.getMessage().contains(keyAlgorithm),
+ "Expected error message to mention key algorithm " + keyAlgorithm + ", but got: " + de.getMessage());
+ }
+ }
+
protected KeyPair generateKeyPair(String Algorithm) throws Exception {
pqcKeyPairGen = KeyPairGenerator.getInstance(Algorithm, getProviderName());
diff --git a/src/test/java/ibm/jceplus/junit/base/BaseTestPQCKeyInterop.java b/src/test/java/ibm/jceplus/junit/base/BaseTestPQCKeyInterop.java
index 837d09c0c..9d8240f5a 100644
--- a/src/test/java/ibm/jceplus/junit/base/BaseTestPQCKeyInterop.java
+++ b/src/test/java/ibm/jceplus/junit/base/BaseTestPQCKeyInterop.java
@@ -16,6 +16,7 @@
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.EncodedKeySpec;
+import java.security.spec.NamedParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
@@ -44,10 +45,8 @@ public void testPQCKeyGenKEM_PlusToInterop() throws Exception {
String pqcAlgorithm = "ML-KEM-512";
boolean same = false;
- if (getProviderName().equals("OpenJCEPlusFIPS")) {
- //This is not in the FIPS provider yet.
- return;
- }
+ //This is not in the FIPS provider yet.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
keyPairGenPlus = KeyPairGenerator.getInstance(pqcAlgorithm, getProviderName());
keyFactoryPlus = KeyFactory.getInstance(pqcAlgorithm, getProviderName());
keyPairGenInterop = KeyPairGenerator.getInstance(pqcAlgorithm, getInteropProviderName());
@@ -111,10 +110,8 @@ public void testPQCKeyGenKEM_Interop() throws Exception {
String pqcAlgorithm = "ML-KEM-512";
boolean same = false;
- if (getProviderName().equals("OpenJCEPlusFIPS")) {
- //This is not in the FIPS provider yet.
- return;
- }
+ //This is not in the FIPS provider yet.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
keyPairGenPlus = KeyPairGenerator.getInstance(pqcAlgorithm, getProviderName());
keyFactoryPlus = KeyFactory.getInstance(pqcAlgorithm, getProviderName());
keyPairGenInterop = KeyPairGenerator.getInstance(pqcAlgorithm, getInteropProviderName());
@@ -148,11 +145,10 @@ public void testPQCKeyGenKEM_PlusToInteropRAW() throws Exception {
String pqcAlgorithm = "ML-KEM-512";
boolean same = false;
- if (getProviderName().equals("OpenJCEPlusFIPS") ||
- getInteropProviderName().equals(Utils.PROVIDER_BC)) {
- //This is not in the FIPS provider yet and Boucy Castle does not support this test.
- return;
- }
+ //This is not in the FIPS provider yet and Bouncy Castle does not support this test.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName()));
+
keyPairGenPlus = KeyPairGenerator.getInstance(pqcAlgorithm, getProviderName());
keyFactoryPlus = KeyFactory.getInstance(pqcAlgorithm, getProviderName());
keyPairGenInterop = KeyPairGenerator.getInstance(pqcAlgorithm, getInteropProviderName());
@@ -181,10 +177,9 @@ public void testPQCKeyGenMLDSA_PlusToInterop() throws Exception {
String pqcAlgorithm = "ML-DSA-65";
boolean same = false;
- if (getProviderName().equals("OpenJCEPlusFIPS")) {
- //This is not in the FIPS provider yet.
- return;
- }
+ //This is not in the FIPS provider yet.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+
keyPairGenPlus = KeyPairGenerator.getInstance(pqcAlgorithm, getProviderName());
keyFactoryPlus = KeyFactory.getInstance(pqcAlgorithm, getProviderName());
keyPairGenInterop = KeyPairGenerator.getInstance(pqcAlgorithm, getInteropProviderName2());
@@ -217,10 +212,9 @@ public void testPQCKeyGenMLDSA_Interop() throws Exception {
String pqcAlgorithm = "ML-DSA-65";
boolean same = false;
- if (getProviderName().equals("OpenJCEPlusFIPS")) {
- //This is not in the FIPS provider yet.
- return;
- }
+ //This is not in the FIPS provider yet.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+
keyPairGenPlus = KeyPairGenerator.getInstance(pqcAlgorithm, getProviderName());
keyFactoryPlus = KeyFactory.getInstance(pqcAlgorithm, getProviderName());
keyPairGenInterop = KeyPairGenerator.getInstance(pqcAlgorithm, getInteropProviderName2());
@@ -253,11 +247,10 @@ public void testPQCKeyGenMLDSA_PlusToInteropRAW() throws Exception {
String pqcAlgorithm = "ML-DSA-65";
boolean same = false;
- if (getProviderName().equals("OpenJCEPlusFIPS") ||
- getInteropProviderName().equals(Utils.PROVIDER_BC)) {
- //This is not in the FIPS provider yet and Bouncy Castle does not support this test.
- return;
- }
+ //This is not in the FIPS provider yet and Bouncy Castle does not support this test.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName()));
+
keyPairGenPlus = KeyPairGenerator.getInstance(pqcAlgorithm, getProviderName());
keyFactoryPlus = KeyFactory.getInstance(pqcAlgorithm, getProviderName());
keyPairGenInterop = KeyPairGenerator.getInstance(pqcAlgorithm, getInteropProviderName2());
@@ -303,14 +296,11 @@ protected KeyPair generateKeyPair(KeyPairGenerator keyPairGen) throws Exception
@ParameterizedTest
@CsvSource({"ML-DSA", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"})
public void testSignInteropAndVerifyPlus(String algorithm) throws Exception {
+ //This is not in the FIPS provider yet.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(algorithm.equalsIgnoreCase("ML-DSA") && getInteropProviderName2().equalsIgnoreCase("BC"));
+
try {
- if (getProviderName().equals("OpenJCEPlusFIPS")) {
- //This is not in the FIPS provider yet.
- return;
- }
- if (algorithm.equalsIgnoreCase("ML-DSA") && getInteropProviderName2().equalsIgnoreCase("BC")) {
- return;
- }
keyPairGenInterop = KeyPairGenerator.getInstance(algorithm, getInteropProviderName2());
KeyPair keyPairInterop = generateKeyPair(keyPairGenInterop);
@@ -341,12 +331,11 @@ public void testSignInteropAndVerifyPlus(String algorithm) throws Exception {
@ParameterizedTest
@CsvSource({"ML-DSA", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"})
public void testSignInteropKeysPlusSignVerify(String algorithm) {
+ //This is not in the FIPS provider yet.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName2()));
+
try {
- if (getProviderName().equals("OpenJCEPlusFIPS") ||
- getInteropProviderName().equals(Utils.PROVIDER_BC)) {
- //This is not in the FIPS provider yet.
- return;
- }
keyPairGenInterop = KeyPairGenerator.getInstance(algorithm, getInteropProviderName2());
KeyPair keyPairInterop = generateKeyPair(keyPairGenInterop);
@@ -376,12 +365,11 @@ public void testSignInteropKeysPlusSignVerify(String algorithm) {
@ParameterizedTest
@CsvSource({"ML-DSA", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"})
public void testSignPlusKeysInteropSignVerify(String algorithm) {
+ //This is not in the FIPS provider yet.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName2()));
+
try {
- if (getProviderName().equals("OpenJCEPlusFIPS") ||
- getInteropProviderName().equals(Utils.PROVIDER_BC)) {
- //This is not in the FIPS provider yet.
- return;
- }
keyPairGenPlus = KeyPairGenerator.getInstance(algorithm, getProviderName());
KeyPair keyPairPlus = generateKeyPair(keyPairGenPlus);
@@ -412,10 +400,8 @@ public void testSignPlusKeysInteropSignVerify(String algorithm) {
@CsvSource({"ML-DSA", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"})
public void testSignPlusAndVerifyInterop(String algorithm) {
try {
- if (getProviderName().equals("OpenJCEPlusFIPS")) {
- //This is not in the FIPS provider yet.
- return;
- }
+ //This is not in the FIPS provider yet.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
keyPairGenPlus = KeyPairGenerator.getInstance(algorithm, getProviderName());
KeyPair keyPairPlus = generateKeyPair(keyPairGenPlus);
@@ -447,13 +433,11 @@ public void testSignPlusAndVerifyInterop(String algorithm) {
@ParameterizedTest
@CsvSource({"ML-KEM", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"})
public void testKEMPlusKeyInteropAll(String Algorithm) {
- try {
- if (getProviderName().equals("OpenJCEPlusFIPS") ||
- getInteropProviderName().equals(Utils.PROVIDER_BC)) {
- //This is not in the FIPS provider yet and Oracle Private keys have an extra Octet in them.
- return;
- }
+ //This is not in the FIPS provider yet and Oracle Private keys have an extra Octet in them.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName()));
+ try {
KEM kemInterop = KEM.getInstance("ML-KEM", getInteropProviderName());
keyPairGenPlus = KeyPairGenerator.getInstance(Algorithm, getProviderName());
@@ -489,13 +473,11 @@ public void testKEMPlusKeyInteropAll(String Algorithm) {
@ParameterizedTest
@CsvSource({"ML-KEM", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"})
public void testKEMInteropKeyPlusAll(String Algorithm) {
- try {
- if (getProviderName().equals("OpenJCEPlusFIPS") ||
- getInteropProviderName().equals(Utils.PROVIDER_BC)) {
- //This is not in the FIPS provider yet and Oracle Private keys have an extra Octet in them.
- return;
- }
+ //This is not in the FIPS provider yet and Oracle Private keys have an extra Octet in them.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName()));
+ try {
KEM kemPlus = KEM.getInstance(Algorithm, getProviderName());
keyPairGenInterop = KeyPairGenerator.getInstance(Algorithm, getInteropProviderName());
@@ -532,10 +514,8 @@ public void testKEMInteropKeyPlusAll(String Algorithm) {
@CsvSource({"ML-KEM", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"})
public void testKEMPlusCreatesInteropGet(String Algorithm) {
try {
- if (getProviderName().equals("OpenJCEPlusFIPS")) {
- //This is not in the FIPS provider yet and Oracle Private keys have an extra Octet in them.
- return;
- }
+ //This is not in the FIPS provider yet and Oracle Private keys have an extra Octet in them.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
KEM kemPlus = KEM.getInstance(Algorithm, getProviderName());
KEM kemInterop = KEM.getInstance("ML-KEM", getInteropProviderName());
@@ -572,10 +552,8 @@ public void testKEMPlusCreatesInteropGet(String Algorithm) {
@CsvSource({"ML-KEM", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"})
public void testKEMInteropCreatesPlusGet(String Algorithm) {
try {
- if (getProviderName().equals("OpenJCEPlusFIPS")) {
- //This is not in the FIPS provider yet and Oracle Private keys have an extra Octet in them.
- return;
- }
+ //This is not in the FIPS provider yet and Oracle Private keys have an extra Octet in them.
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
KEM kemPlus = KEM.getInstance(Algorithm, getProviderName());
KEM kemInterop = KEM.getInstance("ML-KEM", getInteropProviderName());
@@ -605,4 +583,160 @@ public void testKEMInteropCreatesPlusGet(String Algorithm) {
}
}
+ /**
+ * Test ML-KEM interoperability using NamedParameterSpec to initialize KeyPairGenerator.
+ * Tests encapsulation / decapsulation with different providers.
+ *
+ * @param parameterSet The ML-KEM parameter set (ML-KEM-512, ML-KEM-768, ML-KEM-1024)
+ * @throws Exception if any cryptographic operation fails
+ */
+ @ParameterizedTest
+ @CsvSource({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"})
+ public void testMLKEMInteropWithNamedParameterSpec(String parameterSet) throws Exception {
+ // Not in FIPS provider yet and BC doesn't support this test
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName()));
+
+ // Generate key pair using NamedParameterSpec with provider
+ KeyPairGenerator keyPairGenPlus = KeyPairGenerator.getInstance("ML-KEM", getProviderName());
+ keyPairGenPlus.initialize(new NamedParameterSpec(parameterSet));
+ KeyPair keyPairPlus = generateKeyPair(keyPairGenPlus);
+
+ // Encapsulate using provider
+ KEM kemPlus = KEM.getInstance("ML-KEM", getProviderName());
+ KEM.Encapsulator encapsulator = kemPlus.newEncapsulator(keyPairPlus.getPublic());
+ KEM.Encapsulated encapsulated = encapsulator.encapsulate(0, 32, "AES");
+
+ SecretKey encapKey = encapsulated.key();
+ byte[] encapsulation = encapsulated.encapsulation();
+
+ // Decapsulate using interop provider
+ KEM kemInterop = KEM.getInstance("ML-KEM", getInteropProviderName());
+ KEM.Decapsulator decapsulator = kemInterop.newDecapsulator(keyPairPlus.getPrivate());
+ SecretKey decapKey = decapsulator.decapsulate(encapsulation, 0, 32, "AES");
+
+ // Verify that both keys match
+ assertArrayEquals(encapKey.getEncoded(), decapKey.getEncoded(),
+ "Encapsulated and decapsulated keys do not match for " + parameterSet);
+ }
+
+ /**
+ * Test ML-KEM interoperability with empty parameters using NamedParameterSpec.
+ * Tests encapsulation and decapsulation without from/to specification.
+ *
+ * @param parameterSet The ML-KEM parameter set (ML-KEM-512, ML-KEM-768, ML-KEM-1024)
+ * @throws Exception if any cryptographic operation fails
+ */
+ @ParameterizedTest
+ @CsvSource({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"})
+ public void testMLKEMInteropEmptyParamsWithNamedParameterSpec(String parameterSet) throws Exception {
+ // Not in FIPS provider yet and BC doesn't support this test
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName()));
+
+ // Generate key pair using NamedParameterSpec with interop provider
+ KeyPairGenerator keyPairGenInterop = KeyPairGenerator.getInstance("ML-KEM", getInteropProviderName());
+ keyPairGenInterop.initialize(new NamedParameterSpec(parameterSet));
+ KeyPair keyPairInterop = generateKeyPair(keyPairGenInterop);
+
+ // Encapsulate using interop provider (no from/to parameters)
+ KEM kemInterop = KEM.getInstance("ML-KEM", getInteropProviderName());
+ KEM.Encapsulator encapsulator = kemInterop.newEncapsulator(keyPairInterop.getPublic());
+ KEM.Encapsulated encapsulated = encapsulator.encapsulate();
+
+ SecretKey encapKey = encapsulated.key();
+ byte[] encapsulation = encapsulated.encapsulation();
+
+ // Decapsulate using provider (no from/to parameters)
+ KEM kemPlus = KEM.getInstance("ML-KEM", getProviderName());
+ KEM.Decapsulator decapsulator = kemPlus.newDecapsulator(keyPairInterop.getPrivate());
+ SecretKey decapKey = decapsulator.decapsulate(encapsulation);
+
+ // Verify that both keys match
+ assertArrayEquals(encapKey.getEncoded(), decapKey.getEncoded(),
+ "Encapsulated and decapsulated keys do not match for " + parameterSet);
+ }
+
+ /**
+ * Test ML-KEM interoperability with smaller secret size using NamedParameterSpec.
+ * Tests with 16 bytes instead of the default 32 bytes.
+ *
+ * @param parameterSet The ML-KEM parameter set (ML-KEM-512, ML-KEM-768, ML-KEM-1024)
+ * @throws Exception if any cryptographic operation fails
+ */
+ @ParameterizedTest
+ @CsvSource({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"})
+ public void testMLKEMInteropSmallerSecretWithNamedParameterSpec(String parameterSet) throws Exception {
+ // Not in FIPS provider yet and BC doesn't support this test
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName()));
+
+ // Generate key pair using NamedParameterSpec with provider
+ KeyPairGenerator keyPairGenPlus = KeyPairGenerator.getInstance("ML-KEM", getProviderName());
+ keyPairGenPlus.initialize(new NamedParameterSpec(parameterSet));
+ KeyPair keyPairPlus = generateKeyPair(keyPairGenPlus);
+
+ // Encapsulate using provider with smaller secret (16 bytes)
+ KEM kemPlus = KEM.getInstance("ML-KEM", getProviderName());
+ KEM.Encapsulator encapsulator = kemPlus.newEncapsulator(keyPairPlus.getPublic());
+ KEM.Encapsulated encapsulated = encapsulator.encapsulate(0, 16, "AES");
+
+ SecretKey encapKey = encapsulated.key();
+ byte[] encapsulation = encapsulated.encapsulation();
+
+ // Decapsulate using interop provider with same secret size
+ KEM kemInterop = KEM.getInstance("ML-KEM", getInteropProviderName());
+ KEM.Decapsulator decapsulator = kemInterop.newDecapsulator(keyPairPlus.getPrivate());
+ SecretKey decapKey = decapsulator.decapsulate(encapsulation, 0, 16, "AES");
+
+ // Verify that both keys match
+ assertArrayEquals(encapKey.getEncoded(), decapKey.getEncoded(),
+ "Encapsulated and decapsulated keys do not match for " + parameterSet);
+ }
+
+ /**
+ * Test bidirectional ML-KEM interoperability using NamedParameterSpec.
+ * Tests both directions to and from providers.
+ *
+ * @param parameterSet The ML-KEM parameter set (ML-KEM-512, ML-KEM-768, ML-KEM-1024)
+ * @throws Exception if any cryptographic operation fails
+ */
+ @ParameterizedTest
+ @CsvSource({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"})
+ public void testMLKEMBidirectionalInteropWithNamedParameterSpec(String parameterSet) throws Exception {
+ // Not in FIPS provider yet and BC doesn't support this test
+ assumeFalse("OpenJCEPlusFIPS".equals(getProviderName()));
+ assumeFalse(Utils.PROVIDER_BC.equals(getInteropProviderName()));
+
+ // Test 1: Generate with provider, encapsulate with interop provider, decapsulate with provider
+ KeyPairGenerator keyPairGenPlus = KeyPairGenerator.getInstance("ML-KEM", getProviderName());
+ keyPairGenPlus.initialize(new NamedParameterSpec(parameterSet));
+ KeyPair keyPairPlus = generateKeyPair(keyPairGenPlus);
+
+ KEM kemInterop = KEM.getInstance("ML-KEM", getInteropProviderName());
+ KEM.Encapsulator encapsulatorInterop = kemInterop.newEncapsulator(keyPairPlus.getPublic());
+ KEM.Encapsulated encapsulatedInterop = encapsulatorInterop.encapsulate(0, 32, "AES");
+
+ KEM kemPlus = KEM.getInstance("ML-KEM", getProviderName());
+ KEM.Decapsulator decapsulatorPlus = kemPlus.newDecapsulator(keyPairPlus.getPrivate());
+ SecretKey decapKeyPlus = decapsulatorPlus.decapsulate(encapsulatedInterop.encapsulation(), 0, 32, "AES");
+
+ assertArrayEquals(encapsulatedInterop.key().getEncoded(), decapKeyPlus.getEncoded(),
+ "Keys do not match for test 1 with " + parameterSet);
+
+ // Test 2: Generate with interop provider, encapsulate with provider, decapsulate with interop provider
+ KeyPairGenerator keyPairGenInterop = KeyPairGenerator.getInstance("ML-KEM", getInteropProviderName());
+ keyPairGenInterop.initialize(new NamedParameterSpec(parameterSet));
+ KeyPair keyPairInterop = generateKeyPair(keyPairGenInterop);
+
+ KEM.Encapsulator encapsulatorPlus = kemPlus.newEncapsulator(keyPairInterop.getPublic());
+ KEM.Encapsulated encapsulatedPlus = encapsulatorPlus.encapsulate(0, 32, "AES");
+
+ KEM.Decapsulator decapsulatorInterop = kemInterop.newDecapsulator(keyPairInterop.getPrivate());
+ SecretKey decapKeyInterop = decapsulatorInterop.decapsulate(encapsulatedPlus.encapsulation(), 0, 32, "AES");
+
+ assertArrayEquals(encapsulatedPlus.key().getEncoded(), decapKeyInterop.getEncoded(),
+ "Keys do not match for test 2 with " + parameterSet);
+ }
+
}