com.amazonaws.encryptionsdk.internal.HmacKeyDerivationFunction Maven / Gradle / Ivy
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.encryptionsdk.internal;
import static org.apache.commons.lang3.Validate.isTrue;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* HMAC-based Key Derivation Function. Adapted from Hkdf.java in aws-dynamodb-encryption-java
*
* @see RFC 5869
*/
public final class HmacKeyDerivationFunction {
private static final byte[] EMPTY_ARRAY = new byte[0];
private final String algorithm;
private final Provider provider;
private SecretKey prk = null;
/**
* Returns an HmacKeyDerivationFunction
object using the specified algorithm.
*
* @param algorithm the standard name of the requested MAC algorithm. See the Mac section in the
*
* Java Cryptography Architecture Standard Algorithm Name Documentation for information
* about standard algorithm names.
* @return the new Hkdf
object
* @throws NoSuchAlgorithmException if no Provider supports a MacSpi implementation for the
* specified algorithm.
*/
public static HmacKeyDerivationFunction getInstance(final String algorithm)
throws NoSuchAlgorithmException {
// Constructed specifically to sanity-test arguments.
Mac mac = Mac.getInstance(algorithm);
return new HmacKeyDerivationFunction(algorithm, mac.getProvider());
}
/**
* Initializes this Hkdf with input keying material. A default salt of HashLen zeros will be used
* (where HashLen is the length of the return value of the supplied algorithm).
*
* @param ikm the Input Keying Material
*/
public void init(final byte[] ikm) {
init(ikm, null);
}
/**
* Initializes this Hkdf with input keying material and a salt. If
* salt
is null
or of length 0, then a default salt of HashLen zeros will be
* used (where HashLen is the length of the return value of the supplied algorithm).
*
* @param salt the salt used for key extraction (optional)
* @param ikm the Input Keying Material
*/
public void init(final byte[] ikm, final byte[] salt) {
byte[] realSalt = (salt == null) ? EMPTY_ARRAY : salt.clone();
byte[] rawKeyMaterial = EMPTY_ARRAY;
try {
Mac extractionMac = Mac.getInstance(algorithm, provider);
if (realSalt.length == 0) {
realSalt = new byte[extractionMac.getMacLength()];
Arrays.fill(realSalt, (byte) 0);
}
extractionMac.init(new SecretKeySpec(realSalt, algorithm));
rawKeyMaterial = extractionMac.doFinal(ikm);
this.prk = new SecretKeySpec(rawKeyMaterial, algorithm);
} catch (GeneralSecurityException e) {
// We've already checked all of the parameters so no exceptions
// should be possible here.
throw new RuntimeException("Unexpected exception", e);
} finally {
Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array
}
}
private HmacKeyDerivationFunction(final String algorithm, final Provider provider) {
isTrue(
algorithm.startsWith("Hmac"),
"Invalid algorithm " + algorithm + ". Hkdf may only be used with Hmac algorithms.");
this.algorithm = algorithm;
this.provider = provider;
}
/**
* Returns a pseudorandom key of length
bytes.
*
* @param info optional context and application specific information (can be a zero-length array).
* @param length the length of the output key in bytes
* @return a pseudorandom key of length
bytes.
* @throws IllegalStateException if this object has not been initialized
*/
public byte[] deriveKey(final byte[] info, final int length) throws IllegalStateException {
isTrue(length >= 0, "Length must be a non-negative value.");
assertInitialized();
final byte[] result = new byte[length];
Mac mac = createMac();
isTrue(
length <= 255 * mac.getMacLength(),
"Requested keys may not be longer than 255 times the underlying HMAC length.");
byte[] t = EMPTY_ARRAY;
try {
int loc = 0;
byte i = 1;
while (loc < length) {
mac.update(t);
mac.update(info);
mac.update(i);
t = mac.doFinal();
for (int x = 0; x < t.length && loc < length; x++, loc++) {
result[loc] = t[x];
}
i++;
}
} finally {
Arrays.fill(t, (byte) 0); // Zeroize temporary array
}
return result;
}
private Mac createMac() {
try {
Mac mac = Mac.getInstance(algorithm, provider);
mac.init(prk);
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
// We've already validated that this algorithm/key is correct.
throw new RuntimeException(ex);
}
}
/**
* Throws an IllegalStateException
if this object has not been initialized.
*
* @throws IllegalStateException if this object has not been initialized
*/
private void assertInitialized() throws IllegalStateException {
if (prk == null) {
throw new IllegalStateException("Hkdf has not been initialized");
}
}
}