Переглянути джерело

Merge pull request #12723 from nextcloud/bugfix/encrypted-large-file-upload

Bug-fix Encrypted Large File Upload
Tobias Kaminsky 1 рік тому
батько
коміт
b7ee06256d

+ 59 - 76
app/src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java

@@ -36,24 +36,24 @@ import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile;
 import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
 import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedMetadata;
 import com.owncloud.android.datamodel.e2e.v1.decrypted.Encrypted;
-import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFile;
 import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.e2ee.CsrHelper;
 import com.owncloud.android.utils.EncryptionUtils;
 
-import org.apache.commons.codec.binary.Hex;
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.security.DigestInputStream;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.SecureRandom;
 import java.security.interfaces.RSAPrivateCrtKey;
@@ -66,6 +66,7 @@ import java.util.Random;
 import java.util.Set;
 
 import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
 
 import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
 import static com.owncloud.android.utils.EncryptionUtils.decryptFile;
@@ -75,7 +76,6 @@ import static com.owncloud.android.utils.EncryptionUtils.decryptStringAsymmetric
 import static com.owncloud.android.utils.EncryptionUtils.decryptStringSymmetric;
 import static com.owncloud.android.utils.EncryptionUtils.deserializeJSON;
 import static com.owncloud.android.utils.EncryptionUtils.encodeBytesToBase64String;
-import static com.owncloud.android.utils.EncryptionUtils.encryptFile;
 import static com.owncloud.android.utils.EncryptionUtils.encryptFolderMetadata;
 import static com.owncloud.android.utils.EncryptionUtils.generateChecksum;
 import static com.owncloud.android.utils.EncryptionUtils.generateKey;
@@ -99,6 +99,11 @@ public class EncryptionTestIT extends AbstractIT {
 
     ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
 
+    private static final String MD5_ALGORITHM = "MD5";
+
+    private static final String filename = "ia7OEEEyXMoRa1QWQk8r";
+    private static final String secondFilename = "n9WXAIXO2wRY4R8nXwmo";
+
     public static final String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
         "IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV" +
         "GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7" +
@@ -395,34 +400,27 @@ public class EncryptionTestIT extends AbstractIT {
     public void testCryptFileWithoutMetadata() throws Exception {
         byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
         byte[] iv = decodeStringToBase64Bytes("gKm3n+mJzeY26q4OfuZEqg==");
-        byte[] authTag = decodeStringToBase64Bytes("PboI9tqHHX3QeAA22PIu4w==");
 
-        assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r", "78f42172166f9dc8fd1a7156b1753353", key, iv, authTag));
+        assertTrue(cryptFile(filename, "78f42172166f9dc8fd1a7156b1753353", key, iv));
     }
 
     @Test
     public void cryptFileWithMetadata() throws Exception {
         DecryptedFolderMetadataFileV1 metadata = generateFolderMetadataV1_1();
 
-        // n9WXAIXO2wRY4R8nXwmo
-        assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
+        assertTrue(cryptFile(filename,
                              "78f42172166f9dc8fd1a7156b1753353",
-                             decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
+                             decodeStringToBase64Bytes(metadata.getFiles().get(filename)
                                                            .getEncrypted().getKey()),
-                             decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
-                                                           .getInitializationVector()),
-                             decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
-                                                           .getAuthenticationTag())));
+                             decodeStringToBase64Bytes(metadata.getFiles().get(filename)
+                                                           .getInitializationVector())));
 
-        // n9WXAIXO2wRY4R8nXwmo
-        assertTrue(cryptFile("n9WXAIXO2wRY4R8nXwmo",
+        assertTrue(cryptFile(secondFilename,
                              "825143ed1f21ebb0c3b3c3f005b2f5db",
-                             decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
+                             decodeStringToBase64Bytes(metadata.getFiles().get(secondFilename)
                                                            .getEncrypted().getKey()),
-                             decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
-                                                           .getInitializationVector()),
-                             decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
-                                                           .getAuthenticationTag())));
+                             decodeStringToBase64Bytes(metadata.getFiles().get(secondFilename)
+                                                           .getInitializationVector())));
     }
 
     @Test
