mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-15 20:42:00 +00:00
Hmac-SIV encryption/decryption.
This commit is contained in:
committed by
Greyson Parrelli
parent
3907ec8b51
commit
7d70ea78cd
@@ -8,12 +8,15 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public final class CryptoUtil {
|
||||
|
||||
private CryptoUtil () { }
|
||||
private static final String HMAC_SHA256 = "HmacSHA256";
|
||||
|
||||
public static byte[] computeHmacSha256(byte[] key, byte[] data) {
|
||||
private CryptoUtil() {
|
||||
}
|
||||
|
||||
public static byte[] hmacSha256(byte[] key, byte[] data) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
Mac mac = Mac.getInstance(HMAC_SHA256);
|
||||
mac.init(new SecretKeySpec(key, HMAC_SHA256));
|
||||
return mac.doFinal(data);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.whispersystems.signalservice.api.crypto;
|
||||
|
||||
import org.whispersystems.util.StringUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
import static org.whispersystems.signalservice.api.crypto.CryptoUtil.hmacSha256;
|
||||
import static org.whispersystems.util.ByteArrayUtil.concat;
|
||||
import static org.whispersystems.util.ByteArrayUtil.xor;
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts with a Synthetic IV.
|
||||
* <p>
|
||||
* Normal Java casing has been ignored to match original specifications.
|
||||
*/
|
||||
public final class HmacSIV {
|
||||
|
||||
private static final byte[] AUTH_BYTES = StringUtil.utf8("auth");
|
||||
private static final byte[] ENC_BYTES = StringUtil.utf8("enc");
|
||||
|
||||
/**
|
||||
* Encrypts M with K.
|
||||
*
|
||||
* @param K Key
|
||||
* @param M 32-byte Key to encrypt
|
||||
* @return (IV, C) 48-bytes: 16-byte Synthetic IV and 32-byte Ciphertext.
|
||||
*/
|
||||
public static byte[] encrypt(byte[] K, byte[] M) {
|
||||
if (K.length != 32) throw new AssertionError("K was wrong length");
|
||||
if (M.length != 32) throw new AssertionError("M was wrong length");
|
||||
|
||||
byte[] Ka = hmacSha256(K, AUTH_BYTES);
|
||||
byte[] Ke = hmacSha256(K, ENC_BYTES);
|
||||
byte[] IV = copyOfRange(hmacSha256(Ka, M), 0, 16);
|
||||
byte[] Kx = hmacSha256(Ke, IV);
|
||||
byte[] C = xor(Kx, M);
|
||||
return concat(IV, C);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts M from (IV, C) with K.
|
||||
*
|
||||
* @param K Key
|
||||
* @param IVC Output from {@link #encrypt(byte[], byte[])}
|
||||
* @return 32-byte M
|
||||
* @throws InvalidCiphertextException if the supplied IVC was not correct.
|
||||
*/
|
||||
public static byte[] decrypt(byte[] K, byte[] IVC) throws InvalidCiphertextException {
|
||||
if (K.length != 32) throw new AssertionError("K was wrong length");
|
||||
if (IVC.length != 48) throw new InvalidCiphertextException("IVC was wrong length");
|
||||
|
||||
byte[] IV = copyOfRange(IVC, 0, 16);
|
||||
byte[] C = copyOfRange(IVC, 16, 48);
|
||||
|
||||
byte[] Ka = hmacSha256(K, AUTH_BYTES);
|
||||
byte[] Ke = hmacSha256(K, ENC_BYTES);
|
||||
byte[] Kx = hmacSha256(Ke, IV);
|
||||
byte[] M = xor(Kx, C);
|
||||
|
||||
byte[] eExpectedIV = copyOfRange(hmacSha256(Ka, M), 0, 16);
|
||||
|
||||
if (Arrays.equals(IV, eExpectedIV)) {
|
||||
return M;
|
||||
} else {
|
||||
throw new InvalidCiphertextException("IV was incorrect");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
import org.whispersystems.signalservice.api.crypto.HmacSIV;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
|
||||
/**
|
||||
* Represents a hashed pin, from which you can create or decrypt KBS data.
|
||||
* <p>
|
||||
* Normal Java casing has been ignored to match original specifications.
|
||||
*/
|
||||
public final class HashedPin {
|
||||
|
||||
private final byte[] K;
|
||||
private final byte[] kbsAccessKey;
|
||||
|
||||
private HashedPin(byte[] K, byte[] kbsAccessKey) {
|
||||
this.K = K;
|
||||
this.kbsAccessKey = kbsAccessKey;
|
||||
}
|
||||
|
||||
public static HashedPin fromArgon2Hash(byte[] argon2Hash64) {
|
||||
if (argon2Hash64.length != 64) throw new AssertionError();
|
||||
|
||||
byte[] K = copyOfRange(argon2Hash64, 0, 32);
|
||||
byte[] kbsAccessKey = copyOfRange(argon2Hash64, 32, 64);
|
||||
return new HashedPin(K, kbsAccessKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link KbsData} to store on KBS.
|
||||
*/
|
||||
public KbsData createNewKbsData(SecureRandom random) {
|
||||
byte[] M = new byte[32];
|
||||
random.nextBytes(M);
|
||||
byte[] IVC = HmacSIV.encrypt(K, M);
|
||||
return new KbsData(M, kbsAccessKey, IVC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes 48 byte IVC from KBS and returns full {@link KbsData}.
|
||||
*/
|
||||
public KbsData decryptKbsDataIVCipherText(byte[] IVC) throws InvalidCiphertextException {
|
||||
byte[] masterKey = HmacSIV.decrypt(K, IVC);
|
||||
return new KbsData(masterKey, kbsAccessKey, IVC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
/**
|
||||
* Construct from a {@link HashedPin}.
|
||||
*/
|
||||
public final class KbsData {
|
||||
private final MasterKey masterKey;
|
||||
private final byte[] kbsAccessKey;
|
||||
private final byte[] cipherText;
|
||||
|
||||
KbsData(byte[] masterKey, byte[] kbsAccessKey, byte[] cipherText) {
|
||||
this.masterKey = new MasterKey(masterKey);
|
||||
this.kbsAccessKey = kbsAccessKey;
|
||||
this.cipherText = cipherText;
|
||||
}
|
||||
|
||||
public MasterKey getMasterKey() {
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
public byte[] getKbsAccessKey() {
|
||||
return kbsAccessKey;
|
||||
}
|
||||
|
||||
public byte[] getCipherText() {
|
||||
return cipherText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.util.StringUtil;
|
||||
|
||||
import static org.whispersystems.signalservice.api.crypto.CryptoUtil.hmacSha256;
|
||||
|
||||
public final class MasterKey {
|
||||
|
||||
private final byte[] masterKey;
|
||||
|
||||
public MasterKey(byte[] masterKey) {
|
||||
if (masterKey.length != 32) throw new AssertionError();
|
||||
|
||||
this.masterKey = masterKey;
|
||||
}
|
||||
|
||||
public String deriveRegistrationLock() {
|
||||
return Hex.toStringCondensed(derive("Registration Lock"));
|
||||
}
|
||||
|
||||
public byte[] deriveStorageServiceKey() {
|
||||
return derive("Storage Service Encryption");
|
||||
}
|
||||
|
||||
private byte[] derive(String keyName) {
|
||||
return hmacSha256(masterKey, StringUtil.utf8(keyName));
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return masterKey.clone();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.whispersystems.signalservice.api.crypto.CryptoUtil;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class SignalStorageUtil {
|
||||
|
||||
public static byte[] computeStorageServiceKey(byte[] kbsMasterKey) {
|
||||
return CryptoUtil.computeHmacSha256(kbsMasterKey, "Storage Service Encryption".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.whispersystems.util;
|
||||
|
||||
public final class ByteArrayUtil {
|
||||
|
||||
private ByteArrayUtil() {
|
||||
}
|
||||
|
||||
public static byte[] xor(byte[] a, byte[] b) {
|
||||
if (a.length != b.length) {
|
||||
throw new AssertionError("XOR length mismatch");
|
||||
}
|
||||
|
||||
byte[] out = new byte[a.length];
|
||||
|
||||
for (int i = a.length - 1; i >= 0; i--) {
|
||||
out[i] = (byte) (a[i] ^ b[i]);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static byte[] concat(byte[] a, byte[] b) {
|
||||
byte[] result = new byte[a.length + b.length];
|
||||
System.arraycopy(a, 0, result, 0, a.length);
|
||||
System.arraycopy(b, 0, result, a.length, b.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.whispersystems.util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class StringUtil {
|
||||
|
||||
private StringUtil() {
|
||||
}
|
||||
|
||||
public static byte[] utf8(String string) {
|
||||
return string.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user