我正在尝试使用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作为缓冲区大小,但由于相同的异常而失败。
我认为您的缓冲区大小导致了内存不足错误,您可以使用较小的数字来验证是否是原因。
您可以使用JDK提供的buffered I/O streams实现。
Java BufferedOutputStream
类用于缓冲输出流。它在内部使用缓冲区存储数据。与将数据直接写入流相比,它提高了效率。因此,它可以使性能更快。
这似乎与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。