@@ -738,8 +736,8 @@ public class EncryptionTestIT extends AbstractIT {
         DecryptedFolderMetadataFileV1 metadata = new DecryptedFolderMetadataFileV1();
         String mnemonic = "chimney potato joke science ridge trophy result estate spare vapor much room";
 
-        metadata.getFiles().put("n9WXAIXO2wRY4R8nXwmo", new DecryptedFile());
-        metadata.getFiles().put("ia7OEEEyXMoRa1QWQk8r", new DecryptedFile());
+        metadata.getFiles().put(secondFilename, new DecryptedFile());
+        metadata.getFiles().put(filename, new DecryptedFile());
 
         String encryptedMetadataKey = "GuFPAULudgD49S4+VDFck3LiqQ8sx4zmbrBtdpCSGcT+T0W0z4F5gYQYPlzTG6WOkdW5LJZK/";
         metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
@@ -787,9 +785,8 @@ public class EncryptionTestIT extends AbstractIT {
 
     // Helper
     public static boolean compareJsonStrings(String expected, String actual) {
-        JsonParser parser = new JsonParser();
-        JsonElement o1 = parser.parse(expected);
-        JsonElement o2 = parser.parse(actual);
+        JsonElement o1 = JsonParser.parseString(expected);
+        JsonElement o2 = JsonParser.parseString(actual);
 
         if (o1.equals(o2)) {
             return true;
@@ -828,7 +825,7 @@ public class EncryptionTestIT extends AbstractIT {
         file1.setMetadataKey(0);
         file1.setAuthenticationTag("PboI9tqHHX3QeAA22PIu4w==");
 
-        files.put("ia7OEEEyXMoRa1QWQk8r", file1);
+        files.put(filename, file1);
 
         Data data2 = new Data();
         data2.setKey("9dfzbIYDt28zTyZfbcll+g==");
@@ -841,70 +838,56 @@ public class EncryptionTestIT extends AbstractIT {
         file2.setMetadataKey(0);
         file2.setAuthenticationTag("qOQZdu5soFO77Y7y4rAOVA==");
 
-        files.put("n9WXAIXO2wRY4R8nXwmo", file2);
+        files.put(secondFilename, file2);
 
         return new DecryptedFolderMetadataFileV1(metadata1, files);
     }
 
-
-    private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
+    private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv)
         throws Exception {
-        File file = getFile(fileName);
-        assertEquals(md5, getMD5Sum(file));
-
-        EncryptedFile encryptedFile = encryptFile(file, key, iv);
+        File file = File.createTempFile(fileName, "enc");
+        String md5BeforeEncryption = getMD5Sum(file);
 
-        File encryptedTempFile = File.createTempFile("file", "tmp");
-        FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
-        fileOutputStream.write(encryptedFile.getEncryptedBytes());
-        fileOutputStream.close();
-
-        byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.getAuthenticationTag());
-
-        // verify authentication tag
-        assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));
-
-        byte[] decryptedBytes = decryptFile(encryptedTempFile,
-                                            key,
-                                            iv,
-                                            authenticationTag,
-                                            new ArbitraryDataProviderImpl(targetContext),
-                                            user);
+        // Encryption
+        Cipher encryptorCipher = EncryptionUtils.getCipher(Cipher.ENCRYPT_MODE, key, iv);
+        EncryptionUtils.encryptFile(file, encryptorCipher);
+        String encryptorCipherAuthTag = EncryptionUtils.getAuthenticationTag(encryptorCipher);
 
+        // Decryption
+        Cipher decryptorCipher = EncryptionUtils.getCipher(Cipher.DECRYPT_MODE, key, iv);
         File decryptedFile = File.createTempFile("file", "dec");
-        FileOutputStream fileOutputStream1 = new FileOutputStream(decryptedFile);
-        fileOutputStream1.write(decryptedBytes);
-        fileOutputStream1.close();
+        decryptFile(decryptorCipher, file, decryptedFile, encryptorCipherAuthTag, new ArbitraryDataProviderImpl(targetContext), user);
 
-        return md5.compareTo(getMD5Sum(decryptedFile)) == 0;
-    }
+        String md5AfterEncryption = getMD5Sum(decryptedFile);
 
-    private String getMD5Sum(File file) {
-        FileInputStream fileInputStream = null;
-        try {
-            fileInputStream = new FileInputStream(file);
-            MessageDigest md5 = MessageDigest.getInstance("MD5");
-            byte[] bytes = new byte[2048];
-            int readBytes;
-
-            while ((readBytes = fileInputStream.read(bytes)) != -1) {
-                md5.update(bytes, 0, readBytes);
-            }
+        if (md5BeforeEncryption == null) {
+            Assert.fail();
+        }
 
-            return new String(Hex.encodeHex(md5.digest()));
+        return md5BeforeEncryption.equals(md5AfterEncryption);
+    }
 
-        } catch (Exception e) {
-            Log_OC.e(this, e.getMessage());
-        } finally {
-            if (fileInputStream != null) {
-                try {
-                    fileInputStream.close();
-                } catch (IOException e) {
-                    Log_OC.e(this, "Error getting MD5 checksum for file", e);
-                }
+    public static String getMD5Sum(File file) {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
+            DigestInputStream dis = new DigestInputStream(fis, md);
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = dis.read(buffer)) != -1) {
+                md.update(buffer, 0, bytesRead);
             }
+            byte[] digest = md.digest();
+            return bytesToHex(digest);
+        } catch (IOException | NoSuchAlgorithmException e) {
+            return null;
         }
+    }
 
-        return "";
+    private static String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
     }
 }

+ 3 - 1
app/src/main/java/com/owncloud/android/datamodel/e2e/v1/encrypted/EncryptedFile.kt

@@ -21,4 +21,6 @@
  */
 package com.owncloud.android.datamodel.e2e.v1.encrypted
 
-class EncryptedFile(var encryptedBytes: ByteArray, var authenticationTag: String)
+import java.io.File
+
+class EncryptedFile(var encryptedFile: File, var authenticationTag: String)

+ 5 - 18
app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java

@@ -51,6 +51,8 @@ import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import javax.crypto.Cipher;
+
 import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
 
 /**
@@ -262,31 +264,16 @@ public class DownloadFileOperation extends RemoteOperation {
 
                 byte[] key = decodeStringToBase64Bytes(keyString);
                 byte[] iv = decodeStringToBase64Bytes(nonceString);
-                byte[] authenticationTag = decodeStringToBase64Bytes(authenticationTagString);
 
                 try {
-                    byte[] decryptedBytes = EncryptionUtils.decryptFile(tmpFile,
-                                                                        key,
-                                                                        iv,
-                                                                        authenticationTag,
-                                                                        new ArbitraryDataProviderImpl(operationContext),
-                                                                        user);
-
-                    try (FileOutputStream fileOutputStream = new FileOutputStream(tmpFile)) {
-                        fileOutputStream.write(decryptedBytes);
-                    }
+                    Cipher cipher = EncryptionUtils.getCipher(Cipher.DECRYPT_MODE, key, iv);
+                    EncryptionUtils.decryptFile(cipher, tmpFile, newFile, authenticationTagString, new ArbitraryDataProviderImpl(operationContext), user);
                 } catch (Exception e) {
                     return new RemoteOperationResult(e);
                 }
             }
 
-            if (downloadType == DownloadType.DOWNLOAD) {
-                moved = tmpFile.renameTo(newFile);
-                newFile.setLastModified(file.getModificationTimestamp());
-                if (!moved) {
-                    result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED);
-                }
-            } else if (downloadType == DownloadType.EXPORT) {
+            if (downloadType == DownloadType.EXPORT) {
                 new FileExportUtils().exportFile(file.getFileName(),
                                                  file.getMimeType(),
                                                  operationContext.getContentResolver(),

+ 8 - 10
app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -94,6 +94,8 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import javax.crypto.Cipher;
+
 import androidx.annotation.CheckResult;
 import androidx.annotation.Nullable;
 
@@ -558,14 +560,11 @@ public class UploadFileOperation extends SyncOperation {
             Long creationTimestamp = FileUtil.getCreationTimestamp(originalFile);
 
             /***** E2E *****/
-
-            // Key, always generate new one
             byte[] key = EncryptionUtils.generateKey();
-
-            // IV, always generate new one
             byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
-
-            EncryptedFile encryptedFile = EncryptionUtils.encryptFile(mFile, key, iv);
+            Cipher cipher = EncryptionUtils.getCipher(Cipher.ENCRYPT_MODE, key, iv);
+            File file = new File(mFile.getStoragePath());
+            EncryptedFile encryptedFile = EncryptionUtils.encryptFile(file, cipher);
 
             // new random file name, check if it exists in metadata
             String encryptedFileName = EncryptionUtils.generateUid();
@@ -580,10 +579,7 @@ public class UploadFileOperation extends SyncOperation {
                 }
             }
 
-            File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
-            FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
-            fileOutputStream.write(encryptedFile.getEncryptedBytes());
-            fileOutputStream.close();
+            File encryptedTempFile = encryptedFile.getEncryptedFile();
 
             /***** E2E *****/
 
@@ -742,6 +738,8 @@ public class UploadFileOperation extends SyncOperation {
                         token = null;
                     }
                 }
+
+                encryptedTempFile.delete();
             }
         } catch (FileNotFoundException e) {
             Log_OC.d(TAG, mFile.getStoragePath() + " not exists anymore");

+ 88 - 93
app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java

@@ -22,11 +22,13 @@
 package com.owncloud.android.utils;
 
 import android.content.Context;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Pair;
 
 import com.google.common.collect.Lists;
+import com.google.common.primitives.Bytes;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.reflect.TypeToken;
@@ -71,13 +73,17 @@ import org.apache.commons.httpclient.HttpStatus;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.RandomAccessFile;
 import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.Key;
@@ -95,6 +101,7 @@ import java.security.cert.X509Certificate;
 import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.interfaces.RSAPublicKey;
 import java.security.spec.InvalidKeySpecException;
+import java.security.spec.InvalidParameterSpecException;
 import java.security.spec.KeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
@@ -107,6 +114,8 @@ import java.util.UUID;
 
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
 import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.KeyGenerator;
 import javax.crypto.NoSuchPaddingException;
@@ -118,6 +127,7 @@ import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
@@ -554,95 +564,80 @@ public final class EncryptionUtils {
         return Base64.decode(string, Base64.NO_WRAP);
     }
 
-    /*
-    ENCRYPTION
-     */
-
-    /**
-     * @param ocFile             file do crypt
-     * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
-     * @param iv                 initialization vector, either from metadata or
-     *                           {@link EncryptionUtils#randomBytes(int)}
-     * @return encryptedFile with encryptedBytes and authenticationTag
-     */
-    public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
-        throws NoSuchAlgorithmException,
-        InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-        BadPaddingException, IllegalBlockSizeException, IOException {
-        File file = new File(ocFile.getStoragePath());
-
-        return encryptFile(file, encryptionKeyBytes, iv);
+    public static EncryptedFile encryptFile(File file, Cipher cipher) throws InvalidParameterSpecException {
+        File encryptedFile = new File(file.getAbsolutePath() + ".enc");
+        encryptFileWithGivenCipher(file, encryptedFile, cipher);
+        String authenticationTagString = getAuthenticationTag(cipher);
+        return new EncryptedFile(encryptedFile, authenticationTagString);
     }
 
-    /**
-     * @param file               file do crypt
-     * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
-     * @param iv                 initialization vector, either from metadata or
-     *                           {@link EncryptionUtils#randomBytes(int)}
-     * @return encryptedFile with encryptedBytes and authenticationTag
-     */
-    public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
-        throws NoSuchAlgorithmException,
-        InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-        BadPaddingException, IllegalBlockSizeException, IOException {
+    public static String getAuthenticationTag(Cipher cipher) throws InvalidParameterSpecException {
+        byte[] authenticationTag = cipher.getParameters().getParameterSpec(GCMParameterSpec.class).getIV();
+        return encodeBytesToBase64String(authenticationTag);
+    }
 
+    public static Cipher getCipher(int mode, byte[] encryptionKeyBytes, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
         Cipher cipher = Cipher.getInstance(AES_CIPHER);
-
         Key key = new SecretKeySpec(encryptionKeyBytes, AES);
-
         GCMParameterSpec spec = new GCMParameterSpec(128, iv);
-        cipher.init(Cipher.ENCRYPT_MODE, key, spec);
-
-        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
-        byte[] fileBytes = new byte[(int) randomAccessFile.length()];
-        randomAccessFile.readFully(fileBytes);
-
-        byte[] cryptedBytes = cipher.doFinal(fileBytes);
-        String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
-                                                                                cryptedBytes.length - (128 / 8),
-                                                                                cryptedBytes.length));
-
-        return new EncryptedFile(cryptedBytes, authenticationTag);
+        cipher.init(mode, key, spec);
+        return cipher;
     }
 
-    /**
-     * @param file               encrypted file
-     * @param encryptionKeyBytes key from metadata
-     * @param iv                 initialization vector from metadata
-     * @param authenticationTag  authenticationTag from metadata
-     * @return decrypted byte[]
-     */
-    public static byte[] decryptFile(File file,
-                                     byte[] encryptionKeyBytes,
-                                     byte[] iv,
-                                     byte[] authenticationTag,
-                                     ArbitraryDataProvider arbitraryDataProvider,
-                                     User user)
-        throws NoSuchAlgorithmException,
-        InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-        BadPaddingException, IllegalBlockSizeException, IOException {
+    public static void encryptFileWithGivenCipher(File inputFile, File encryptedFile, Cipher cipher) {
+        try( FileInputStream inputStream = new FileInputStream(inputFile);
+             FileOutputStream fileOutputStream = new FileOutputStream(encryptedFile);
+             CipherOutputStream outputStream = new CipherOutputStream(fileOutputStream, cipher)) {
+            byte[] buffer = new byte[4096];
+            int bytesRead;
 
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
 
-        Cipher cipher = Cipher.getInstance(AES_CIPHER);
-        Key key = new SecretKeySpec(encryptionKeyBytes, AES);
-        GCMParameterSpec spec = new GCMParameterSpec(128, iv);
-        cipher.init(Cipher.DECRYPT_MODE, key, spec);
+            outputStream.close();
+            inputStream.close();
 
-        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
-        byte[] fileBytes = new byte[(int) randomAccessFile.length()];
-        randomAccessFile.readFully(fileBytes);
+            Log_OC.d(TAG, encryptedFile.getName() + "encrypted successfully");
+        } catch (IOException exception) {
+            Log_OC.d(TAG, "Error caught at encryptFileWithGivenCipher(): " + exception.getLocalizedMessage());
+        }
+    }
 
-        // check authentication tag
-        byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes,
-                                                               fileBytes.length - (128 / 8),
-                                                               fileBytes.length);
+    public static void decryptFile(Cipher cipher,
+                                   File encryptedFile,
+                                   File decryptedFile,
+                                   String authenticationTag,
+                                   ArbitraryDataProvider arbitraryDataProvider,
+                                   User user) {
+        try (FileInputStream inputStream = new FileInputStream(encryptedFile);
+             FileOutputStream outputStream = new FileOutputStream(decryptedFile)) {
+
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                byte[] output = cipher.update(buffer, 0, bytesRead);
+                if (output != null) {
+                    outputStream.write(output);
+                }
+            }
+            byte[] output = cipher.doFinal();
+            if (output != null) {
+                outputStream.write(output);
+            }
+            inputStream.close();
+            outputStream.close();
 
-        if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
-            reportE2eError(arbitraryDataProvider, user);
-            throw new SecurityException("Tag not correct");
-        }
+            if (!getAuthenticationTag(cipher).equals(authenticationTag)) {
+                reportE2eError(arbitraryDataProvider, user);
+                throw new SecurityException("Tag not correct");
+            }
 
-        return cipher.doFinal(fileBytes);
+            Log_OC.d(TAG, encryptedFile.getName() + "decrypted successfully");
+        } catch (IOException | BadPaddingException | IllegalBlockSizeException | InvalidParameterSpecException |
+                 SecurityException exception) {
+            Log_OC.d(TAG, "Error caught at decryptFile(): " + exception.getLocalizedMessage());
+        }
     }
 
     /**
@@ -840,7 +835,7 @@ public final class EncryptionUtils {
         return metadata.getCiphertext();
     }
 
-//    /**
+    //    /**
 //     * Encrypt string with AES/GCM/NoPadding
 //     *
 //     * @param string             string to encrypt
@@ -872,22 +867,22 @@ public final class EncryptionUtils {
 //
 //        return encodedCryptedBytes + delimiter + encodedIV;
 //    }
-public static String decryptStringSymmetricAsString(String string,
-                                                    byte[] encryptionKeyBytes,
-                                                    byte[] iv,
-                                                    byte[] authenticationTag,
-                                                    ArbitraryDataProvider arbitraryDataProvider,
-                                                    User user
-                                                   ) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
-    return decryptStringSymmetricAsString(
-        decodeStringToBase64Bytes(string),
-        encryptionKeyBytes,
-        iv,
-        authenticationTag,
-        false,
-        arbitraryDataProvider,
-        user);
-}
+    public static String decryptStringSymmetricAsString(String string,
+                                                        byte[] encryptionKeyBytes,
+                                                        byte[] iv,
+                                                        byte[] authenticationTag,
+                                                        ArbitraryDataProvider arbitraryDataProvider,
+                                                        User user
+                                                       ) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
+        return decryptStringSymmetricAsString(
+            decodeStringToBase64Bytes(string),
+            encryptionKeyBytes,
+            iv,
+            authenticationTag,
+            false,
+            arbitraryDataProvider,
+            user);
+    }
 
     public static String decryptStringSymmetricAsString(String string,
                                                         byte[] encryptionKeyBytes,
@@ -1196,7 +1191,7 @@ public static String decryptStringSymmetricAsString(String string,
         return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
             + "\n-----END PRIVATE KEY-----";
     }
-    
+
     public static PrivateKey PEMtoPrivateKey(String pem) throws NoSuchAlgorithmException, InvalidKeySpecException {
         byte[] privateKeyBytes = EncryptionUtils.decodeStringToBase64Bytes(pem);
         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);