java - 使用密码解密大文件时出现内存不足异常

标签 java exception encryption out-of-memory heap-memory

我试图使用 javax.crypto 下的类和用于输入/输出的文件流来实现加密/解密程序。 为了限制内存使用,我使用 -Xmx256m 参数运行。

它适用于较小文件的加密和解密。 但是在解密超大文件(1G大小)时,会出现out of memory异常:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3236)
    at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
    at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
    at com.sun.crypto.provider.GaloisCounterMode.decrypt(GaloisCounterMode.java:505)
    at com.sun.crypto.provider.CipherCore.update(CipherCore.java:782)
    at com.sun.crypto.provider.CipherCore.update(CipherCore.java:667)
    at com.sun.crypto.provider.AESCipher.engineUpdate(AESCipher.java:380)
    at javax.crypto.Cipher.update(Cipher.java:1831)
    at javax.crypto.CipherOutputStream.write(CipherOutputStream.java:166)

解密代码如下:

private final int _readSize = 0x10000;//64k

...

GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(gcmTagSize, iv);
Key keySpec = new SecretKeySpec(key, keyParts[0]);
Cipher decCipher = Cipher.getInstance("AES/GCM/PKCS5Padding");

decCipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);

try (InputStream fileInStream = Files.newInputStream(inputEncryptedFile);
    OutputStream fileOutStream = Files.newOutputStream(outputDecryptedFile)) {
    try (CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutStream, decCipher)) {
        long count = 0L;
        byte[] buffer = new byte[_readSize];

        int n;
        for (; (n = fileInStream.read(buffer)) != -1; count += (long) n) {
            cipherOutputStream.write(buffer, 0, n);
        }
    }
}

像 gcmTagSize 和 iv 这样的关键参数是从一个关键文件中读取的,它适用于较小的文件,比如一些大约 50M 的文件。

据我了解,每次只有 64k 数据传递给 decipher,为什么会用完堆内存? 我怎样才能避免这种情况?

编辑:

实际上我尝试过使用 4k 作为缓冲区大小,但失败并出现同样的异常。

编辑 2:

经过更多测试,它可以处理的最大文件大小约为堆大小的 1/4。例如,如果您设置 -Xmx256m,大​​于 64M 的文件将无法解密。

最佳答案

这似乎是 GCM 模式实现的问题。我不确定您是否可以解决它。

如果您查看堆栈跟踪:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3236)
    at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
    at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
    at com.sun.crypto.provider.GaloisCounterMode.decrypt(GaloisCounterMode.java:505)

GaloisCounterMode 中写入 ByteArrayOutputStream 时发生内存不足错误。您使用了 FileOutputStream,因此您没有显示正确的代码,或者此 ByteArrayStream 已在内部使用。

如果您查看 source for GaloisCounterMode您会看到它定义了一个内部 ByteArrayOutputStream(它实际上定义了两个,但我认为这就是问题所在):

    // buffer for storing input in decryption, not used for encryption
    private ByteArrayOutputStream ibuffer = null;

然后,稍后,它将字节写入此流。注意代码注释。

    int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
        processAAD();

        if (len > 0) {
            // store internally until decryptFinal is called because
            // spec mentioned that only return recovered data after tag
            // is successfully verified
            ibuffer.write(in, inOfs, len);
        }
        return 0;
    }

该缓冲区直到 decryptFinal() 才被重置。


编辑:查看this CSx answer看起来 GCM 需要缓冲整个流。如果您有大文件且内存不足,那将是一个非常糟糕的选择。

我认为您最好的解决方案是切换到 CBC 模式。

关于java - 使用密码解密大文件时出现内存不足异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61792534/

相关文章:

java - Java中使用AES算法加密

node.js - 开源程序中的加密?

java - OrientDB ETL导入: 'skip' operator throws exception

java - java中的正则表达式,共享符号

C++,捕获错误作为引用时的段错误

c++ - 删除指向流的类成员指针的段错误

c# - 返回值的方法不能传递给异常处理程序

java - 使用RSA在java中加密和解密大字符串

填充对象数组时出现 java.lang.NullPointerException

java - 编写一个正则表达式来确定匹配数并提取所需的字符串