Skip to content

Commit 953ef6b

Browse files
author
royb
committed
Updates to ML-KEM Updated to FIPS203
Added internal functions isolating randomness changed keygen gen matrix to not be transposed (p||i||j -> p||j||i) added domain separation in genKeyPair refactored input order to match specs
1 parent 8a50bb7 commit 953ef6b

File tree

4 files changed

+59
-44
lines changed

4 files changed

+59
-44
lines changed

core/src/main/java/org/bouncycastle/pqc/crypto/crystals/kyber/KyberEngine.java

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,10 @@ public void init(SecureRandom random)
196196
this.random = random;
197197
}
198198

199-
public byte[][] generateKemKeyPair()
199+
//Internal functions are deterministic. No randomness is sampled inside them
200+
public byte[][] generateKemKeyPairInternal(byte[] d, byte[] z)
200201
{
201-
byte[][] indCpaKeyPair = indCpa.generateKeyPair();
202+
byte[][] indCpaKeyPair = indCpa.generateKeyPair(d);
202203

203204
byte[] s = new byte[KyberIndCpaSecretKeyBytes];
204205

@@ -208,41 +209,19 @@ public byte[][] generateKemKeyPair()
208209

209210
symmetric.hash_h(hashedPublicKey, indCpaKeyPair[0], 0);
210211

211-
byte[] z = new byte[KyberSymBytes];
212-
random.nextBytes(z);
213212

214213
byte[] outputPublicKey = new byte[KyberIndCpaPublicKeyBytes];
215214
System.arraycopy(indCpaKeyPair[0], 0, outputPublicKey, 0, KyberIndCpaPublicKeyBytes);
216215
return new byte[][]{ Arrays.copyOfRange(outputPublicKey, 0, outputPublicKey.length - 32), Arrays.copyOfRange(outputPublicKey, outputPublicKey.length - 32, outputPublicKey.length), s, hashedPublicKey, z };
217216
}
218217

219-
public byte[][] kemEncrypt(byte[] publicKeyInput)
218+
public byte[][] kemEncryptInternal(byte[] publicKeyInput, byte[] randBytes)
220219
{
221-
// Input validation (6.2 ML-KEM Encaps)
222-
// Type Check
223-
if (publicKeyInput.length != KyberIndCpaPublicKeyBytes)
224-
{
225-
throw new IllegalArgumentException("Input validation Error: Type check failed for ml-kem encapsulation");
226-
}
227-
// Modulus Check
228-
PolyVec polyVec = new PolyVec(this);
229-
byte[] seed = indCpa.unpackPublicKey(polyVec, publicKeyInput);
230-
byte[] ek = indCpa.packPublicKey(polyVec, seed);
231-
if (!Arrays.areEqual(ek, publicKeyInput))
232-
{
233-
throw new IllegalArgumentException("Input validation: Modulus check failed for ml-kem encapsulation");
234-
}
235-
236-
237220
byte[] outputCipherText;
238221

239222
byte[] buf = new byte[2 * KyberSymBytes];
240223
byte[] kr = new byte[2 * KyberSymBytes];
241224

242-
byte[] randBytes = new byte[KyberSymBytes];
243-
244-
random.nextBytes(randBytes);
245-
246225
System.arraycopy(randBytes, 0, buf, 0, KyberSymBytes);
247226

248227
// SHA3-256 Public Key
@@ -252,33 +231,32 @@ public byte[][] kemEncrypt(byte[] publicKeyInput)
252231
symmetric.hash_g(kr, buf);
253232

254233
// IndCpa Encryption
255-
outputCipherText = indCpa.encrypt(Arrays.copyOfRange(buf, 0, KyberSymBytes), publicKeyInput, Arrays.copyOfRange(kr, 32, kr.length));
234+
outputCipherText = indCpa.encrypt(publicKeyInput, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, 32, kr.length));
256235

257236
byte[] outputSharedSecret = new byte[sessionKeyLength];
258237

