107 lines
3.4 KiB
Java
107 lines
3.4 KiB
Java
/**
|
|
* Copyright (C) 2013 Open Whisper Systems
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.whispersystems.libaxolotl.kdf;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
|
public class HKDF {
|
|
|
|
private static final int HASH_OUTPUT_SIZE = 32;
|
|
private static final int KEY_MATERIAL_SIZE = 64;
|
|
|
|
private static final int CIPHER_KEYS_OFFSET = 0;
|
|
private static final int MAC_KEYS_OFFSET = 32;
|
|
|
|
public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] info) {
|
|
byte[] salt = new byte[HASH_OUTPUT_SIZE];
|
|
return deriveSecrets(inputKeyMaterial, salt, info);
|
|
}
|
|
|
|
public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info) {
|
|
byte[] prk = extract(salt, inputKeyMaterial);
|
|
byte[] okm = expand(prk, info, KEY_MATERIAL_SIZE);
|
|
|
|
SecretKeySpec cipherKey = deriveCipherKey(okm);
|
|
SecretKeySpec macKey = deriveMacKey(okm);
|
|
|
|
return new DerivedSecrets(cipherKey, macKey);
|
|
}
|
|
|
|
private SecretKeySpec deriveCipherKey(byte[] okm) {
|
|
byte[] cipherKey = new byte[32];
|
|
System.arraycopy(okm, CIPHER_KEYS_OFFSET, cipherKey, 0, cipherKey.length);
|
|
return new SecretKeySpec(cipherKey, "AES");
|
|
}
|
|
|
|
private SecretKeySpec deriveMacKey(byte[] okm) {
|
|
byte[] macKey = new byte[32];
|
|
System.arraycopy(okm, MAC_KEYS_OFFSET, macKey, 0, macKey.length);
|
|
return new SecretKeySpec(macKey, "HmacSHA256");
|
|
}
|
|
|
|
private byte[] extract(byte[] salt, byte[] inputKeyMaterial) {
|
|
try {
|
|
Mac mac = Mac.getInstance("HmacSHA256");
|
|
mac.init(new SecretKeySpec(salt, "HmacSHA256"));
|
|
return mac.doFinal(inputKeyMaterial);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError(e);
|
|
} catch (InvalidKeyException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
private byte[] expand(byte[] prk, byte[] info, int outputSize) {
|
|
try {
|
|
int iterations = (int)Math.ceil((double)outputSize/(double)HASH_OUTPUT_SIZE);
|
|
byte[] mixin = new byte[0];
|
|
ByteArrayOutputStream results = new ByteArrayOutputStream();
|
|
|
|
for (int i=0;i<iterations;i++) {
|
|
Mac mac = Mac.getInstance("HmacSHA256");
|
|
mac.init(new SecretKeySpec(prk, "HmacSHA256"));
|
|
|
|
mac.update(mixin);
|
|
if (info != null) {
|
|
mac.update(info);
|
|
}
|
|
mac.update((byte)i);
|
|
|
|
byte[] stepResult = mac.doFinal();
|
|
results.write(stepResult, 0, stepResult.length);
|
|
|
|
mixin = stepResult;
|
|
}
|
|
|
|
return results.toByteArray();
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError(e);
|
|
} catch (InvalidKeyException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|