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