259238
System.arraycopy(kr, 0, outputSharedSecret, 0, outputSharedSecret.length);
260-
239+
261240
byte[][] outBuf = new byte[2][];
262241
outBuf[0] = outputSharedSecret;
263242
outBuf[1] = outputCipherText;
264-
265243
return outBuf;
266244
}
267245

268-
public byte[] kemDecrypt(byte[] cipherText, byte[] secretKey)
246+
public byte[] kemDecryptInternal(byte[] secretKey, byte[] cipherText)
269247
{
270248
byte[] buf = new byte[2 * KyberSymBytes],
271-
kr = new byte[2 * KyberSymBytes];
249+
kr = new byte[2 * KyberSymBytes];
272250

273251
byte[] publicKey = Arrays.copyOfRange(secretKey, KyberIndCpaSecretKeyBytes, secretKey.length);
274252

275-
System.arraycopy(indCpa.decrypt(cipherText, secretKey), 0, buf, 0, KyberSymBytes);
253+
System.arraycopy(indCpa.decrypt(secretKey, cipherText), 0, buf, 0, KyberSymBytes);
276254

277255
System.arraycopy(secretKey, KyberSecretKeyBytes - 2 * KyberSymBytes, buf, KyberSymBytes, KyberSymBytes);
278256

279257
symmetric.hash_g(kr, buf);
280258

281-
byte[] cmp = indCpa.encrypt(Arrays.copyOfRange(buf, 0, KyberSymBytes), publicKey, Arrays.copyOfRange(kr, KyberSymBytes, kr.length));
259+
byte[] cmp = indCpa.encrypt(publicKey, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, KyberSymBytes, kr.length));
282260

283261
boolean fail = !(Arrays.constantTimeAreEqual(cipherText, cmp));
284262

@@ -289,6 +267,42 @@ public byte[] kemDecrypt(byte[] cipherText, byte[] secretKey)
289267
return Arrays.copyOfRange(kr, 0, sessionKeyLength);
290268
}
291269

270+
public byte[][] generateKemKeyPair()
271+
{
272+
byte[] d = new byte[KyberSymBytes];
273+
byte[] z = new byte[KyberSymBytes];
274+
random.nextBytes(d);
275+
random.nextBytes(z);
276+
277+
return generateKemKeyPairInternal(d, z);
278+
}
279+
280+
public byte[][] kemEncrypt(byte[] publicKeyInput, byte[] randBytes)
281+
{
282+
//TODO: do input validation elsewhere?
283+
// Input validation (6.2 ML-KEM Encaps)
284+
// Type Check
285+
if (publicKeyInput.length != KyberIndCpaPublicKeyBytes)
286+
{
287+
throw new IllegalArgumentException("Input validation Error: Type check failed for ml-kem encapsulation");
288+
}
289+
// Modulus Check
290+
PolyVec polyVec = new PolyVec(this);
291+
byte[] seed = indCpa.unpackPublicKey(polyVec, publicKeyInput);
292+
byte[] ek = indCpa.packPublicKey(polyVec, seed);
293+
if (!Arrays.areEqual(ek, publicKeyInput))
294+
{
295+
throw new IllegalArgumentException("Input validation: Modulus check failed for ml-kem encapsulation");
296+
}
297+
298+
return kemEncryptInternal(publicKeyInput, randBytes);
299+
}
300+
public byte[] kemDecrypt(byte[] secretKey, byte[] cipherText)
301+
{
302+
//TODO: do input validation
303+
return kemDecryptInternal(secretKey, cipherText);
304+
}
305+
292306
private void cmov(byte[] r, byte[] x, int xlen, boolean b)
293307
{
294308
if (b)

core/src/main/java/org/bouncycastle/pqc/crypto/crystals/kyber/KyberIndCpa.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.bouncycastle.crypto.digests.SHAKEDigest;
44
import org.bouncycastle.util.Arrays;
5+
import org.bouncycastle.util.Pack;
56

67
class KyberIndCpa
78
{
@@ -45,20 +46,16 @@ public KyberIndCpa(KyberEngine engine)
4546
*
4647
* @return KeyPair where each key is represented as bytes
4748
*/
48-
byte[][] generateKeyPair()
49+
byte[][] generateKeyPair(byte[] d)
4950
{
5051
PolyVec secretKey = new PolyVec(engine),
5152
publicKey = new PolyVec(engine),
5253
e = new PolyVec(engine);
5354

54-
byte[] d = new byte[32];
55-
56-
// (p, sigma) <- G(d)
57-
58-
engine.getRandomBytes(d);
55+
// (p, sigma) <- G(d || k)
5956

6057
byte[] buf = new byte[64];
61-
symmetric.hash_g(buf, d);
58+
symmetric.hash_g(buf, Arrays.concatenate(d, Pack.intToLittleEndian(kyberK)));
6259

6360
byte[] publicSeed = new byte[32]; // p in docs
6461
byte[] noiseSeed = new byte[32]; // sigma in docs
@@ -141,7 +138,7 @@ byte[][] generateKeyPair()
141138
return new byte[][]{packPublicKey(publicKey, publicSeed), packSecretKey(secretKey)};
142139
}
143140

144-
public byte[] encrypt(byte[] msg, byte[] publicKeyInput, byte[] coins)
141+
public byte[] encrypt(byte[] publicKeyInput, byte[] msg, byte[] coins)
145142
{
146143
int i;
147144
byte[] seed;
@@ -180,7 +177,7 @@ public byte[] encrypt(byte[] msg, byte[] publicKeyInput, byte[] coins)
180177
aMatrixTranspose[i] = new PolyVec(engine);
181178
}
182179

183-
generateMatrix(aMatrixTranspose, seed, true);
180+
generateMatrix(aMatrixTranspose, seed, false);
184181

185182
// System.out.print("matrix transposed = ");
186183
// for (i = 0; i < kyberK; i++) {
@@ -383,7 +380,7 @@ private static int rejectionSampling(Poly outputBuffer, int coeffOff, int len, b
383380

384381
}
385382

386-
public byte[] decrypt(byte[] cipherText, byte[] secretKey)
383+
public byte[] decrypt(byte[] secretKey, byte[] cipherText)
387384
{
388385
int i;
389386
byte[] outputMessage = new byte[KyberEngine.getKyberIndCpaMsgBytes()];

core/src/main/java/org/bouncycastle/pqc/crypto/crystals/kyber/KyberKEMExtractor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private void initCipher(AsymmetricKeyParameter recipientKey)
2626
public byte[] extractSecret(byte[] encapsulation)
2727
{
2828
// Decryption
29-
byte[] sharedSecret = engine.kemDecrypt(encapsulation, key.getEncoded());
29+
byte[] sharedSecret = engine.kemDecrypt(key.getEncoded(), encapsulation);
3030
return sharedSecret;
3131
}
3232

core/src/main/java/org/bouncycastle/pqc/crypto/crystals/kyber/KyberKEMGenerator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ public SecretWithEncapsulation generateEncapsulated(AsymmetricKeyParameter recip
2323
KyberPublicKeyParameters key = (KyberPublicKeyParameters)recipientKey;
2424
KyberEngine engine = key.getParameters().getEngine();
2525
engine.init(sr);
26-
byte[][] kemEncrypt = engine.kemEncrypt(key.getEncoded());
26+
27+
byte[] randBytes = new byte[32];
28+
engine.getRandomBytes(randBytes);
29+
30+
byte[][] kemEncrypt = engine.kemEncrypt(key.getEncoded(), randBytes);
2731
return new SecretWithEncapsulationImpl(kemEncrypt[0], kemEncrypt[1]);
2832
}
2933
}

0 commit comments

Comments
 (0)