Skip to content

Commit ce06f46

Browse files
committed
JCE: fix DH KeyAgreement, pad DH secrets to primeLen with leading zeros and add test
1 parent 6bf12d9 commit ce06f46

File tree

2 files changed

+116
-16
lines changed

2 files changed

+116
-16
lines changed

src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset)
235235
throws IllegalStateException, ShortBufferException {
236236

237237
byte tmp[] = null;
238+
int returnLen = 0;
238239

239240
if (this.state != EngineState.WC_PUBKEY_DONE)
240241
throw new IllegalStateException(
@@ -248,32 +249,56 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset)
248249
switch (this.type) {
249250
case WC_DH:
250251

251-
if ((sharedSecret.length - offset) < this.primeLen) {
252-
throw new ShortBufferException(
253-
"Input buffer too small when generating " +
254-
"shared secret");
255-
}
256-
257252
/* public key has been stored inside this.dh already */
258253
tmp = this.dh.makeSharedSecret();
259254
if (tmp == null) {
260255
throw new RuntimeException("Error when creating DH " +
261256
"shared secret");
262257
}
263258

264-
if ((sharedSecret.length - offset) < tmp.length) {
259+
/* DH shared secrets can vary in length depending on if they
260+
* are padded or not at the beginning with zero bytes to make
261+
* a total output size matching the prime length.
262+
*
263+
* Native wolfCrypt does not prepend zero bytes to DH shared
264+
* secrets, following RFC 5246 (8.1.2) which instructs to
265+
* strip leading zero bytes.
266+
*
267+
* Sun KeyAgreement DH implementations as of after Java 8
268+
* prepend zero bytes if total length is not equal to prime
269+
* length. This was changed with OpenJDK bug fix JDK-7146728.
270+
*
271+
* BouncyCastle also behaves the same way, prepending zero
272+
* bytes if total secret size is not prime length. This
273+
* follows RFC 2631 (2.1.2).
274+
*
275+
* To match Sun and BC behavior, we pad the secret to primeLen
276+
* by prepending zeros for both generateSecret() methods.
277+
*/
278+
byte[] paddedSecret = new byte[this.primeLen];
279+
Arrays.fill(paddedSecret, (byte)0);
280+
System.arraycopy(tmp, 0, paddedSecret,
281+
paddedSecret.length - tmp.length, tmp.length);
282+
283+
if ((sharedSecret.length - offset) < paddedSecret.length) {
265284
zeroArray(tmp);
285+
zeroArray(paddedSecret);
266286
throw new ShortBufferException(
267287
"Output buffer too small when generating " +
268288
"DH shared secret");
269289
}
270290

271-
/* copy array back to output offset */
272-
System.arraycopy(tmp, 0, sharedSecret, offset, tmp.length);
291+
/* copy padded array back to output offset */
292+
System.arraycopy(paddedSecret, 0, sharedSecret, offset,
293+
paddedSecret.length);
294+
295+
returnLen = this.primeLen;
273296

274297
/* reset state, using same private info and alg params */
275298
this.state = EngineState.WC_PRIVKEY_DONE;
276299

300+
zeroArray(paddedSecret);
301+
277302
break;
278303

279304
case WC_ECDH:
@@ -294,6 +319,8 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset)
294319
/* copy array back to output ofset */
295320
System.arraycopy(tmp, 0, sharedSecret, offset, tmp.length);
296321

322+
returnLen = tmp.length;
323+
297324
/* reset state, using same private info and alg params */
298325
byte[] priv = this.ecPrivate.exportPrivate();
299326
if (priv == null) {
@@ -313,15 +340,11 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset)
313340
break;
314341
};
315342

316-
if (tmp != null) {
317-
318-
log("generated secret, len: " + tmp.length);
343+
log("generated secret, len: " + returnLen);
319344

320-
zeroArray(tmp);
321-
return tmp.length;
322-
}
345+
zeroArray(tmp);
323346

324-
return 0;
347+
return returnLen;
325348
}
326349

327350
@Override

