Skip to content

Commit 451ca8b

Browse files
committed
Merge branch 'main' of gitlab.cryptoworkshop.com:root/bc-java
2 parents d57ff69 + 75de1b6 commit 451ca8b

17 files changed

Lines changed: 413 additions & 67 deletions

File tree

‎core/src/main/java/org/bouncycastle/crypto/agreement/kdf/ConcatenationKDFGenerator.java‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ public int generateBytes(
101101

102102
digest.update(C, 0, C.length);
103103
digest.update(shared, 0, shared.length);
104-
digest.update(otherInfo, 0, otherInfo.length);
104+
105+
if (otherInfo != null)
106+
{
107+
digest.update(otherInfo, 0, otherInfo.length);
108+
}
105109

106110
digest.doFinal(hashBuf, 0);
107111

@@ -117,7 +121,11 @@ public int generateBytes(
117121

118122
digest.update(C, 0, C.length);
119123
digest.update(shared, 0, shared.length);
120-
digest.update(otherInfo, 0, otherInfo.length);
124+
125+
if (otherInfo != null)
126+
{
127+
digest.update(otherInfo, 0, otherInfo.length);
128+
}
121129

122130
digest.doFinal(hashBuf, 0);
123131

‎core/src/main/java/org/bouncycastle/crypto/params/KDFParameters.java‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,59 @@
33
import org.bouncycastle.crypto.DerivationParameters;
44

55
/**
6-
* parameters for Key derivation functions for IEEE P1363a
6+
* Parameters for the key derivation functions that derive keying material from a shared secret and
7+
* an optional block of additional input: KDF1 and KDF2 of IEEE P1363a / ISO 18033-2 (KDF2 being the
8+
* ANSI X9.63 KDF, where the additional input is the SharedInfo), and the NIST SP 800-56A concatenation
9+
* KDF (where it is the OtherInfo).
10+
* <p>
11+
* For historical reasons the additional input is referred to as the "IV".
12+
* </p>
713
*/
814
public class KDFParameters
915
implements DerivationParameters
1016
{
1117
byte[] iv;
1218
byte[] shared;
1319

20+
/**
21+
* Base constructor.
22+
*
23+
* @param shared the shared secret the keying material is derived from.
24+
* @param iv the additional input to the key derivation function, or null if there is none.
25+
* @throws NullPointerException if shared is null.
26+
*/
1427
public KDFParameters(
1528
byte[] shared,
1629
byte[] iv)
1730
{
31+
if (shared == null)
32+
{
33+
throw new NullPointerException("'shared' cannot be null");
34+
}
35+
1836
this.shared = shared;
1937
this.iv = iv;
2038
}
2139

40+
/**
41+
* Return the shared secret the keying material is derived from.
42+
*
43+
* @return the shared secret, never null.
44+
*/
2245
public byte[] getSharedSecret()
2346
{
47+
// TODO Consider returning a clone (and cloning in the constructor)
2448
return shared;
2549
}
2650

51+
/**
52+
* Return the additional input to the key derivation function.
53+
*
54+
* @return the additional input, or null if there is none.
55+
*/
2756
public byte[] getIV()
2857
{
58+
// TODO Consider returning a clone (and cloning in the constructor)
2959
return iv;
3060
}
3161
}

‎core/src/test/java/org/bouncycastle/crypto/test/ConcatenationKDFTest.java‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,49 @@ public void performTest()
2929
implSHA256Test();
3030
implSHA512Test();
3131
implKDFPositiveLenTest();
32+
implNullOtherInfoTest();
33+
}
34+
35+
/**
36+
* The OtherInfo is optional in KDFParameters: a null one has to derive what an empty one does,
37+
* H(counter || Z), on both the single-block and multi-block paths.
38+
*/
39+
private void implNullOtherInfoTest()
40+
{
41+
byte[] sharedSecretBytes = Hex.decodeStrict("3f892bd8b84dae64a782a35f6eaa8f00");
42+
ConcatenationKDFGenerator kdf = new ConcatenationKDFGenerator(new SHA256Digest());
43+
44+
for (int len = 1; len <= 3 * 32 + 1; ++len)
45+
{
46+
byte[] withNull = new byte[len];
47+
byte[] withEmpty = new byte[len];
48+
49+
kdf.init(new KDFParameters(sharedSecretBytes, null));
50+
kdf.generateBytes(withNull, 0, len);
51+
52+
kdf.init(new KDFParameters(sharedSecretBytes, new byte[0]));
53+
kdf.generateBytes(withEmpty, 0, len);
54+
55+
if (!Arrays.areEqual(withNull, withEmpty))
56+
{
57+
fail("ConcatenationKDF with null OtherInfo differs from empty OtherInfo for len " + len);
58+
}
59+
}
60+
61+
Digest digest = new SHA256Digest();
62+
byte[] block1 = new byte[digest.getDigestSize()];
63+
digest.update(new byte[]{ 0, 0, 0, 1 }, 0, 4);
64+
digest.update(sharedSecretBytes, 0, sharedSecretBytes.length);
65+
digest.doFinal(block1, 0);
66+
67+
byte[] output = new byte[block1.length];
68+
kdf.init(new KDFParameters(sharedSecretBytes, null));
69+
kdf.generateBytes(output, 0, output.length);
70+
71+
if (!Arrays.areEqual(block1, output))
72+
{
73+
fail("ConcatenationKDF with null OtherInfo is not H(counter || Z)");
74+
}
3275
}
3376

3477
private void implSHA1Test()

‎docs/releasenotes.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ Date: 2026, TBD
6464
- The raw JCA provider bounded the PBKDF2 iteration count taken from an encoding (org.bouncycastle.pbe.max_iteration_count, default 10,000,000) but not the counts of the legacy PBES1 (PKCS#5 scheme 1) and PKCS#12 PBE families beside it. Their AlgorithmParameters (PKCS12PBE and its OID aliases, PBKDF1) accepted any count, narrowing one beyond the int range with intValue() so that 2^32 arrived as 0, and every Cipher, Mac and SecretKeyFactory derivation ran with whatever count it was given - including a count decoded by another provider's AlgorithmParameters, as when javax.crypto.EncryptedPrivateKeyInfo.getKeySpec() decrypts a PKCS#12 PBE-protected key with BC. As these schemes carry the count in unauthenticated parameters and derive before anything can be checked, a supplied blob could hold a derivation for tens of minutes. The parameter parse now rejects a negative, beyond-int or over-limit count, and the derivations reject a negative or over-limit count, under the same property as PBKDF2. The PKCS#12 key store derives through the same code, so a org.bouncycastle.pkcs12.max_it_count raised above 10,000,000 now needs org.bouncycastle.pbe.max_iteration_count raised with it.
6565
- The ML-DSA public key constructor taking rho and t1 separately checked only for null, so an ML-DSA SubjectPublicKeyInfo in the SEQUENCE { OCTET STRING rho, OCTET STRING t1 } form with a t1 of the wrong length was accepted by PublicKeyFactory and the JCA KeyFactory, unlike the same key in the standard raw encoding. The component constructors for ML-DSA private keys (rho, K, tr, s1, s2, t0, t1, seed) and SLH-DSA private keys (skSeed, prf, pkSeed, pkRoot) likewise accepted components of any length. All three now reject a component whose length does not match the parameter set with an IllegalArgumentException.
6666
- The light-weight CryptoProWrapEngine (RFC 4357 sec. 6.3) diversified the key encryption key in the caller's own array, so after init the KeyParameter it was given held the diversified key, and initialising again with the same parameters - to unwrap what had just been wrapped, say - diversified it a second time and used a different key. It also failed with a NullPointerException when given no S-box, although init has a branch for that case. It now diversifies a copy, and given no S-box uses the GOST 28147 engine's default S-box for the diversification, the one the wrap itself then uses. The provider's GOST 28147 key wrap ciphers were unaffected, as they always supply an S-box and build a new KeyParameter on every init.
67+
- A UserKeyingMaterialSpec carrying a KDF salt was accepted by every KeyAgreement that takes one, although only the HKDF based agreements (X25519withSHA256HKDF, X448withSHA512HKDF and the RFC 8418 XDHwithSHA\*HKDF schemes) use a salt: the X9.63 and concatenation KDF agreements for EC, DH and XDH, and the ECGOST VKO agreements, derived their key without it and gave no sign of having done so. A salt given to any agreement whose KDF is not HKDF is now refused at init with an InvalidAlgorithmParameterException.
68+
- The light-weight ConcatenationKDFGenerator (NIST SP 800-56A concatenation KDF) threw NullPointerException from generateBytes when its KDFParameters carried no OtherInfo, although KDFParameters documents the additional input as optional and KDF1 and KDF2 already treated a null one as empty. Every provider key agreement over it that was given no user keying material hit it: ECCDHwithSHA\*CKDF initialised without a UserKeyingMaterialSpec, and ECCDHUwithSHA\*CKDF, ECMQVwithSHA\*CKDF, DHUwithSHA\*CKDF and MQVwithSHA\*CKDF initialised with a DHUParameterSpec or MQVParameterSpec that carried none. The XDH agreements were unaffected, as they substituted an empty value. A null OtherInfo now derives what an empty one does, H(counter || Z), and KDFParameters refuses a null shared secret at construction rather than leaving it to fail inside the KDF.
69+
- The PKCS12-DEF-AES256-AES128 and PKCS12-DEF-AES256-AES128-GCM KeyStores performed their key protection and MAC operations with the BC provider, rather than through the default JCA provider search that the other PKCS12-DEF stores use, as their implementations were built with the BC helper. They now use the default search, and the PKCS12-AES256-AES128 and PKCS12-AES256-AES128-GCM KeyStores, which had been registered to those same implementations, are now registered to the BC ones.
6770

6871
- The BCPQC MAYO, SDitH, SNOVA and NTRU+ KeyPairGenerators refused every java.security.spec.NamedParameterSpec with InvalidAlgorithmParameterException, whatever parameter set it named and in whatever case: they lower-cased the name and then looked it up in a table of upper-case names. The name is now upper-cased, so NamedParameterSpec("MAYO-1") and NamedParameterSpec("mayo-1") both select MAYO-1, as the other BCPQC families already allowed. The generators' own ParameterSpec classes were never affected.
6972

@@ -86,6 +89,8 @@ Date: 2026, TBD
8689

8790
- The BC provider's ML-DSA, ML-KEM and SLH-DSA keys now implement getParams(), which keys inherit from java.security.AsymmetricKey from Java 22, returning a java.security.spec.NamedParameterSpec naming the parameter set (for example "ML-DSA-65") on Java 11 and later, and null before. The JDK's own ML-DSA and ML-KEM keys return only the family name, "ML-DSA" or "ML-KEM", from getAlgorithm() and report the parameter set this way, so comparing getParams() rather than getAlgorithm() now identifies the parameter set whichever provider made the key. The BCPQC provider's keys do the same for every family with named parameter sets - AIMer, BIKE, FAEST, Falcon, HAETAE, HQC, MAYO, MQOM, NTRU, NTRU+, NTRU LPRime, Streamlined NTRU Prime, QR-UOV, SABER, SDitH, SMAUG-T, SNOVA, SQIsign and UOV - with the name their ParameterSpec reports, which the family's KeyPairGenerator accepts back. getAlgorithm() on BC and BCPQC keys is unchanged (github #2467).
8891

92+
- The provider adds ECDHwithSHA256HKDF, ECDHwithSHA384HKDF and ECDHwithSHA512HKDF KeyAgreement algorithms: ECDH followed by HKDF (RFC 5869), the counterparts for elliptic curve keys of the XDHwithSHA\*HKDF agreements used for the RFC 8418 dhSinglePass-stdDH-hkdf schemes. As with those, a UserKeyingMaterialSpec supplies the HKDF info and, optionally, its salt. They are registered by name only; the RFC 8418 scheme object identifiers remain registered to the XDH agreements.
93+
8994
### 2.1.4 Additional Notes
9095

9196
- The sources and javadoc jars of the Ant-built distributions (jdk14, jdk15to18 and jdk13) no longer carry test material. Each module's javadoc target copies the package documentation it needs - org/bouncycastle/<area>/**/*.html - back into the module source directory that has already been compiled from, and zip-src zips that directory afterwards, so every test package's package.html arrived in the sources jar by that route; javadoc-util additionally copied org/bouncycastle/asn1/isismtt/**/*.java, which put test classes into the bcutil javadoc as generated pages, and javadoc-pg deliberately copied the gpg and bcpg test sources in order to document them. Separately the source copies excluded test material only one directory deep and only for *.java, because Ant reads ** as an any-depth wildcard just where it is a whole path segment, so anything nested further or with another extension - the PEM certificate fixtures under org/bouncycastle/est/test/san corrected in 1.86, and an ICAO master list under org/bouncycastle/asn1/icao/test - went through. The source and javadoc copies of every module now exclude test directories at any depth, and javadoc-pg no longer documents the test packages. org.bouncycastle.util.test is unaffected and still ships in the bcprov binary, sources and javadoc jars, as it does from the Gradle build: it is the SimpleTest framework the light-weight API's own test classes are written against, not test material of the distribution. No binary changes - the classes and resources of every Ant-built jar are identical to those of the 1.86 release - and the Gradle-built jdk18on artifacts never carried any of this.

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/EC.java‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ public void configure(ConfigurableProvider provider)
157157
provider.addAlgorithm("KeyAgreement.ECDHWITHSHA512KDF", PREFIX + "KeyAgreementSpi$DHwithSHA512KDFAndSharedInfo", generalEcAttributes);
158158
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA512KDF", PREFIX + "KeyAgreementSpi$CDHwithSHA512KDFAndSharedInfo", generalEcAttributes);
159159

160+
// HKDF (RFC 5869) - by name only: the RFC 8418 dhSinglePass-stdDH-hkdf OIDs are registered to XDH in EdEC
161+
provider.addAlgorithm("KeyAgreement.ECDHWITHSHA256HKDF", PREFIX + "KeyAgreementSpi$DHwithSHA256HKDF", generalEcAttributes);
162+
provider.addAlgorithm("KeyAgreement.ECDHWITHSHA384HKDF", PREFIX + "KeyAgreementSpi$DHwithSHA384HKDF", generalEcAttributes);
163+
provider.addAlgorithm("KeyAgreement.ECDHWITHSHA512HKDF", PREFIX + "KeyAgreementSpi$DHwithSHA512HKDF", generalEcAttributes);
164+
160165
provider.addAlgorithm("KeyAgreement", X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme, PREFIX + "KeyAgreementSpi$DHwithSHA1KDFAndSharedInfo", generalEcAttributes);
161166
provider.addAlgorithm("KeyAgreement", X9ObjectIdentifiers.dhSinglePass_cofactorDH_sha1kdf_scheme, PREFIX + "KeyAgreementSpi$CDHwithSHA1KDFAndSharedInfo", generalEcAttributes);
162167

@@ -172,10 +177,10 @@ public void configure(ConfigurableProvider provider)
172177
provider.addAlgorithm("KeyAgreement", SECObjectIdentifiers.dhSinglePass_stdDH_sha512kdf_scheme, PREFIX + "KeyAgreementSpi$DHwithSHA512KDFAndSharedInfo", generalEcAttributes);
173178
provider.addAlgorithm("KeyAgreement", SECObjectIdentifiers.dhSinglePass_cofactorDH_sha512kdf_scheme, PREFIX + "KeyAgreementSpi$CDHwithSHA512KDFAndSharedInfo", generalEcAttributes);
174179

175-
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA1CKDF", PREFIX + "KeyAgreementSpi$DHwithSHA1CKDF", generalEcAttributes);
176-
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA256CKDF", PREFIX + "KeyAgreementSpi$DHwithSHA256CKDF", generalEcAttributes);
177-
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA384CKDF", PREFIX + "KeyAgreementSpi$DHwithSHA384CKDF", generalEcAttributes);
178-
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA512CKDF", PREFIX + "KeyAgreementSpi$DHwithSHA512CKDF", generalEcAttributes);
180+
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA1CKDF", PREFIX + "KeyAgreementSpi$CDHwithSHA1CKDF", generalEcAttributes);
181+
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA256CKDF", PREFIX + "KeyAgreementSpi$CDHwithSHA256CKDF", generalEcAttributes);
182+
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA384CKDF", PREFIX + "KeyAgreementSpi$CDHwithSHA384CKDF", generalEcAttributes);
183+
provider.addAlgorithm("KeyAgreement.ECCDHWITHSHA512CKDF", PREFIX + "KeyAgreementSpi$CDHwithSHA512CKDF", generalEcAttributes);
179184

180185
provider.addAlgorithm("KeyAgreement.ECCDHUWITHSHA1CKDF", PREFIX + "KeyAgreementSpi$DHUwithSHA1CKDF", generalEcAttributes);
181186
provider.addAlgorithm("KeyAgreement.ECCDHUWITHSHA224CKDF", PREFIX + "KeyAgreementSpi$DHUwithSHA224CKDF", generalEcAttributes);

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/KeyAgreementSpi.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ else if (params instanceof UserKeyingMaterialSpec)
328328
}
329329
this.p = privKey.getParams().getP();
330330
this.g = privKey.getParams().getG();
331+
checkKdfSalt((UserKeyingMaterialSpec)params);
331332
this.dheParameters = null;
332333
this.ukmParameters = ((UserKeyingMaterialSpec)params).getUserKeyingMaterial();
333334
}

0 commit comments

Comments
 (0)