blob: b2d04a4e88861690970c4c73a361930c7a092c43 [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.nestlabs.weave.security;
/**
* Java implementation of the HKDF key derivation function, as defined in RFC-5869.
*/
public class HKDF {
public void beginExtractKey() {
beginExtractKey(null);
}
public void beginExtractKey(byte[] salt) {
if (mState != State.INIT) {
throw new IllegalStateException();
}
// NOTE: The check for length==0 works-around a bug in Java's HMAC implementation
// which disallows zero-length keys, even though this is allowed by the HMAC spec.
if (salt == null || salt.length == 0) {
salt = new byte[] { 0 };
}
initMac(salt);
mState = State.EXTRACT;
}
public void addKeyMaterial(byte[] keyMaterial) {
if (mState != State.EXTRACT) {
throw new IllegalStateException();
}
mMac.update(keyMaterial, 0, keyMaterial.length);
}
public void finishExtractKey() {
if (mState != State.EXTRACT) {
throw new IllegalStateException();
}
mPseudoRandomKey = mMac.doFinal();
mState = State.EXPAND;
}
public byte[] expandKey(int requestedKeyLen, byte[] info) {
if (mState != State.EXPAND) {
throw new IllegalStateException();
}
final int macLen = mMac.getMacLength();
if (requestedKeyLen < 1 || requestedKeyLen > 255 * macLen) {
throw new IllegalArgumentException("requestedKeyLen too large");
}
byte[] generatedKey = new byte[requestedKeyLen];
for (int generatedKeyLen = 0; generatedKeyLen < requestedKeyLen; generatedKeyLen += macLen) {
initMac(mPseudoRandomKey);
if (generatedKeyLen > 0) {
mMac.update(generatedKey, generatedKeyLen - macLen, macLen);
}
if (info != null) {
mMac.update(info);
}
mMac.update((byte) ((generatedKeyLen / macLen) + 1));
byte[] newKeyData = mMac.doFinal();
System.arraycopy(newKeyData, 0, generatedKey, generatedKeyLen, Math.min(requestedKeyLen - generatedKeyLen, macLen));
}
return generatedKey;
}
public void reset() {
mMac.reset();
mPseudoRandomKey = null;
mState = State.INIT;
}
public byte[] pseudoRandomKey() {
return mPseudoRandomKey;
}
public static HKDF getInstance(String algorithm) throws java.security.NoSuchAlgorithmException {
if (algorithm.equalsIgnoreCase("HKDFSHA1"))
return new HKDF(javax.crypto.Mac.getInstance("HmacSHA1"));
if (algorithm.equalsIgnoreCase("HKDFSHA256"))
return new HKDF(javax.crypto.Mac.getInstance("HmacSHA256"));
throw new java.security.NoSuchAlgorithmException("Unknown HKDF algorithm: " + algorithm);
}
// ===== Private Members =====
private enum State {
INIT,
EXTRACT,
EXPAND,
};
private State mState;
private javax.crypto.Mac mMac;
private byte[] mPseudoRandomKey;
private HKDF(javax.crypto.Mac mac) {
mState = State.INIT;
mMac = mac;
}
private void initMac(byte[] key) {
try {
mMac.init(new javax.crypto.spec.SecretKeySpec(key, mMac.getAlgorithm()));
}
catch (java.security.InvalidKeyException ex) {
throw new IllegalArgumentException("Invalid MAC algorithm supplied for HKDF");
}
}
}