Skip to content

Commit 2c726a9

Browse files
author
royb
committed
Updated HKDF to handle list of ikms/salts (relates to github #2209)
1 parent bdc9703 commit 2c726a9

File tree

2 files changed

+106
-8
lines changed
  • prov/src
    • main/jdk25/org/bouncycastle/jcajce/provider/kdf/hkdf
    • test/jdk25/org/bouncycastle/jcajce/provider/kdf/test

2 files changed

+106
-8
lines changed

prov/src/main/jdk25/org/bouncycastle/jcajce/provider/kdf/hkdf/HKDFSpi.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.security.NoSuchAlgorithmException;
1717
import java.security.spec.AlgorithmParameterSpec;
1818
import java.util.List;
19+
import org.bouncycastle.util.Arrays;
1920

2021
class HKDFSpi
2122
extends KDFSpi
@@ -67,7 +68,6 @@ protected byte[] engineDeriveData(AlgorithmParameterSpec derivationSpec)
6768
throw new InvalidAlgorithmParameterException("Invalid AlgorithmParameterSpec provided");
6869
}
6970

70-
// TODO: deal with the multi ikm/salt thing
7171
HKDFParameters hkdfParameters = null;
7272
int derivedDataLength = 0;
7373
if (derivationSpec instanceof HKDFParameterSpec.ExtractThenExpand)
@@ -77,7 +77,10 @@ protected byte[] engineDeriveData(AlgorithmParameterSpec derivationSpec)
7777
List<SecretKey> ikms = spec.ikms();
7878
List<SecretKey> salts = spec.salts();
7979

80-
hkdfParameters = new HKDFParameters(ikms.get(0).getEncoded(), salts.get(0).getEncoded(), spec.info());
80+
byte[] salt = flattenSecretKeys(salts);
81+
byte[] ikm = flattenSecretKeys(ikms);
82+
83+
hkdfParameters = new HKDFParameters(ikm, salt, spec.info());
8184
derivedDataLength = spec.length();
8285

8386
hkdf.init(hkdfParameters);
@@ -94,7 +97,10 @@ else if (derivationSpec instanceof HKDFParameterSpec.Extract)
9497
List<SecretKey> ikms = spec.ikms();
9598
List<SecretKey> salts = spec.salts();
9699

97-
return hkdf.extractPRK(salts.get(0).getEncoded(), ikms.get(0).getEncoded());
100+
byte[] salt = flattenSecretKeys(salts);
101+
byte[] ikm = flattenSecretKeys(ikms);
102+
103+
return hkdf.extractPRK(salt, ikm);
98104
}
99105
else if (derivationSpec instanceof org.bouncycastle.jcajce.spec.HKDFParameterSpec)
100106
{
@@ -126,6 +132,24 @@ private static KDFParameters requireNull(KDFParameters kdfParameters,
126132
return null;
127133
}
128134

135+
private byte[] flattenSecretKeys(List<SecretKey> keys)
136+
{
137+
int len = 0;
138+
int off = 0;
139+
for (SecretKey key: keys)
140+
{
141+
len += key.getEncoded().length;
142+
}
143+
byte[] res = new byte[len];
144+
for (SecretKey key: keys)
145+
{
146+
byte[] encoded = key.getEncoded();
147+
System.arraycopy(encoded, 0, res, off, encoded.length);
148+
off += encoded.length;
149+
}
150+
return res;
151+
}
152+
129153
public static class HKDFwithSHA256 extends HKDFSpi
130154
{
131155
public HKDFwithSHA256(KDFParameters kdfParameters) throws InvalidAlgorithmParameterException

prov/src/test/jdk25/org/bouncycastle/jcajce/provider/kdf/test/HKDFTest.java

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void testKDF()
4646
byte[] okm = Hex.decode("2124ffb29fac4e0fbbc7d5d87492bff3");
4747
byte[] genOkm;
4848
HKDFParameterSpec.ExtractThenExpand hkdfParams1 = HKDFParameterSpec.ofExtract().addIKM(ikm)
49-
.addSalt(salt).thenExpand(info, okm.length);
49+
.addSalt(salt).thenExpand(info, okm.length);
5050

5151
genOkm = kdfHkdf.deriveData(hkdfParams1);
5252

@@ -77,12 +77,13 @@ public void testKDF()
7777
//kdf.init(new KDFParameter(new SHA1Digest()));
7878
//kdf.deriveData(hkdfParams);
7979
}
80+
8081
private boolean doComparison(String algorithm, byte[] ikm, byte[] salt, byte[] info)
8182
throws Exception
8283
{
8384
KDF kdf = KDF.getInstance(algorithm, "BC");
8485
HKDFParameterSpec.ExtractThenExpand spec = HKDFParameterSpec.ofExtract().addIKM(ikm)
85-
.addSalt(salt).thenExpand(info, ikm.length);
86+
.addSalt(salt).thenExpand(info, ikm.length);
8687

8788
org.bouncycastle.jcajce.spec.HKDFParameterSpec pre25spec = new org.bouncycastle.jcajce.spec.HKDFParameterSpec(ikm, salt, info, ikm.length);
8889
byte[] kdfSecret = kdf.deriveData(spec);
@@ -97,7 +98,7 @@ public void testSecretKeyFactoryComparison()
9798
throws Exception
9899
{
99100
setUp();
100-
String[] algorithms = new String[] {
101+
String[] algorithms = new String[]{
101102
"HKDF-SHA256",
102103
"HKDF-SHA384",
103104
"HKDF-SHA512",
@@ -106,12 +107,12 @@ public void testSecretKeyFactoryComparison()
106107
byte[] salt = new byte[16];
107108
byte[] info = new byte[16];
108109
SecureRandom random = new SecureRandom();
109-
for (String algorithm: algorithms)
110+
for (String algorithm : algorithms)
110111
{
111112
random.nextBytes(ikm);
112113
random.nextBytes(salt);
113114
random.nextBytes(info);
114-
if(!doComparison(algorithm, ikm, salt, info))
115+
if (!doComparison(algorithm, ikm, salt, info))
115116
{
116117
fail("failed to generate same secret using kdf and secret key factory for: " + algorithm);
117118
}
@@ -183,6 +184,79 @@ public void testExceptionHandling()
183184
// }
184185
}
185186

187+
public void testExtractWithConcatenatedIKMAndSalts()
188+
throws Exception
189+
{
190+
setUp();
191+
KDF kdfHkdf = KDF.getInstance("HKDF-SHA256", "BC");
192+
193+
byte[][] ikms = new byte[][]
194+
{
195+
Hex.decode("000102030405060708090a0b0c0d0e0f"),
196+
Hex.decode("101112131415161718191a1b1c1d1e1f"),
197+
Hex.decode("202122232425262728292a2b2c2d2e2f"),
198+
Hex.decode("303132333435363738393a3b3c3d3e3f"),
199+
Hex.decode("404142434445464748494a4b4c4d4e4f"),
200+
};
201+
202+
byte[][] salts = new byte[][]
203+
{
204+
Hex.decode("606162636465666768696a6b6c6d6e6f"),
205+
Hex.decode("707172737475767778797a7b7c7d7e7f"),
206+
Hex.decode("808182838485868788898a8b8c8d8e8f"),
207+
Hex.decode("909192939495969798999a9b9c9d9e9f"),
208+
Hex.decode("a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"),
209+
};
210+
byte[] info = Hex.decode("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
211+
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
212+
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
213+
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
214+
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
215+
byte[] okm = Hex.decode(
216+
"b11e398dc80327a1c8e7f78c596a4934" +
217+
"4f012eda2d4efad8a050cc4c19afa97c" +
218+
"59045a99cac7827271cb41c65e590e09" +
219+
"da3275600c2f09b8367793a9aca3db71" +
220+
"cc30c58179ec3e87c14c01d5c1f3434f" +
221+
"1d87");
222+
223+
HKDFParameterSpec.ExtractThenExpand hkdfParams1 = HKDFParameterSpec.ofExtract()
224+
.addIKM(ikms[0]).addIKM(ikms[1]).addIKM(ikms[2]).addIKM(ikms[3]).addIKM(ikms[4])
225+
.addSalt(salts[0]).addSalt(salts[1]).addSalt(salts[2]).addSalt(salts[3]).addSalt(salts[4])
226+
.thenExpand(info, okm.length);
227+
228+
byte[] genOkm = kdfHkdf.deriveData(hkdfParams1);
229+
230+
if (!areEqual(genOkm, okm))
231+
{
232+
fail("HKDF failed for multiple ikms/salts");
233+
}
234+
235+
HKDFParameterSpec.Extract hkdfParams2 = HKDFParameterSpec.ofExtract()
236+
.addIKM(ikms[0]).addIKM(ikms[1]).addIKM(ikms[2]).addIKM(ikms[3]).addIKM(ikms[4])
237+
.addSalt(salts[0]).addSalt(salts[1]).addSalt(salts[2]).addSalt(salts[3]).addSalt(salts[4])
238+
.extractOnly();
239+
240+
okm = Hex.decode("06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244");
241+
genOkm = kdfHkdf.deriveData(hkdfParams2);
242+
243+
if (!areEqual(genOkm, okm))
244+
{
245+
fail("HKDF failed for multiple ikms/salts");
246+
}
247+
}
248+
249+
/**
250+
* Helper method to concatenate two byte arrays.
251+
*/
252+
private byte[] concatenate(byte[] first, byte[] second)
253+
{
254+
byte[] result = new byte[first.length + second.length];
255+
System.arraycopy(first, 0, result, 0, first.length);
256+
System.arraycopy(second, 0, result, first.length, second.length);
257+
return result;
258+
}
259+
186260
private static class InvalidKDFParameters
187261
implements KDFParameters
188262
{

0 commit comments

Comments
 (0)