Convert SignalStorageCipher and keys to kotlin.

This commit is contained in:
Greyson Parrelli
2024-11-13 10:26:59 -05:00
parent 2ebf668db4
commit 8746f483c0
10 changed files with 175 additions and 189 deletions
@@ -1,52 +0,0 @@
package org.whispersystems.signalservice.api.storage;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.whispersystems.signalservice.internal.util.Util;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Encrypts and decrypts data from the storage service.
*/
public class SignalStorageCipher {
private static final int IV_LENGTH = 12;
public static byte[] encrypt(StorageCipherKey key, byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] iv = Util.getSecretBytes(IV_LENGTH);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.serialize(), "AES"), new GCMParameterSpec(128, iv));
byte[] ciphertext = cipher.doFinal(data);
return Util.join(iv, ciphertext);
} catch (NoSuchAlgorithmException | java.security.InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) {
throw new AssertionError(e);
}
}
public static byte[] decrypt(StorageCipherKey key, byte[] data) throws InvalidKeyException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[][] split = Util.split(data, IV_LENGTH, data.length - IV_LENGTH);
byte[] iv = split[0];
byte[] cipherText = split[1];
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.serialize(), "AES"), new GCMParameterSpec(128, iv));
return cipher.doFinal(cipherText);
} catch (java.security.InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
throw new InvalidKeyException(e);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
}
}
}
@@ -0,0 +1,69 @@
package org.whispersystems.signalservice.api.storage
import org.whispersystems.signalservice.internal.util.Util
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
/**
* Encrypts and decrypts data from the storage service.
*/
object SignalStorageCipher {
private const val IV_LENGTH = 12
@JvmStatic
fun encrypt(key: StorageCipherKey, data: ByteArray): ByteArray {
try {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val iv = Util.getSecretBytes(IV_LENGTH)
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key.serialize(), "AES"), GCMParameterSpec(128, iv))
val ciphertext = cipher.doFinal(data)
return iv + ciphertext
} catch (e: NoSuchAlgorithmException) {
throw AssertionError(e)
} catch (e: InvalidKeyException) {
throw AssertionError(e)
} catch (e: InvalidAlgorithmParameterException) {
throw AssertionError(e)
} catch (e: NoSuchPaddingException) {
throw AssertionError(e)
} catch (e: BadPaddingException) {
throw AssertionError(e)
} catch (e: IllegalBlockSizeException) {
throw AssertionError(e)
}
}
@JvmStatic
@Throws(org.signal.libsignal.protocol.InvalidKeyException::class)
fun decrypt(key: StorageCipherKey, data: ByteArray): ByteArray {
try {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val iv = data.copyOfRange(0, IV_LENGTH)
val cipherText = data.copyOfRange(IV_LENGTH, data.size)
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key.serialize(), "AES"), GCMParameterSpec(128, iv))
return cipher.doFinal(cipherText)
} catch (e: InvalidKeyException) {
throw org.signal.libsignal.protocol.InvalidKeyException(e)
} catch (e: BadPaddingException) {
throw org.signal.libsignal.protocol.InvalidKeyException(e)
} catch (e: IllegalBlockSizeException) {
throw org.signal.libsignal.protocol.InvalidKeyException(e)
} catch (e: NoSuchAlgorithmException) {
throw AssertionError(e)
} catch (e: NoSuchPaddingException) {
throw AssertionError(e)
} catch (e: InvalidAlgorithmParameterException) {
throw AssertionError(e)
}
}
}
@@ -1,5 +0,0 @@
package org.whispersystems.signalservice.api.storage;
public interface StorageCipherKey {
byte[] serialize();
}
@@ -0,0 +1,5 @@
package org.whispersystems.signalservice.api.storage
interface StorageCipherKey {
fun serialize(): ByteArray
}
@@ -1,38 +0,0 @@
package org.whispersystems.signalservice.api.storage;
import java.util.Arrays;
/**
* Key used to encrypt individual storage items in the storage service.
*
* Created via {@link StorageKey#deriveItemKey(byte[]) }.
*/
public final class StorageItemKey implements StorageCipherKey {
private static final int LENGTH = 32;
private final byte[] key;
StorageItemKey(byte[] key) {
if (key.length != LENGTH) throw new AssertionError();
this.key = key;
}
@Override
public byte[] serialize() {
return key.clone();
}
@Override
public boolean equals(Object o) {
if (o == null || o.getClass() != getClass()) return false;
return Arrays.equals(((StorageItemKey) o).key, key);
}
@Override
public int hashCode() {
return Arrays.hashCode(key);
}
}
@@ -0,0 +1,27 @@
package org.whispersystems.signalservice.api.storage
/**
* Key used to encrypt individual storage items in the storage service.
*
* Created via [StorageKey.deriveItemKey].
*/
class StorageItemKey(val key: ByteArray) : StorageCipherKey {
init {
check(key.size == 32)
}
override fun serialize(): ByteArray = key.clone()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StorageItemKey
return key.contentEquals(other.key)
}
override fun hashCode(): Int {
return key.contentHashCode()
}
}
@@ -1,56 +0,0 @@
package org.whispersystems.signalservice.api.storage;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.signal.core.util.Base64;
import org.whispersystems.util.StringUtil;
import java.util.Arrays;
import static org.signal.core.util.CryptoUtil.hmacSha256;
/**
* Key used to encrypt data on the storage service. Not used directly -- instead we used keys that
* are derived for each item we're storing.
*
* Created via {@link MasterKey#deriveStorageServiceKey()}.
*/
public final class StorageKey {
private static final int LENGTH = 32;
private final byte[] key;
public StorageKey(byte[] key) {
if (key.length != LENGTH) throw new AssertionError();
this.key = key;
}
public StorageManifestKey deriveManifestKey(long version) {
return new StorageManifestKey(derive("Manifest_" + version));
}
public StorageItemKey deriveItemKey(byte[] key) {
return new StorageItemKey(derive("Item_" + Base64.encodeWithPadding(key)));
}
private byte[] derive(String keyName) {
return hmacSha256(key, StringUtil.utf8(keyName));
}
public byte[] serialize() {
return key.clone();
}
@Override
public boolean equals(Object o) {
if (o == null || o.getClass() != getClass()) return false;
return Arrays.equals(((StorageKey) o).key, key);
}
@Override
public int hashCode() {
return Arrays.hashCode(key);
}
}
@@ -0,0 +1,47 @@
package org.whispersystems.signalservice.api.storage
import org.signal.core.util.Base64.encodeWithPadding
import org.signal.core.util.CryptoUtil
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.util.StringUtil
/**
* Key used to encrypt data on the storage service. Not used directly -- instead we used keys that
* are derived for each item we're storing.
*
* Created via [MasterKey.deriveStorageServiceKey].
*/
class StorageKey(val key: ByteArray) {
init {
check(key.size == 32)
}
fun deriveManifestKey(version: Long): StorageManifestKey {
return StorageManifestKey(derive("Manifest_$version"))
}
fun deriveItemKey(key: ByteArray): StorageItemKey {
return StorageItemKey(derive("Item_" + encodeWithPadding(key)))
}
private fun derive(keyName: String): ByteArray {
return CryptoUtil.hmacSha256(key, StringUtil.utf8(keyName))
}
fun serialize(): ByteArray {
return key.clone()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StorageKey
return key.contentEquals(other.key)
}
override fun hashCode(): Int {
return key.contentHashCode()
}
}
@@ -1,38 +0,0 @@
package org.whispersystems.signalservice.api.storage;
import java.util.Arrays;
/**
* Key used to encrypt a manifest in the storage service.
*
* Created via {@link StorageKey#deriveManifestKey(long)}.
*/
public final class StorageManifestKey implements StorageCipherKey {
private static final int LENGTH = 32;
private final byte[] key;
StorageManifestKey(byte[] key) {
if (key.length != LENGTH) throw new AssertionError();
this.key = key;
}
@Override
public byte[] serialize() {
return key.clone();
}
@Override
public boolean equals(Object o) {
if (o == null || o.getClass() != getClass()) return false;
return Arrays.equals(((StorageManifestKey) o).key, key);
}
@Override
public int hashCode() {
return Arrays.hashCode(key);
}
}
@@ -0,0 +1,27 @@
package org.whispersystems.signalservice.api.storage
/**
* Key used to encrypt a manifest in the storage service.
*
* Created via [StorageKey.deriveManifestKey].
*/
class StorageManifestKey(val key: ByteArray) : StorageCipherKey {
init {
check(key.size == 32)
}
override fun serialize(): ByteArray = key.clone()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StorageManifestKey
return key.contentEquals(other.key)
}
override fun hashCode(): Int {
return key.contentHashCode()
}
}