简体   繁体   English

java.lang.OutOfMemoryError 加密/解密大文件时

[英]java.lang.OutOfMemoryError when encrypting/decrypting large files

The problem问题

I'm trying to make a file encryptor for Android(Encryption algorithm is AES).我正在尝试为 Android 制作文件加密器(加密算法是 AES)。 Everything works fine until I try to encrypt/decrypt a large file.一切正常,直到我尝试加密/解密一个大文件。 For example, when I try to encrypt a 771 MB file, it gives me this error:例如,当我尝试加密一个 771 MB 的文件时,它给了我这个错误:

E/AndroidRuntime: FATAL EXCEPTION: Thread-6
Process: com.suslanium.encryptor, PID: 27638
java.lang.OutOfMemoryError: Failed to allocate a 536869904 byte allocation with 25165824 free bytes and 254MB until OOM, target footprint 295637424, growth limit 536870912
    at com.android.org.conscrypt.OpenSSLCipher$EVP_AEAD.expand(OpenSSLCipher.java:1219)
    at com.android.org.conscrypt.OpenSSLCipher$EVP_AEAD.updateInternal(OpenSSLCipher.java:1336)
    at com.android.org.conscrypt.OpenSSLCipher.engineUpdate(OpenSSLCipher.java:323)
    at javax.crypto.Cipher.update(Cipher.java:1722)
    at javax.crypto.CipherOutputStream.write(CipherOutputStream.java:158)
    at com.suslanium.encryptor.MainActivity.encryptFileAES_GCM(MainActivity.java:912)
    at com.suslanium.encryptor.MainActivity$18.run(MainActivity.java:794)
    at java.lang.Thread.run(Thread.java:919)

Fragment of my code我的代码片段

public void encryptFileAES_GCM(File file , File fileToSave) throws Exception {
    File keyEncrypted = new File(pathToStorage + "EncryptedKey.enc");
    File IVEncrypted = new File(pathToStorage + "EncryptedKIV.enc");
    if (keyEncrypted.exists() && IVEncrypted.exists()) {
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(fileToSave);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(globalKey, "AES");
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, globalIV);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
        CipherOutputStream cos = new CipherOutputStream(fos, cipher);
        int b;
        byte[] d = new byte[512];
        while ((b = fis.read(d)) != -1) {
            cos.write(d, 0, b); //Line 912
        }
        cos.flush();
        cos.close();
        fis.close();
    } else {
        throw new Exception("Key or IV does not exist, encryption can't be done. Please create a key first.");
    }
}

I already tried to search for some solutions, but with no luck.我已经尝试寻找一些解决方案,但没有运气。 I have no idea what's wrong with this code.我不知道这段代码有什么问题。 The buffer size is not too big, but even after decreasing it - nothing happens.缓冲区大小不是太大,但即使减小它 - 也没有任何反应。 Can anyone please help me?谁能帮帮我吗?

UPDATE更新

Problem solved.问题解决了。 When encrypting large files, I decided to use the following algorithm: split the file into chunks with X size, encrypt these chunks and put them in a zip archive.在加密大文件时,我决定使用以下算法:将文件分成 X 大小的块,加密这些块并将它们放入 zip 存档中。 When decrypting large files, you need to extract the previously encrypted chunks from the archive, decrypt them and merge decrypted chunks into one file.解密大文件时,您需要从存档中提取先前加密的块,解密它们并将解密的块合并到一个文件中。

NB: Your code is all over the place and looks to be COMPLETELY INSECURE .注意:您的代码到处都是,看起来完全不安全 Even if you do fix this problem.即使你确实解决了这个问题。 See note below.请参阅下面的注释。

You've found a "bug" in conscrypt.您在 conscrypt 中发现了一个“错误”。 Conscrypt is technically fulfilling the requirements as per the stated spec. Conscrypt 在技术上满足所述规范的要求。 We can either point fingers at android/google for shipping such a useless excuse for a library, or for the community not to have better libraries, at the spec authors for allowing such a useless excuse to claim they technically don't violate spec, or at conscrypt for writing such a poor implementation.我们可以指责 android/google 为图书馆提供这样一个无用的借口,或者社区没有更好的图书馆,在规范作者允许这样一个无用的借口声称他们在技术上不违反规范,或者在 conscrypt 写了这么糟糕的实现。 I leave that to you.我把它留给你。 The upshot is: It doesn't work, it won't work, and anybody you care to complain to, to fix it, will simply point fingers elsewhere.结果是:它不起作用,它不会起作用,任何你想抱怨、解决它的人都会把矛头指向别处。

Specifically, conscrypt will just cache it all and won't perform the encryption until the very end, which is bizarre, but that's how it works.具体来说,conscrypt 只会将所有内容缓存起来,直到最后才执行加密,这很奇怪,但这就是它的工作原理。 See this SO question/answer for proof and details.有关证明和详细信息,请参阅此 SO 问题/答案

Solutions:解决方案:

