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