Add HKDF support for new sessions.
parent
dbc070cd65
commit
a03fff8b24
@ -0,0 +1,22 @@
|
||||
package org.whispersystems.textsecure.crypto.kdf;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class DerivedSecrets {
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
|
||||
public DerivedSecrets(SecretKeySpec cipherKey, SecretKeySpec macKey) {
|
||||
this.cipherKey = cipherKey;
|
||||
this.macKey = macKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getCipherKey() {
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return macKey;
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package org.whispersystems.textsecure.crypto.kdf;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class HKDF extends KDF {
|
||||
|
||||
private static final int HASH_OUTPUT_SIZE = 32;
|
||||
private static final int KEY_MATERIAL_SIZE = 72;
|
||||
|
||||
private static final int CIPHER_KEYS_OFFSET = 0;
|
||||
private static final int MAC_KEYS_OFFSET = 32;
|
||||
|
||||
@Override
|
||||
public DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
|
||||
boolean isLowEnd, byte[] info)
|
||||
{
|
||||
byte[] inputKeyMaterial = concatenateSharedSecrets(sharedSecret);
|
||||
byte[] salt = new byte[HASH_OUTPUT_SIZE];
|
||||
byte[] prk = extract(salt, inputKeyMaterial);
|
||||
byte[] okm = expand(prk, info, KEY_MATERIAL_SIZE);
|
||||
|
||||
SecretKeySpec cipherKey = deriveCipherKey(okm, isLowEnd);
|
||||
SecretKeySpec macKey = deriveMacKey(okm, isLowEnd);
|
||||
|
||||
return new DerivedSecrets(cipherKey, macKey);
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveCipherKey(byte[] okm, boolean isLowEnd) {
|
||||
byte[] cipherKey = new byte[16];
|
||||
|
||||
if (isLowEnd) {
|
||||
System.arraycopy(okm, CIPHER_KEYS_OFFSET + 0, cipherKey, 0, cipherKey.length);
|
||||
} else {
|
||||
System.arraycopy(okm, CIPHER_KEYS_OFFSET + 16, cipherKey, 0, cipherKey.length);
|
||||
}
|
||||
|
||||
return new SecretKeySpec(cipherKey, "AES");
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveMacKey(byte[] okm, boolean isLowEnd) {
|
||||
byte[] macKey = new byte[20];
|
||||
|
||||
if (isLowEnd) {
|
||||
System.arraycopy(okm, MAC_KEYS_OFFSET + 0, macKey, 0, macKey.length);
|
||||
} else {
|
||||
System.arraycopy(okm, MAC_KEYS_OFFSET + 20, macKey, 0, macKey.length);
|
||||
}
|
||||
|
||||
return new SecretKeySpec(macKey, "HmacSHA1");
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package org.whispersystems.textsecure.crypto.kdf;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class KDF {
|
||||
|
||||
public abstract DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
|
||||
boolean isLowEnd, byte[] info);
|
||||
|
||||
protected byte[] concatenateSharedSecrets(List<BigInteger> sharedSecrets) {
|
||||
int totalByteSize = 0;
|
||||
List<byte[]> byteValues = new LinkedList<byte[]>();
|
||||
|
||||
for (BigInteger sharedSecret : sharedSecrets) {
|
||||
byte[] byteValue = sharedSecret.toByteArray();
|
||||
totalByteSize += byteValue.length;
|
||||
byteValues.add(byteValue);
|
||||
}
|
||||
|
||||
byte[] combined = new byte[totalByteSize];
|
||||
int offset = 0;
|
||||
|
||||
for (byte[] byteValue : byteValues) {
|
||||
System.arraycopy(byteValue, 0, combined, offset, byteValue.length);
|
||||
offset += byteValue.length;
|
||||
}
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package org.whispersystems.textsecure.crypto.kdf;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class NKDF extends KDF {
|
||||
|
||||
@Override
|
||||
public DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
|
||||
boolean isLowEnd, byte[] info)
|
||||
{
|
||||
SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret);
|
||||
SecretKeySpec macKey = deriveMacSecret(cipherKey);
|
||||
|
||||
return new DerivedSecrets(cipherKey, macKey);
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveCipherSecret(boolean isLowEnd, List<BigInteger> sharedSecret) {
|
||||
byte[] sharedSecretBytes = concatenateSharedSecrets(sharedSecret);
|
||||
byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
|
||||
byte[] cipherSecret = new byte[16];
|
||||
|
||||
if (isLowEnd) {
|
||||
System.arraycopy(derivedBytes, 16, cipherSecret, 0, 16);
|
||||
} else {
|
||||
System.arraycopy(derivedBytes, 0, cipherSecret, 0, 16);
|
||||
}
|
||||
|
||||
return new SecretKeySpec(cipherSecret, "AES");
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveMacSecret(SecretKeySpec key) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
byte[] secret = md.digest(key.getEncoded());
|
||||
|
||||
return new SecretKeySpec(secret, "HmacSHA1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("SHA-1 Not Supported!",e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] deriveBytes(byte[] seed, int bytesNeeded) {
|
||||
MessageDigest md;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.w("NKDF", e);
|
||||
throw new IllegalArgumentException("SHA-256 Not Supported!");
|
||||
}
|
||||
|
||||
int rounds = bytesNeeded / md.getDigestLength();
|
||||
|
||||
for (int i=1;i<=rounds;i++) {
|
||||
byte[] roundBytes = Conversions.intToByteArray(i);
|
||||
md.update(roundBytes);
|
||||
md.update(seed);
|
||||
}
|
||||
|
||||
return md.digest();
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue