diff --git a/src/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java b/src/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java index 4ac402093..f459d5dc8 100644 --- a/src/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java +++ b/src/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java @@ -5,6 +5,7 @@ import android.support.annotation.NonNull; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.security.InvalidAlgorithmParameterException; @@ -23,21 +24,47 @@ public class ModernDecryptingPartInputStream { public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull File file) throws IOException { + return createFor(attachmentSecret, random, new FileInputStream(file)); + } + + public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file) + throws IOException + { + FileInputStream inputStream = new FileInputStream(file); + byte[] random = new byte[32]; + + readFully(inputStream, random); + + return createFor(attachmentSecret, random, inputStream); + } + + private static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull InputStream inputStream) { try { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256")); - FileInputStream fileInputStream = new FileInputStream(file); - byte[] iv = new byte[16]; - byte[] key = mac.doFinal(random); + byte[] iv = new byte[16]; + byte[] key = mac.doFinal(random); Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); - return new CipherInputStream(fileInputStream, cipher); + return new CipherInputStream(inputStream, cipher); } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) { throw new AssertionError(e); } + + } + + private static void readFully(InputStream in, byte[] buffer) throws IOException { + int offset = 0; + + for (;;) { + int read = in.read(buffer, offset, buffer.length-offset); + + if (read + offset < buffer.length) offset += read; + else return; + } } } diff --git a/src/org/thoughtcrime/securesms/crypto/ModernEncryptingPartOutputStream.java b/src/org/thoughtcrime/securesms/crypto/ModernEncryptingPartOutputStream.java index 9ad21ecc5..a62947e97 100644 --- a/src/org/thoughtcrime/securesms/crypto/ModernEncryptingPartOutputStream.java +++ b/src/org/thoughtcrime/securesms/crypto/ModernEncryptingPartOutputStream.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.crypto; import android.support.annotation.NonNull; +import android.util.Pair; import java.io.File; import java.io.FileOutputStream; @@ -10,6 +11,7 @@ import java.io.OutputStream; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.CipherOutputStream; @@ -25,9 +27,12 @@ import javax.crypto.spec.SecretKeySpec; */ public class ModernEncryptingPartOutputStream { - public static OutputStream createFor(@NonNull AttachmentSecret attachmentSecret, byte[] random, File file) + public static Pair createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file, boolean inline) throws IOException { + byte[] random = new byte[32]; + new SecureRandom().nextBytes(random); + try { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256")); @@ -39,7 +44,11 @@ public class ModernEncryptingPartOutputStream { Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); - return new CipherOutputStream(fileOutputStream, cipher); + if (inline) { + fileOutputStream.write(random); + } + + return new Pair<>(random, new CipherOutputStream(fileOutputStream, cipher)); } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) { throw new AssertionError(e); } diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 09c508613..3537a59ed 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -29,6 +29,7 @@ import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import net.sqlcipher.database.SQLiteDatabase; @@ -53,7 +54,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.security.SecureRandom; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; @@ -82,8 +82,8 @@ public class AttachmentDatabase extends Database { static final String DIGEST = "digest"; static final String VOICE_NOTE = "voice_note"; public static final String FAST_PREFLIGHT_ID = "fast_preflight_id"; - static final String DATA_RANDOM = "data_random"; - static final String THUMBNAIL_RANDOM = "thumbnail_random"; + private static final String DATA_RANDOM = "data_random"; + private static final String THUMBNAIL_RANDOM = "thumbnail_random"; public static final int TRANSFER_PROGRESS_DONE = 0; public static final int TRANSFER_PROGRESS_STARTED = 1; @@ -462,13 +462,10 @@ public class AttachmentDatabase extends Database { throws MmsException { try { - byte[] random = new byte[32]; - new SecureRandom().nextBytes(random); + Pair out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, destination, false); + long length = Util.copy(in, out.second); - OutputStream out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, random, destination); - long length = Util.copy(in, out); - - return new DataInfo(destination, length, random); + return new DataInfo(destination, length, out.first); } catch (IOException e) { throw new MmsException(e); } diff --git a/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java index 16881bfaa..93b33ab81 100644 --- a/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java +++ b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java @@ -8,6 +8,7 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; +import android.util.Pair; import android.webkit.MimeTypeMap; import org.thoughtcrime.securesms.crypto.AttachmentSecret; @@ -15,7 +16,6 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; -import org.thoughtcrime.securesms.util.Conversions; import org.thoughtcrime.securesms.util.FileProviderUtil; import org.thoughtcrime.securesms.util.Util; @@ -119,8 +119,8 @@ public class PersistentBlobProvider { { executor.submit(() -> { try { - OutputStream output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, Conversions.longToByteArray(id), getFile(context, id).file); - Util.copy(input, output); + Pair output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, getFile(context, id).file, true); + Util.copy(input, output.second); } catch (IOException e) { Log.w(TAG, e); } @@ -160,7 +160,7 @@ public class PersistentBlobProvider { FileData fileData = getFile(context, id); - if (fileData.modern) return ModernDecryptingPartInputStream.createFor(attachmentSecret, Conversions.longToByteArray(id), fileData.file); + if (fileData.modern) return ModernDecryptingPartInputStream.createFor(attachmentSecret, fileData.file); else return ClassicDecryptingPartInputStream.createFor(attachmentSecret, fileData.file); }