Skip to content

Commit 0657f44

Browse files
author
royb
committed
added MLKEM to jdk21
1 parent b4315db commit 0657f44

File tree

6 files changed

+449
-0
lines changed

6 files changed

+449
-0
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.bouncycastle.pqc.jcajce.provider;
2+
3+
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
4+
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
5+
import org.bouncycastle.jcajce.provider.asymmetric.mlkem.MLKEMKeyFactorySpi;
6+
import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
7+
import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
8+
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
9+
10+
public class MLKEM
11+
{
12+
private static final String PREFIX = "org.bouncycastle.pqc.jcajce.provider" + ".mlkem.";
13+
14+
public static class Mappings
15+
extends AsymmetricAlgorithmProvider
16+
{
17+
public Mappings()
18+
{
19+
}
20+
21+
public void configure(ConfigurableProvider provider)
22+
{
23+
provider.addAlgorithm("KeyFactory.MLKEM", PREFIX + "MLKEMKeyFactorySpi");
24+
provider.addAlgorithm("KeyPairGenerator.MLKEM", PREFIX + "MLKEMKeyPairGeneratorSpi");
25+
26+
provider.addAlgorithm("KeyGenerator.MLKEM", PREFIX + "MLKEMKeyGeneratorSpi");
27+
28+
AsymmetricKeyInfoConverter keyFact = new MLKEMKeyFactorySpi();
29+
30+
provider.addAlgorithm("Cipher.ML-KEM", PREFIX + "MLKEMCipherSpi$Base");
31+
provider.addAlgorithm("Alg.Alias.Cipher", "ML-KEM");
32+
33+
registerOid(provider, NISTObjectIdentifiers.id_alg_ml_kem_512, "ML-KEM", keyFact);
34+
registerOid(provider, NISTObjectIdentifiers.id_alg_ml_kem_768, "ML-KEM", keyFact);
35+
registerOid(provider, NISTObjectIdentifiers.id_alg_ml_kem_1024, "ML-KEM", keyFact);
36+
37+
provider.addAlgorithm("Kem.ML-KEM", PREFIX + "MLKEMSpi");
38+
provider.addAlgorithm("Alg.Alias.Kem", "ML-KEM");
39+
}
40+
}
41+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package org.bouncycastle.pqc.jcajce.provider.mlkem;
2+
3+
import org.bouncycastle.jcajce.provider.asymmetric.mlkem.BCMLKEMPrivateKey;
4+
import org.bouncycastle.jcajce.spec.KTSParameterSpec;
5+
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberKEMExtractor;
6+
import org.bouncycastle.pqc.jcajce.provider.Util;
7+
8+
import javax.crypto.DecapsulateException;
9+
import javax.crypto.KEMSpi;
10+
import javax.crypto.SecretKey;
11+
import javax.crypto.spec.SecretKeySpec;
12+
import java.security.InvalidKeyException;
13+
import java.util.Arrays;
14+
import java.util.Objects;
15+
16+
public class MLKEMDecapsulatorSpi
17+
implements KEMSpi.DecapsulatorSpi
18+
{
19+
BCMLKEMPrivateKey privateKey;
20+
KTSParameterSpec parameterSpec;
21+
KyberKEMExtractor kemExt;
22+
23+
public MLKEMDecapsulatorSpi(BCMLKEMPrivateKey privateKey, KTSParameterSpec parameterSpec)
24+
{
25+
this.privateKey = privateKey;
26+
this.parameterSpec = parameterSpec;
27+
28+
this.kemExt = new KyberKEMExtractor(privateKey.getKeyParams());
29+
}
30+
31+
@Override
32+
public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm) throws DecapsulateException
33+
{
34+
Objects.checkFromToIndex(from, to, engineSecretSize());
35+
Objects.requireNonNull(algorithm, "null algorithm");
36+
Objects.requireNonNull(encapsulation, "null encapsulation");
37+
38+
if (encapsulation.length != engineEncapsulationSize())
39+
{
40+
throw new DecapsulateException("incorrect encapsulation size");
41+
}
42+
43+
// if algorithm is Generic then use parameterSpec to wrap key
44+
if (!parameterSpec.getKeyAlgorithmName().equals("Generic") &&
45+
algorithm.equals("Generic"))
46+
{
47+
algorithm = parameterSpec.getKeyAlgorithmName();
48+
}
49+
50+
// check spec algorithm mismatch provided algorithm
51+
if (!parameterSpec.getKeyAlgorithmName().equals("Generic") &&
52+
!parameterSpec.getKeyAlgorithmName().equals(algorithm))
53+
{
54+
throw new UnsupportedOperationException(parameterSpec.getKeyAlgorithmName() + " does not match " + algorithm);
55+
}
56+
57+
// Only use KDF when ktsParameterSpec is provided
58+
// Considering any ktsParameterSpec with "Generic" as ktsParameterSpec not provided
59+
boolean useKDF = parameterSpec.getKdfAlgorithm() != null;
60+
61+
byte[] secret = kemExt.extractSecret(encapsulation);
62+
63+
if (useKDF)
64+
{
65+
try
66+
{
67+
secret = Util.makeKeyBytes(parameterSpec, secret);
68+
}
69+
catch (InvalidKeyException e)
70+
{
71+
throw new IllegalStateException(e);
72+
}
73+
}
74+
byte[] secretKey = Arrays.copyOfRange(secret, from, to);
75+
76+
return new SecretKeySpec(secretKey, algorithm);
77+
}
78+
79+
@Override
80+
public int engineSecretSize()
81+
{
82+
return parameterSpec.getKeySize() / 8;
83+
}
84+
85+
@Override
86+
public int engineEncapsulationSize()
87+
{
88+
return kemExt.getEncapsulationLength();
89+
}
90+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.bouncycastle.pqc.jcajce.provider.mlkem;
2+
3+
import org.bouncycastle.crypto.SecretWithEncapsulation;
4+
import org.bouncycastle.jcajce.provider.asymmetric.mlkem.BCMLKEMPublicKey;
5+
import org.bouncycastle.jcajce.spec.KTSParameterSpec;
6+
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberKEMGenerator;
7+
import org.bouncycastle.pqc.jcajce.provider.Util;
8+
9+
import javax.crypto.KEM;
10+
import javax.crypto.KEMSpi;
11+
import javax.crypto.spec.SecretKeySpec;
12+
import java.security.InvalidKeyException;
13+
import java.security.SecureRandom;
14+
import java.util.Arrays;
15+
import java.util.Objects;
16+
17+
public class MLKEMEncapsulatorSpi
18+
implements KEMSpi.EncapsulatorSpi
19+
{
20+
private final BCMLKEMPublicKey publicKey;
21+
private final KTSParameterSpec parameterSpec;
22+
private final KyberKEMGenerator kemGen;
23+
24+
public MLKEMEncapsulatorSpi(BCMLKEMPublicKey publicKey, KTSParameterSpec parameterSpec, SecureRandom random)
25+
{
26+
this.publicKey = publicKey;
27+
this.parameterSpec = parameterSpec;
28+
29+
this.kemGen = new KyberKEMGenerator(random);
30+
}
31+
32+
33+
@Override
34+
public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm)
35+
{
36+
Objects.checkFromToIndex(from, to, engineSecretSize());
37+
Objects.requireNonNull(algorithm, "null algorithm");
38+
39+
// if algorithm is Generic then use parameterSpec to wrap key
40+
if (!parameterSpec.getKeyAlgorithmName().equals("Generic") &&
41+
algorithm.equals("Generic"))
42+
{
43+
algorithm = parameterSpec.getKeyAlgorithmName();
44+
}
45+
46+
// check spec algorithm mismatch provided algorithm
47+
if (!parameterSpec.getKeyAlgorithmName().equals("Generic") &&
48+
!parameterSpec.getKeyAlgorithmName().equals(algorithm))
49+
{
50+
throw new UnsupportedOperationException(parameterSpec.getKeyAlgorithmName() + " does not match " + algorithm);
51+
}
52+
53+
// Only use KDF when ktsParameterSpec is provided
54+
// Considering any ktsParameterSpec with "Generic" as ktsParameterSpec not provided
55+
boolean useKDF = parameterSpec.getKdfAlgorithm() != null;
56+
57+
SecretWithEncapsulation secEnc = kemGen.generateEncapsulated(publicKey.getKeyParams());
58+
59+
byte[] encapsulation = secEnc.getEncapsulation();
60+
byte[] secret = secEnc.getSecret();
61+
62+
byte[] secretKey;
63+
64+
if (useKDF)
65+
{
66+
try
67+
{
68+
secret = Util.makeKeyBytes(parameterSpec, secret);
69+
}
70+
catch (InvalidKeyException e)
71+
{
72+
throw new IllegalStateException(e);
73+
}
74+
}
75+
76+
secretKey = Arrays.copyOfRange(secret, from, to);
77+
78+
return new KEM.Encapsulated(new SecretKeySpec(secretKey, algorithm), encapsulation, null); //TODO: DER encoding for params }
79+
}
80+
81+
@Override
82+
public int engineSecretSize()
83+
{
84+
return parameterSpec.getKeySize() / 8;
85+
}
86+
87+
88+
@Override
89+
public int engineEncapsulationSize()
90+
{
91+
return kemGen.getEncapsulationSize(publicKey.getKeyParams());
92+
}
93+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.bouncycastle.pqc.jcajce.provider.mlkem;
2+
3+
import org.bouncycastle.jcajce.provider.asymmetric.mlkem.BCMLKEMPrivateKey;
4+
import org.bouncycastle.jcajce.provider.asymmetric.mlkem.BCMLKEMPublicKey;
5+
import org.bouncycastle.jcajce.spec.KTSParameterSpec;
6+
7+
import javax.crypto.KEMSpi;
8+
import java.security.InvalidAlgorithmParameterException;
9+
import java.security.InvalidKeyException;
10+
import java.security.PrivateKey;
11+
import java.security.PublicKey;
12+
import java.security.SecureRandom;
13+
import java.security.spec.AlgorithmParameterSpec;
14+
15+
public class MLKEMSpi
16+
implements KEMSpi
17+
{
18+
@Override
19+
public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, AlgorithmParameterSpec spec, SecureRandom secureRandom)
20+
throws InvalidAlgorithmParameterException, InvalidKeyException
21+
{
22+
if (!(publicKey instanceof BCMLKEMPublicKey))
23+
{
24+
throw new InvalidKeyException("unsupported key");
25+
}
26+
if (spec == null)
27+
{
28+
// Do not wrap key, no KDF
29+
spec = new KTSParameterSpec.Builder("Generic", 256).withNoKdf().build();
30+
}
31+
if (!(spec instanceof KTSParameterSpec))
32+
{
33+
throw new InvalidAlgorithmParameterException("MLKEM can only accept KTSParameterSpec");
34+
}
35+
if (secureRandom == null)
36+
{
37+
secureRandom = new SecureRandom();
38+
}
39+
return new MLKEMEncapsulatorSpi((BCMLKEMPublicKey) publicKey, (KTSParameterSpec) spec, secureRandom);
40+
}
41+
42+
@Override
43+
public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec)
44+
throws InvalidAlgorithmParameterException, InvalidKeyException
45+
{
46+
if (!(privateKey instanceof BCMLKEMPrivateKey))
47+
{
48+
throw new InvalidKeyException("unsupported key");
49+
}
50+
if (spec == null)
51+
{
52+
// Do not unwrap key, no KDF
53+
spec = new KTSParameterSpec.Builder("Generic", 256).withNoKdf().build();
54+
}
55+
if (!(spec instanceof KTSParameterSpec))
56+
{
57+
throw new InvalidAlgorithmParameterException("MLKEM can only accept KTSParameterSpec");
58+
}
59+
return new MLKEMDecapsulatorSpi((BCMLKEMPrivateKey) privateKey, (KTSParameterSpec) spec);
60+
}
61+
}

prov/src/test/jdk21/org/bouncycastle/jcacje/provider/test/AllTests21.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public static Test suite()
2020
TestSuite suite = new TestSuite("JDK21 Provider Tests");
2121
suite.addTestSuite(NTRUKEMTest.class);
2222
suite.addTestSuite(SNTRUPrimeKEMTest.class);
23+
suite.addTestSuite(MLKEMTest.class);
2324
return suite;
2425
}
2526
}

0 commit comments

Comments
 (0)