I don't have an android phone to test this for you and I rarely do android dev, which complicates matters - there is no obvious fix here.我没有 android 手机来为你测试这个,我很少做 android 开发,这使事情变得复杂 - 这里没有明显的解决方法。

  1. Look into using BouncyCastle.考虑使用 BouncyCastle。 android used to ship with it and sort of still does for backwards compatibility, but I'm pretty sure it's not going to be reliably used here, so you'd have to look into shipping the BC library as part of your app. android 曾经与它一起发布,并且在某种程度上仍然可以实现向后兼容性,但我很确定它不会在这里可靠地使用,因此您必须考虑将 BC 库作为您的应用程序的一部分发布。 Consider using the BC-specific API (not built on top of javax.crypto), which guarantees that android won't pull a switcheroo on you and pick the broken conscrypt implementation.考虑使用特定于 BC 的 API(不是基于 javax.crypto 构建的),这可以保证 android 不会对您进行切换并选择损坏的 conscrypt 实现。

  2. Find the code in conscrypt that is written so poorly and send a pull request with a fix.在 conscrypt 中找到写得很糟糕的代码,然后发送一个带有修复的拉取请求。 They can point fingers all day long if you file a bug, but an improvement in their code base, all set to go, just press merge - that's harder for them to deny.如果您提交错误,他们可以整天指责,但他们的代码库有所改进,全部设置为 go,只需按合并 - 他们更难否认。

  3. Change your code to save in chunks instead.更改您的代码以保存在块中。 Write a little protocol: files are stored not as an encrypted blob, but as a sequence of cryptounits, where each unit is a length (as a big-endian 32-bit integer), followed by that many bytes of crypto, followed by another unit.编写一个小协议:文件不是作为加密的 blob 存储的,而是作为一个加密单元序列存储的,其中每个单元是一个长度(作为一个大端序的 32 位整数),然后是这么多字节的加密,然后是另一个单元。 A sequence of cryptounits always ends in a 0-length unit, which tells you it's done.一个密码单元序列总是以一个长度为 0 的单元结束,这告诉你它已经完成了。 To write these, pick an arbitrary size (maybe 10MB: 10*1024*1024 ), and write data (might as well do it all in memory, I guess - conscrypt will be doing that in nay case), then you know how much data you have which you can then save by writing the length, then writing all the data.要编写这些,请选择任意大小(可能是 10MB: 10*1024*1024 ),然后写入数据(最好在 memory 中完成,我猜 - conscrypt 在任何情况下都会这样做),然后你知道多少您拥有的数据可以通过写入长度来保存,然后写入所有数据。 Then keep going until your file is done.然后继续,直到你的文件完成。

NB: Your code is all over the place and looks to be COMPLETELY INSECURE .注意:您的代码到处都是,看起来完全不安全 You have 'keyEncrypted' and 'IVEncrypted', which you then do not use (other than to abort with an exception if these files are not available).您有 'keyEncrypted' 和 'IVEncrypted',然后您不使用它们(除非这些文件不可用时异常中止)。 Instead, you are doing your crypto with the 'global key', directly.相反,您直接使用“全局密钥”进行加密。

That means anybody can decrypt anything your app produced, needing nothing except your app apk which is everywhere .这意味着任何人都可以解密您的应用程序生成的任何内容,除了无处不在的应用程序 apk 之外什么都不需要 Surely that's not what you intended.这肯定不是你想要的。 Perhaps you intended to use the global key to decrypt iv and key, and then use the decrypted iv/key combo to encrypt or decrypt the file?也许您打算使用全局密钥来解密 iv 和密钥,然后使用解密的 iv/key 组合来加密或解密文件? Note that this 'encryptedIV.key' malarkey is security theatre.请注意,这个 'encryptedIV.key' malarkey 是安全剧院。 Even if you fix your bug, it's bad crypto: Somebody using your app sees that you name these files encryptedKey and would then probably assume they are encrypted.即使你修复了你的错误,它也是糟糕的加密:使用你的应用程序的人看到你将这些文件命名为encryptedKey ,然后可能会认为它们是加密的。 That's a misleading name: They are encrypted with public info, and encrypting something with public info is idiotic: What are you trying to accomplish?这是一个误导性的名称:它们是用公共信息加密的,而用公共信息加密某些东西是愚蠢的:你想完成什么? A 2-bit hacker with 5 seconds to peruse your code (even if you try to obfuscate it) will figure this out, extract the 'globalKey', and decrypt these files just the same.一个 2 位黑客有 5 秒的时间来细读您的代码(即使您尝试对其进行混淆)将弄清楚这一点,提取“globalKey”,并以同样的方式解密这些文件。

If the user themselves does not have to enter any passphrase to kickstart some encryption or decryption, and you can't use the android built in security stuff that eg asks them to confirm with a fingerprint scan, then accept that anybody with access to the phone's disk contents or the phone itself unlocked CAN read all this stuff, period, and make that clear by not suggesting these key files are encrypted (because, they effectively aren't, given that the only thing they are encrypted with, is publicly known keys, which does not count).如果用户自己不需要输入任何密码来启动某些加密或解密,并且您不能使用 android 内置的安全功能,例如要求他们通过指纹扫描确认,那么接受任何有权访问手机的人磁盘内容或手机本身解锁可以读取所有这些东西,期间,并通过不建议这些密钥文件被加密来明确这一点(因为,它们实际上不是,因为它们被加密的唯一东西是公开的密钥,这不算)。

android:hardwareAccelerated="false" , android:largeHeap="true"

add these to in your manifest file application tag将这些添加到您的清单文件应用程序标记中

<application
android:allowBackup="true"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM