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

问题描述 投票:0回答:2

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

它可以与较小的文件进行加密和解密工作正常。但是,当解密一个大文件(大小为1G)时,会出现内存不足的异常:

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数据传递给解密时,为什么它用完了堆内存?如何避免这种情况?

编辑:

实际上,我尝试将4k作为缓冲区大小,但由于相同的异常而失败。

java exception encryption out-of-memory heap
2个回答
0
投票

我认为您的缓冲区大小导致了内存不足错误,您可以使用较小的数字来验证是否是原因。

您可以使用JDK提供的buffered I/O streams实现。

Java BufferedOutputStream类用于缓冲输出流。它在内部使用缓冲区存储数据。与将数据直接写入流相比,它提高了效率。因此,它可以使性能更快。


0
投票

这似乎与JDK的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)

ByteArrayOutputStream内部写入GaloisCounterMode时发生内存不足错误。您使用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(),该缓冲区才被重置。>>

我没有看过这一点。您有可能通过刷新输出流来触发decryptFinal(),因此值得在循环的每次迭代中尝试cipherOutputStream.flush()(如果这样做,则可能更好地进行64k读取)。但是,除非您位于块边界(128位)上,否则可能什么都不做。

如果不起作用,您可能必须切换到其他密码模式,例如CBC。

© www.soinside.com 2019 - 2024. All rights reserved.