Update file format for backupV2.

This commit is contained in:
Greyson Parrelli
2024-04-22 10:26:00 -04:00
committed by Cody Henthorne
parent 2e7279c72f
commit 34dbd11db0
7 changed files with 66 additions and 28 deletions

View File

@@ -41,18 +41,21 @@ class EncryptedBackupReader(
val stream: InputStream
init {
val keyMaterial = key.deriveSecrets(aci)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding").apply {
init(Cipher.DECRYPT_MODE, SecretKeySpec(keyMaterial.cipherKey, "AES"), IvParameterSpec(keyMaterial.iv))
}
val keyMaterial = key.deriveBackupSecrets(aci)
validateMac(keyMaterial.macKey, streamLength, dataStream())
val inputStream = dataStream()
val iv = inputStream.readNBytesOrThrow(16)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding").apply {
init(Cipher.DECRYPT_MODE, SecretKeySpec(keyMaterial.cipherKey, "AES"), IvParameterSpec(iv))
}
stream = GZIPInputStream(
CipherInputStream(
TruncatingInputStream(
wrapped = dataStream(),
wrapped = inputStream,
maxBytes = streamLength - MAC_SIZE
),
cipher

View File

@@ -9,6 +9,7 @@ import org.signal.core.util.stream.MacOutputStream
import org.signal.core.util.writeVarInt32
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.backup.BackupKey
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.io.IOException
@@ -36,14 +37,19 @@ class EncryptedBackupWriter(
private val macStream: MacOutputStream
init {
val keyMaterial = key.deriveSecrets(aci)
val keyMaterial = key.deriveBackupSecrets(aci)
val iv: ByteArray = Util.getSecretBytes(16)
outputStream.write(iv)
outputStream.flush()
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding").apply {
init(Cipher.ENCRYPT_MODE, SecretKeySpec(keyMaterial.cipherKey, "AES"), IvParameterSpec(keyMaterial.iv))
init(Cipher.ENCRYPT_MODE, SecretKeySpec(keyMaterial.cipherKey, "AES"), IvParameterSpec(iv))
}
val mac = Mac.getInstance("HmacSHA256").apply {
init(SecretKeySpec(keyMaterial.macKey, "HmacSHA256"))
update(iv)
}
macStream = MacOutputStream(outputStream, mac)

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.backup.v2.stream
import org.junit.Assert.assertEquals
import org.junit.Test
import org.signal.core.util.Base64
import org.signal.core.util.Hex
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
@@ -56,7 +57,7 @@ class EncryptedBackupReaderWriterTest {
val key = BackupKey(Util.getSecretBytes(32))
val aci = ACI.from(UUID.randomUUID())
val sizes = (1..10)
val uniqueSizes = (1..10)
.map { frameCount ->
val outputStream = ByteArrayOutputStream()
@@ -72,6 +73,29 @@ class EncryptedBackupReaderWriterTest {
}
.toSet()
assertEquals(1, sizes.size)
assertEquals(1, uniqueSizes.size)
}
@Test
fun `using a different IV every time`() {
val key = BackupKey(Util.getSecretBytes(32))
val aci = ACI.from(UUID.randomUUID())
val count = 10
val uniqueOutputs = (0 until count)
.map {
val outputStream = ByteArrayOutputStream()
EncryptedBackupWriter(key, aci, outputStream, append = { outputStream.write(it) }).use { writer ->
writer.write(BackupInfo(version = 1, backupTimeMs = 1000L))
writer.write(Frame(account = AccountData(username = "static-data")))
}
outputStream.toByteArray()
}
.map { Base64.encodeWithPadding(it) }
.toSet()
assertEquals(count, uniqueOutputs.size)
}
}

View File

@@ -184,7 +184,7 @@ public class SignalServiceMessageReceiver {
*
* @return An InputStream that streams the plaintext attachment contents.
*/
public InputStream retrieveArchivedAttachment(@Nonnull BackupKey.KeyMaterial<MediaId> archivedMediaKeyMaterial,
public InputStream retrieveArchivedAttachment(@Nonnull BackupKey.MediaKeyMaterial archivedMediaKeyMaterial,
@Nonnull Map<String, String> readCredentialHeaders,
@Nonnull File archiveDestination,
@Nonnull SignalServiceAttachmentPointer pointer,

View File

@@ -22,16 +22,15 @@ class BackupKey(val value: ByteArray) {
)
}
fun deriveSecrets(aci: ACI): KeyMaterial<BackupId> {
fun deriveBackupSecrets(aci: ACI): BackupKeyMaterial {
val backupId = deriveBackupId(aci)
val extendedKey = HKDF.deriveSecrets(this.value, backupId.value, "20231003_Signal_Backups_EncryptMessageBackup".toByteArray(), 80)
return KeyMaterial(
return BackupKeyMaterial(
id = backupId,
macKey = extendedKey.copyOfRange(0, 32),
cipherKey = extendedKey.copyOfRange(32, 64),
iv = extendedKey.copyOfRange(64, 80)
cipherKey = extendedKey.copyOfRange(32, 64)
)
}
@@ -39,14 +38,14 @@ class BackupKey(val value: ByteArray) {
return MediaId(HKDF.deriveSecrets(value, mediaName.toByteArray(), "Media ID".toByteArray(), 15))
}
fun deriveMediaSecrets(mediaName: MediaName): KeyMaterial<MediaId> {
fun deriveMediaSecrets(mediaName: MediaName): MediaKeyMaterial {
return deriveMediaSecrets(deriveMediaId(mediaName))
}
fun deriveMediaSecrets(mediaId: MediaId): KeyMaterial<MediaId> {
private fun deriveMediaSecrets(mediaId: MediaId): MediaKeyMaterial {
val extendedKey = HKDF.deriveSecrets(this.value, mediaId.value, "20231003_Signal_Backups_EncryptMedia".toByteArray(), 80)
return KeyMaterial(
return MediaKeyMaterial(
id = mediaId,
macKey = extendedKey.copyOfRange(0, 32),
cipherKey = extendedKey.copyOfRange(32, 64),
@@ -54,16 +53,22 @@ class BackupKey(val value: ByteArray) {
)
}
class KeyMaterial<Id> (
val id: Id,
class BackupKeyMaterial(
val id: BackupId,
val macKey: ByteArray,
val cipherKey: ByteArray
)
class MediaKeyMaterial(
val id: MediaId,
val macKey: ByteArray,
val cipherKey: ByteArray,
val iv: ByteArray
) {
companion object {
@JvmStatic
fun forMedia(id: ByteArray, keyMac: ByteArray, iv: ByteArray): KeyMaterial<MediaId> {
return KeyMaterial(
fun forMedia(id: ByteArray, keyMac: ByteArray, iv: ByteArray): MediaKeyMaterial {
return MediaKeyMaterial(
MediaId(id),
keyMac.copyOfRange(32, 64),
keyMac.copyOfRange(0, 32),

View File

@@ -110,7 +110,7 @@ public class AttachmentCipherInputStream extends FilterInputStream {
/**
* Decrypt archived media to it's original attachment encrypted blob.
*/
public static InputStream createForArchivedMedia(BackupKey.KeyMaterial<MediaId> archivedMediaKeyMaterial, File file, long originalCipherTextLength)
public static InputStream createForArchivedMedia(BackupKey.MediaKeyMaterial archivedMediaKeyMaterial, File file, long originalCipherTextLength)
throws InvalidMessageException, IOException
{
try {

View File

@@ -93,7 +93,7 @@ public final class AttachmentCipherTest {
@Test
public void archive_encryptDecrypt() throws IOException, InvalidMessageException {
byte[] key = Util.getSecretBytes(64);
BackupKey.KeyMaterial<MediaId> keyMaterial = BackupKey.KeyMaterial.forMedia(Util.getSecretBytes(15), key, Util.getSecretBytes(16));
BackupKey.MediaKeyMaterial keyMaterial = BackupKey.MediaKeyMaterial.forMedia(Util.getSecretBytes(15), key, Util.getSecretBytes(16));
byte[] plaintextInput = "Peter Parker".getBytes();
EncryptResult encryptResult = encryptData(plaintextInput, key, false);
File cipherFile = writeToFile(encryptResult.ciphertext);
@@ -108,7 +108,7 @@ public final class AttachmentCipherTest {
@Test
public void archive_encryptDecryptEmpty() throws IOException, InvalidMessageException {
byte[] key = Util.getSecretBytes(64);
BackupKey.KeyMaterial<MediaId> keyMaterial = BackupKey.KeyMaterial.forMedia(Util.getSecretBytes(15), key, Util.getSecretBytes(16));
BackupKey.MediaKeyMaterial keyMaterial = BackupKey.MediaKeyMaterial.forMedia(Util.getSecretBytes(15), key, Util.getSecretBytes(16));
byte[] plaintextInput = "".getBytes();
EncryptResult encryptResult = encryptData(plaintextInput, key, false);
File cipherFile = writeToFile(encryptResult.ciphertext);
@@ -128,7 +128,7 @@ public final class AttachmentCipherTest {
try {
byte[] key = Util.getSecretBytes(64);
byte[] badKey = Util.getSecretBytes(64);
BackupKey.KeyMaterial<MediaId> keyMaterial = BackupKey.KeyMaterial.forMedia(Util.getSecretBytes(15), badKey, Util.getSecretBytes(16));
BackupKey.MediaKeyMaterial keyMaterial = BackupKey.MediaKeyMaterial.forMedia(Util.getSecretBytes(15), badKey, Util.getSecretBytes(16));
byte[] plaintextInput = "Gwen Stacy".getBytes();
EncryptResult encryptResult = encryptData(plaintextInput, key, false);
@@ -270,7 +270,7 @@ public final class AttachmentCipherTest {
File cipherFile = writeToFile(encryptedData);
BackupKey.KeyMaterial<MediaId> keyMaterial = BackupKey.KeyMaterial.forMedia(Util.getSecretBytes(15), key, Util.getSecretBytes(16));
BackupKey.MediaKeyMaterial keyMaterial = BackupKey.MediaKeyMaterial.forMedia(Util.getSecretBytes(15), key, Util.getSecretBytes(16));
InputStream decryptedStream = AttachmentCipherInputStream.createForArchivedMedia(keyMaterial, cipherFile, length);
byte[] plaintextOutput = readInputStreamFully(decryptedStream);
@@ -348,7 +348,7 @@ public final class AttachmentCipherTest {
cipherFile = writeToFile(badMacCiphertext);
BackupKey.KeyMaterial<MediaId> keyMaterial = BackupKey.KeyMaterial.forMedia(Util.getSecretBytes(15), key, Util.getSecretBytes(16));
BackupKey.MediaKeyMaterial keyMaterial = BackupKey.MediaKeyMaterial.forMedia(Util.getSecretBytes(15), key, Util.getSecretBytes(16));
AttachmentCipherInputStream.createForArchivedMedia(keyMaterial, cipherFile, plaintextInput.length);
fail();
} catch (InvalidMessageException e) {