src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyAgreementTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,83 @@ public void testECDHGenerateSecretKeyForDES()
10111011
}
10121012
}
10131013

1014+
@Test
1015+
public void testDHKeyAgreementPadding()
1016+
throws NoSuchProviderException, NoSuchAlgorithmException,
1017+
InvalidParameterSpecException, InvalidKeyException,
1018+
InvalidAlgorithmParameterException,
1019+
ShortBufferException {
1020+
1021+
/* This test verifies that DH shared secrets are properly padded to
1022+
* the prime length when using generateSecret(byte[], int). This
1023+
* matches the behavior of SunJCE after Java 8 (JDK-7146728) and
1024+
* prevents regressions related to padding. Both generateSecret()
1025+
* methods pad to primeLen. */
1026+
1027+
/* Skip in FIPS mode. FIPS 186-4 only allows 1024, 2048, and
1028+
* 3072-bit DH parameter generation */
1029+
if (Fips.enabled) {
1030+
return;
1031+
}
1032+
1033+
/* Generate 2048-bit DH parameters */
1034+
AlgorithmParameterGenerator paramGen =
1035+
AlgorithmParameterGenerator.getInstance("DH");
1036+
paramGen.init(2048);
1037+
1038+
AlgorithmParameters params = paramGen.generateParameters();
1039+
DHParameterSpec dhParams =
1040+
(DHParameterSpec)params.getParameterSpec(DHParameterSpec.class);
1041+
1042+
/* Prime length should be 256 bytes for 2048-bit DH */
1043+
int primeLen = dhParams.getP().toByteArray().length;
1044+
if (dhParams.getP().toByteArray()[0] == 0x00) {
1045+
primeLen--;
1046+
}
1047+
1048+
/* Initialize key pair generator */
1049+
KeyPairGenerator keyGen =
1050+
KeyPairGenerator.getInstance("DH", "wolfJCE");
1051+
keyGen.initialize(dhParams, secureRandom);
1052+
1053+
KeyAgreement alice = KeyAgreement.getInstance("DH", "wolfJCE");
1054+
KeyAgreement bob = KeyAgreement.getInstance("DH", "wolfJCE");
1055+
1056+
/* Run multiple iterations to ensure consistent padding behavior */
1057+
for (int i = 0; i < 100; i++) {
1058+
byte[] aliceSecret = new byte[primeLen];
1059+
byte[] bobSecret = new byte[primeLen];
1060+
1061+
/* Fill buffers with different stale data to ensure padding
1062+
* overwrites any existing data */
1063+
Arrays.fill(aliceSecret, (byte)'a');
1064+
Arrays.fill(bobSecret, (byte)'b');
1065+
1066+
/* Generate new key pairs for this iteration */
1067+
KeyPair aliceKeyPair = keyGen.generateKeyPair();
1068+
KeyPair bobKeyPair = keyGen.generateKeyPair();
1069+
1070+
/* Perform key agreement */
1071+
alice.init(aliceKeyPair.getPrivate());
1072+
alice.doPhase(bobKeyPair.getPublic(), true);
1073+
int aliceLen = alice.generateSecret(aliceSecret, 0);
1074+
1075+
bob.init(bobKeyPair.getPrivate());
1076+
bob.doPhase(aliceKeyPair.getPublic(), true);
1077+
int bobLen = bob.generateSecret(bobSecret, 0);
1078+
1079+
/* Both secrets should be exactly primeLen bytes (always padded) */
1080+
assertEquals("Alice's secret length should equal prime length",
1081+
primeLen, aliceLen);
1082+
assertEquals("Bob's secret length should equal prime length",
1083+
primeLen, bobLen);
1084+
1085+
/* Both secrets should be identical, including padding */
1086+
assertArrayEquals("Alice and Bob should generate identical " +
1087+
"secrets at iteration " + i, aliceSecret, bobSecret);
1088+
}
1089+
}
1090+
10141091
@Test
10151092
public void testThreadedKeyAgreement()
10161093
throws InterruptedException, NoSuchAlgorithmException {

0 commit comments

Comments
 (0)