Skip to content

Commit ce4aeba

Browse files
committed
added choice key encodings similar to ML-DSA
1 parent 2e96d8e commit ce4aeba

File tree

5 files changed

+92
-15
lines changed

5 files changed

+92
-15
lines changed

core/src/main/java/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55
public class MLKEMPrivateKeyParameters
66
extends MLKEMKeyParameters
77
{
8+
public static final int BOTH = 0;
9+
public static final int SEED_ONLY = 1;
10+
public static final int EXPANDED_KEY = 2;
11+
812
final byte[] s;
913
final byte[] hpk;
1014
final byte[] nonce;
1115
final byte[] t;
1216
final byte[] rho;
1317
final byte[] seed;
14-
18+
19+
private final int prefFormat;
20+
1521
public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] s, byte[] hpk, byte[] nonce, byte[] t, byte[] rho)
1622
{
1723
this(params, s, hpk, nonce, t, rho, null);
@@ -27,6 +33,7 @@ public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] s, byte[] hpk, b
2733
this.t = Arrays.clone(t);
2834
this.rho = Arrays.clone(rho);
2935
this.seed = Arrays.clone(seed);
36+
this.prefFormat = BOTH;
3037
}
3138

3239
public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] encoding)
@@ -60,6 +67,35 @@ public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] encoding)
6067
this.nonce = Arrays.copyOfRange(encoding, index, index + MLKEMEngine.KyberSymBytes);
6168
this.seed = null;
6269
}
70+
71+
this.prefFormat = BOTH;
72+
}
73+
74+
private MLKEMPrivateKeyParameters(MLKEMPrivateKeyParameters params, int preferredFormat)
75+
{
76+
super(true, params.getParameters());
77+
78+
this.s = params.s;
79+
this.t = params.t;
80+
this.rho = params.rho;
81+
this.hpk = params.hpk;
82+
this.nonce = params.nonce;
83+
this.seed = params.seed;
84+
this.prefFormat = preferredFormat;
85+
}
86+
87+
public MLKEMPrivateKeyParameters getParametersWithFormat(int format)
88+
{
89+
if (this.seed == null && format == SEED_ONLY)
90+
{
91+
throw new IllegalArgumentException("no seed available");
92+
}
93+
return new MLKEMPrivateKeyParameters(this, format);
94+
}
95+
96+
public int getPreferredFormat()
97+
{
98+
return prefFormat;
6399
}
64100

65101
public byte[] getEncoded()

core/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyInfoFactory.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import org.bouncycastle.pqc.legacy.crypto.mceliece.McElieceCCA2PrivateKeyParameters;
5454
import org.bouncycastle.pqc.legacy.crypto.qtesla.QTESLAPrivateKeyParameters;
5555
import org.bouncycastle.util.Pack;
56-
import org.bouncycastle.util.Properties;
5756

5857
/**
5958
* Factory to create ASN.1 private key info objects from lightweight private keys.
@@ -249,16 +248,15 @@ else if (privateKey instanceof MLKEMPrivateKeyParameters)
249248

250249
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(Utils.mlkemOidLookup(params.getParameters()));
251250

252-
byte[] seed = params.getSeed();
253-
if (Properties.isOverrideSet("org.bouncycastle.mlkem.seedOnly"))
251+
if (params.getPreferredFormat() == MLKEMPrivateKeyParameters.SEED_ONLY)
254252
{
255-
if (seed == null) // very difficult to imagine, but...
256-
{
257-
throw new IOException("no seed available");
258-
}
259-
return new PrivateKeyInfo(algorithmIdentifier, seed, attributes);
253+
return new PrivateKeyInfo(algorithmIdentifier, new DERTaggedObject(false, 0, new DEROctetString(params.getSeed())), attributes);
260254
}
261-
return new PrivateKeyInfo(algorithmIdentifier, getBasicPQCEncoding(seed, params.getEncoded()), attributes);
255+
else if (params.getPreferredFormat() == MLKEMPrivateKeyParameters.EXPANDED_KEY)
256+
{
257+
return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(params.getEncoded()), attributes);
258+
}
259+
return new PrivateKeyInfo(algorithmIdentifier, getBasicPQCEncoding(params.getSeed(), params.getEncoded()), attributes);
262260
}
263261
else if (privateKey instanceof NTRULPRimePrivateKeyParameters)
264262
{
@@ -297,7 +295,6 @@ else if (privateKey instanceof MLDSAPrivateKeyParameters)
297295

298296
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(Utils.mldsaOidLookup(params.getParameters()));
299297

300-
byte[] seed = params.getSeed();
301298
if (params.getPreferredFormat() == MLDSAPrivateKeyParameters.SEED_ONLY)
302299
{
303300
return new PrivateKeyInfo(algorithmIdentifier, new DERTaggedObject(false, 0, new DEROctetString(params.getSeed())), attributes);

prov/src/main/java/org/bouncycastle/jcajce/interfaces/MLKEMPrivateKey.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,12 @@ public interface MLKEMPrivateKey
2525
* @return the seed for the private key, null if not available.
2626
*/
2727
byte[] getSeed();
28+
29+
/**
30+
* Return a privateKey which will encode as seed-only or as an expanded-key.
31+
*
32+
* @param preferSeedOnly if true, return a privateKey which will encode to seed-only if possible.
33+
* @return a new MLKEMPrivateKey which encodes to either seed-only or expanded-key.
34+
*/
35+
MLKEMPrivateKey getPrivateKey(boolean preferSeedOnly);
2836
}

prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/mlkem/BCMLKEMPrivateKey.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.bouncycastle.jcajce.interfaces.MLKEMPrivateKey;
1010
import org.bouncycastle.jcajce.interfaces.MLKEMPublicKey;
1111
import org.bouncycastle.jcajce.spec.MLKEMParameterSpec;
12+
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPrivateKeyParameters;
1213
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
1314
import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
1415
import org.bouncycastle.pqc.crypto.util.PrivateKeyInfoFactory;
@@ -120,6 +121,21 @@ public byte[] getSeed()
120121
return params.getSeed();
121122
}
122123

124+
@Override
125+
public MLKEMPrivateKey getPrivateKey(boolean preferSeedOnly)
126+
{
127+
if (preferSeedOnly)
128+
{
129+
byte[] seed = params.getSeed();
130+
if (seed != null)
131+
{
132+
return new BCMLKEMPrivateKey(this.params.getParametersWithFormat(MLDSAPrivateKeyParameters.SEED_ONLY));
133+
}
134+
}
135+
136+
return new BCMLKEMPrivateKey(this.params.getParametersWithFormat(MLDSAPrivateKeyParameters.EXPANDED_KEY));
137+
}
138+
123139
public MLKEMParameterSpec getParameterSpec()
124140
{
125141
return MLKEMParameterSpec.fromName(params.getParameters().getName());

prov/src/test/java/org/bouncycastle/pqc/jcajce/provider/test/MLKEMTest.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import junit.framework.TestCase;
2222
import org.bouncycastle.asn1.ASN1OctetString;
2323
import org.bouncycastle.asn1.ASN1Sequence;
24+
import org.bouncycastle.asn1.ASN1TaggedObject;
2425
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
2526
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
2627
import org.bouncycastle.jcajce.SecretKeyWithEncapsulation;
@@ -155,16 +156,35 @@ public void testSeedPrivateKeyEncoding()
155156
+ "300102030405060708090a0b0c0d0e0f");
156157
kpGen512.initialize(MLKEMParameterSpec.ml_kem_512, new FixedSecureRandom(seed));
157158
KeyPair kp512 = kpGen512.generateKeyPair();
158-
Security.setProperty("org.bouncycastle.mlkem.seedOnly", "true");
159159

160-
PrivateKeyInfo privInfo = PrivateKeyInfo.getInstance(kp512.getPrivate().getEncoded());
160+
PrivateKeyInfo privInfo = PrivateKeyInfo.getInstance(((MLKEMPrivateKey)kp512.getPrivate()).getPrivateKey(true).getEncoded());
161161

162-
Security.setProperty("org.bouncycastle.mlkem.seedOnly", "false");
163-
ASN1OctetString k = privInfo.getPrivateKey();
162+
ASN1OctetString k = ASN1OctetString.getInstance((ASN1TaggedObject)privInfo.parsePrivateKey(), false);
164163

165164
assertTrue(Arrays.areEqual(k.getOctets(), seed));
166165
}
167166

167+
public void testExpandedPrivateKeyEncoding()
168+
throws Exception
169+
{
170+
KeyPairGenerator kpGen512 = KeyPairGenerator.getInstance("ML-KEM-512", "BC");
171+
172+
byte[] seed = Hex.decode("000102030405060708090a0b0c0d0e0f"
173+
+ "100102030405060708090a0b0c0d0e0f"
174+
+ "200102030405060708090a0b0c0d0e0f"
175+
+ "300102030405060708090a0b0c0d0e0f");
176+
byte[] expanded = Base64.decode("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");
177+
178+
kpGen512.initialize(MLKEMParameterSpec.ml_kem_512, new FixedSecureRandom(seed));
179+
KeyPair kp512 = kpGen512.generateKeyPair();
180+
181+
PrivateKeyInfo privInfo = PrivateKeyInfo.getInstance(((MLKEMPrivateKey)kp512.getPrivate()).getPrivateKey(false).getEncoded());
182+
183+
ASN1OctetString k = ASN1OctetString.getInstance(privInfo.parsePrivateKey());
184+
185+
assertTrue(Arrays.areEqual(k.getOctets(), expanded));
186+
}
187+
168188
public void testPrivateKeyRecoding()
169189
throws Exception
170190
{

0 commit comments

Comments
 (0)