【问题标题】:java.lang.OutOfMemoryError when encrypting/decrypting large files加密/解密大文件时出现 java.lang.OutOfMemoryError
【发布时间】:2021-05-13 23:39:04
【问题描述】:

问题

我正在尝试为 Android 制作文件加密器(加密算法为 AES)。一切正常,直到我尝试加密/解密一个大文件。例如,当我尝试加密一个 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)

我的代码片段

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.");
    }
}

我已经尝试寻找一些解决方案,但没有成功。我不知道这段代码有什么问题。缓冲区大小不是太大,但即使减小它 - 也没有任何反应。谁能帮帮我?

更新

问题解决了。在加密大文件时,我决定使用以下算法:将文件分成 X 大小的块,加密这些块并将它们放入 zip 存档中。解密大文件时,需要从存档中提取之前加密的chunk,解密后将解密后的chunk合并到一个文件中。

【问题讨论】:

  • 确实很奇怪。让你的缓冲区更大。至少 8192 并在 while 循环中尝试使用 flush()。
  • 是第一个 write() 调用导致了这种情况吗?还是过了一段时间?
  • 1.你知道CipherOutputStream 使用内部缓冲区吗? 2.我认为用同一个实例加密16 x N字节是安全的。
  • 您是否在链接的stackoverflow.com/a/57384337/8166854 中查看@President James K. Polk 的示例?他给出了一个完整的例子来避免“Conscrypt”中的错误,并建议使用 Bouncy Castle

标签: java android android-studio encryption out-of-memory


【解决方案1】:
android:hardwareAccelerated="false" , android:largeHeap="true"

将这些添加到您的清单文件应用程序标签中

<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">

【讨论】:

  • 谢谢,但我已经尝试过了。它仍然给我同样的错误。
  • 你在哪里运行应用真机或模拟器?
  • 我在真实设备上运行这个应用程序。在三星 Galaxy S7 和 S10e 上尝试过 - 都出现同样的错误。
【解决方案2】:

注意:您的代码到处都是并且看起来完全不安全。即使你确实解决了这个问题。请参阅下面的注释。

您在 conscrypt 中发现了一个“错误”。 Conscrypt 在技术上满足所述规范的要求。我们可以指责 android/google 为图书馆提供这样一个无用的借口,或者社区没有更好的图书馆,在规范作者允许这样一个无用的借口声称他们在技术上不违反规范,或者在 conscrypt 写了这么糟糕的实现。我把它留给你。结果是:它不起作用,它不会起作用,任何你想投诉、修复它的人都会把矛头指向别处。

具体来说,conscrypt 只会缓存所有内容,直到最后才执行加密,这很奇怪,但它就是这样工作的。有关证明和详细信息,请参阅this SO question/answer

解决方案:

我没有安卓手机来为你测试这个,我也很少做安卓开发,这使事情变得复杂——这里没有明显的解决办法。

  1. 考虑使用 BouncyCastle。 android 曾经附带它,并且仍然可以向后兼容,但我很确定它不会在这里可靠地使用,所以你必须考虑将 BC 库作为你的应用程序的一部分。考虑使用特定于 BC 的 API(不是基于 javax.crypto 构建的),这可以保证 android 不会对您进行切换并选择损坏的 conscrypt 实现。

  2. 在 conscrypt 中找到写得很糟糕的代码,并发送一个带有修复的拉取请求。如果您提交错误,他们可以整天指责,但他们的代码库的改进,一切准备就绪,只需按合并 - 他们更难否认。

  3. 更改您的代码以保存在块中。编写一个小协议:文件不是作为加密的 blob 存储的,而是作为一个加密单元序列存储的,其中每个单元是一个长度(作为一个大端序的 32 位整数),然后是这么多字节的加密,然后是另一个单元。一个密码单元序列总是以一个长度为 0 的单元结束,这告诉你它已经完成了。要写这些,选择一个任意大小(可能是 10MB:10*1024*1024),然后写入数据(我猜也可以全部在内存中完成 - conscrypt 在任何情况下都会这样做),然后你就知道你有多少数据了然后你可以通过写入长度来保存它,然后写入所有数据。然后继续,直到你的文件完成。

注意:您的代码到处都是并且看起来完全不安全。您有 'keyEncrypted' 和 'IVEncrypted',然后您不使用它们(除非这些文件不可用时异常中止)。相反,您直接使用“全局密钥”进行加密。

这意味着任何人都可以解密您的应用程序生成的任何内容,除了无处不在的应用程序 apk 之外什么都不需要。这肯定不是你想要的。也许您打算使用全局密钥来解密 iv 和密钥,然后使用解密的 iv/key 组合来加密或解密文件?请注意,这个 'encryptedIV.key' malarkey 是安全剧院。即使你修复了你的错误,它也是糟糕的加密:使用你的应用程序的人看到你将这些文件命名为encryptedKey,然后可能会认为它们是加密的。这是一个误导性的名称:它们是用公共信息加密的,而用公共信息加密某些东西是愚蠢的:你想完成什么?一个 2 位黑客有 5 秒的时间仔细阅读您的代码(即使您尝试对其进行混淆)会弄清楚这一点,提取“globalKey”,然后以同样的方式解密这些文件。

如果用户自己不需要输入任何密码来启动某些加密或解密,并且您不能使用 android 内置的安全功能,例如要求他们通过指纹扫描确认,然后接受任何有权访问手机磁盘内容或手机本身解锁的人都可以读取所有这些内容,期间,并通过不建议这些密钥文件被加密来明确这一点(因为,它们有效地不是,因为它们被加密的唯一东西是公开的密钥,这不算)。

【讨论】:

  • 好吧,我不是信息安全专家,但我必须澄清一些事情。当用户第一次启动应用程序时,他必须点击“生成密钥”按钮,然后应用程序要求用户输入密钥的密码。之后,生成一个随机密钥和IV,然后它们是使用用户的密码使用不同的算法加密并保存到 keyEncrypted 和 IVEncrypted 文件中。重新进入应用程序时,会提示用户输入密码以解密密钥和 IV。
  • 然后使用密码解密key和IV,将接收到的数据写入globalKey,而globalIV.globalKey和globalIV在代码中并不是预定义的值。为方便起见,我不会单独解密每个文件的密钥,而是仅在进入应用程序时解密。因此,要解密用户数据,您不需要应用程序的 apk 文件,而是需要两个文件(一个带有密钥,另一个带有 IV)和一个用户密码。
  • 然后应用程序要求用户输入密钥的密码。 - 提示用户输入密码以解密密钥和 IV。这一切听起来都不错。但是,如果您已经完成了获取这些文件中的数据并使用用户输入的 PW 对其进行解密的工作,那么您为什么要重新检查这些文件是否存在,然后不使用它们呢?
  • 因为用户可以在密钥尚未生成或从存储中导入时(第一次启动应用程序时)单击“加密文件”按钮。在这种情况下,globalKey 和 globalIV 为 null,无法进行加密或解密。
  • 听起来检查那些字段是否为null 会好一些,而不是检查文件是否存在。无论如何,好吧——你的代码在没有那个上下文的情况下只是有点误导,但是有了这个上下文——随便一瞥就可以了。这只留下了 conscrypt 在不首先缓冲整个数据流的情况下无法运行的不幸情况。
猜你喜欢
  • 1970-01-01
  • 2012-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多