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 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;

import static org.apache.commons.lang3.Validate.isTrue;

/**
 * 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