Skip to content

Commit 18377b6

Browse files
Remove use of BouncyCastle for HMAC key derivation (#130)
* Remove use of BouncyCastle for HMAC key derivation *Issue #, if available:* #41 *Description of changes:* Removes explicit use of BouncyCastle for deriving an HMAC in `CryptoAlgorithm`. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files.
1 parent 4ebbbcc commit 18377b6

File tree

3 files changed

+385
-14
lines changed

3 files changed

+385
-14
lines changed

src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java

+14-14
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import java.nio.ByteBuffer;
1717
import java.nio.ByteOrder;
1818
import java.security.InvalidKeyException;
19-
import java.security.Security;
19+
import java.security.NoSuchAlgorithmException;
2020
import java.util.EnumSet;
2121
import java.util.HashMap;
2222
import java.util.Map;
@@ -25,11 +25,7 @@
2525
import javax.crypto.spec.SecretKeySpec;
2626

2727
import com.amazonaws.encryptionsdk.internal.BouncyCastleConfiguration;
28-
import org.bouncycastle.crypto.Digest;
29-
import org.bouncycastle.crypto.digests.SHA256Digest;
30-
import org.bouncycastle.crypto.digests.SHA384Digest;
31-
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
32-
import org.bouncycastle.crypto.params.HKDFParameters;
28+
import com.amazonaws.encryptionsdk.internal.HmacKeyDerivationFunction;
3329

3430
import com.amazonaws.encryptionsdk.internal.Constants;
3531
import com.amazonaws.encryptionsdk.model.CiphertextHeaders;
@@ -267,7 +263,7 @@ public SecretKey getEncryptionKeyFromDataKey(final SecretKey dataKey, final Ciph
267263
+ dataKey.getAlgorithm());
268264
}
269265

270-
final Digest dgst;
266+
final String macAlgorithm;
271267

272268
switch (this) {
273269
case ALG_AES_128_GCM_IV12_TAG16_NO_KDF:
@@ -278,11 +274,11 @@ public SecretKey getEncryptionKeyFromDataKey(final SecretKey dataKey, final Ciph
278274
case ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA256:
279275
case ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256:
280276
case ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256:
281-
dgst = new SHA256Digest();
277+
macAlgorithm = "HmacSHA256";
282278
break;
283279
case ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384:
284280
case ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384:
285-
dgst = new SHA384Digest();
281+
macAlgorithm = "HmacSHA384";
286282
break;
287283
default:
288284
throw new UnsupportedOperationException("Support for " + this + " not yet built.");
@@ -304,10 +300,14 @@ public SecretKey getEncryptionKeyFromDataKey(final SecretKey dataKey, final Ciph
304300
+ rawDataKey.length);
305301
}
306302

307-
final byte[] rawEncKey = new byte[getKeyLength()];
308-
final HKDFBytesGenerator hkdf = new HKDFBytesGenerator(dgst);
309-
hkdf.init(new HKDFParameters(rawDataKey, null, info.array()));
310-
hkdf.generateBytes(rawEncKey, 0, getKeyLength());
311-
return new SecretKeySpec(rawEncKey, getKeyAlgo());
303+
final HmacKeyDerivationFunction hkdf;
304+
try {
305+
hkdf = HmacKeyDerivationFunction.getInstance(macAlgorithm);
306+
} catch (NoSuchAlgorithmException e) {
307+
throw new IllegalStateException(e);
308+
}
309+
310+
hkdf.init(rawDataKey);
311+
return new SecretKeySpec(hkdf.deriveKey(info.array(), getKeyLength()), getKeyAlgo());
312312
}
313313
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package com.amazonaws.encryptionsdk.internal;
16+
17+
import java.security.GeneralSecurityException;
18+
import java.security.InvalidKeyException;
19+
import java.security.NoSuchAlgorithmException;
20+
import java.security.Provider;
21+
import java.util.Arrays;
22+
23+
import javax.crypto.Mac;
24+
import javax.crypto.SecretKey;
25+
import javax.crypto.spec.SecretKeySpec;
26+
27+
import static org.apache.commons.lang3.Validate.isTrue;
28+
29+
/**
30+
* HMAC-based Key Derivation Function.
31+
* Adapted from Hkdf.java in aws-dynamodb-encryption-java
32+
*
33+
* @see <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>
34+
*/
35+
public final class HmacKeyDerivationFunction {
36+
private static final byte[] EMPTY_ARRAY = new byte[0];
37+
private final String algorithm;
38+
private final Provider provider;
39+
private SecretKey prk = null;
40+
41+
/**
42+
* Returns an <code>HmacKeyDerivationFunction</code> object using the specified algorithm.
43+
*
44+
* @param algorithm the standard name of the requested MAC algorithm. See the Mac
45+
* section in the <a href=
46+
* "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Mac"
47+
* > Java Cryptography Architecture Standard Algorithm Name
48+
* Documentation</a> for information about standard algorithm
49+
* names.
50+
* @return the new <code>Hkdf</code> object
51+
* @throws NoSuchAlgorithmException if no Provider supports a MacSpi implementation for the
52+
* specified algorithm.
53+
*/
54+
public static HmacKeyDerivationFunction getInstance(final String algorithm)
55+
throws NoSuchAlgorithmException {
56+
// Constructed specifically to sanity-test arguments.
57+
Mac mac = Mac.getInstance(algorithm);
58+
return new HmacKeyDerivationFunction(algorithm, mac.getProvider());
59+
}
60+
61+
/**
62+
* Initializes this Hkdf with input keying material. A default salt of
63+
* HashLen zeros will be used (where HashLen is the length of the return
64+
* value of the supplied algorithm).
65+
*
66+
* @param ikm the Input Keying Material
67+
*/
68+
public void init(final byte[] ikm) {
69+
init(ikm, null);
70+
}
71+
72+
/**
73+
* Initializes this Hkdf with input keying material and a salt. If <code>
74+
* salt</code> is <code>null</code> or of length 0, then a default salt of
75+
* HashLen zeros will be used (where HashLen is the length of the return
76+
* value of the supplied algorithm).
77+
*
78+
* @param salt the salt used for key extraction (optional)
79+
* @param ikm the Input Keying Material
80+
*/
81+
public void init(final byte[] ikm, final byte[] salt) {
82+
byte[] realSalt = (salt == null) ? EMPTY_ARRAY : salt.clone();
83+
byte[] rawKeyMaterial = EMPTY_ARRAY;
84+
try {
85+
Mac extractionMac = Mac.getInstance(algorithm, provider);
86+
if (realSalt.length == 0) {
87+
realSalt = new byte[extractionMac.getMacLength()];
88+
Arrays.fill(realSalt, (byte) 0);
89+
}
90+
extractionMac.init(new SecretKeySpec(realSalt, algorithm));
91+
rawKeyMaterial = extractionMac.doFinal(ikm);
92+
this.prk = new SecretKeySpec(rawKeyMaterial, algorithm);
93+
} catch (GeneralSecurityException e) {
94+
// We've already checked all of the parameters so no exceptions
95+
// should be possible here.
96+
throw new RuntimeException("Unexpected exception", e);
97+
} finally {
98+
Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array
99+
}
100+
}
101+
102+
private HmacKeyDerivationFunction(final String algorithm, final Provider provider) {
103+
isTrue(algorithm.startsWith("Hmac"), "Invalid algorithm " + algorithm
104+
+ ". Hkdf may only be used with Hmac algorithms.");
105+
this.algorithm = algorithm;
106+
this.provider = provider;
107+
}
108+
109+
/**
110+
* Returns a pseudorandom key of <code>length</code> bytes.
111+
*
112+
* @param info optional context and application specific information (can be
113+
* a zero-length array).
114+
* @param length the length of the output key in bytes
115+
* @return a pseudorandom key of <code>length</code> bytes.
116+
* @throws IllegalStateException if this object has not been initialized
117+
*/
118+
public byte[] deriveKey(final byte[] info, final int length) throws IllegalStateException {
119+
isTrue(length >= 0, "Length must be a non-negative value.");
120+
assertInitialized();
121+
final byte[] result = new byte[length];
122+
Mac mac = createMac();
123+
124+
isTrue(length <= 255 * mac.getMacLength(),
125+
"Requested keys may not be longer than 255 times the underlying HMAC length.");
126+
127+
byte[] t = EMPTY_ARRAY;
128+
try {
129+
int loc = 0;
130+
byte i = 1;
131+
while (loc < length) {
132+
mac.update(t);
133+
mac.update(info);
134+
mac.update(i);
135+
t = mac.doFinal();
136+
137+
for (int x = 0; x < t.length && loc < length; x++, loc++) {
138+
result[loc] = t[x];
139+
}
140+
141+
i++;
142+
}
143+
} finally {
144+
Arrays.fill(t, (byte) 0); // Zeroize temporary array
145+
}
146+
return result;
147+
}
148+
149+
private Mac createMac() {
150+
try {
151+
Mac mac = Mac.getInstance(algorithm, provider);
152+
mac.init(prk);
153+
return mac;
154+
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
155+
// We've already validated that this algorithm/key is correct.
156+
throw new RuntimeException(ex);
157+
}
158+
}
159+
160+
/**
161+
* Throws an <code>IllegalStateException</code> if this object has not been
162+
* initialized.
163+
*
164+
* @throws IllegalStateException if this object has not been initialized
165+
*/
166+
private void assertInitialized() throws IllegalStateException {
167+
if (prk == null) {
168+
throw new IllegalStateException("Hkdf has not been initialized");
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)