All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.encryptionsdk.internal.HmacKeyDerivationFunction Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
/*
 * 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");
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy