Я пытаюсь зашифровать файл, передав файловый поток, результирующий поток и зашифрованный объект, который содержит информацию о типе шифрования. Вот что я пробовал:
private void processFile(Cipher cipher,InputStream inputStream, OutputStream outputStream){
byte[] tempInputBuffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(tempInputBuffer)) != -1) {
byte[] tempOutputBuffer = cipher.update(tempInputBuffer, 0, len);
if ( tempOutputBuffer != null ) outputStream.write(tempOutputBuffer);
}
byte[] obuf = cipher.doFinal();
if ( obuf != null ) outputStream.write(obuf);
}catch (IOException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
}
Это функция, из которой я вызываю processFile
public ByteArrayOutputStream encryptFile( InputStream fileStream, PublicKey publicKey ) throws EmprisException {
ByteArrayOutputStream encryptedFileStream = null;
KeyGenerator keyGenerator;
try {
//generate AES key
keyGenerator = KeyGenerator.getInstance(FileUploadDownloadConstants.AES_ALGORITHM);
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
byte[] initializationVector = new byte[16];
SecureRandom srandom = new SecureRandom();
srandom.nextBytes(initializationVector);
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
//encrypting the aes key using rsa public key and adding it to a file
encryptedFileStream = new ByteArrayOutputStream();
Cipher cipherRSA = Cipher.getInstance(FileUploadDownloadConstants.RSA_TRANSFORMATION);
cipherRSA.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] secretKeyBytes = cipherRSA.doFinal(secretKey.getEncoded());
encryptedFileStream.write(secretKeyBytes);
encryptedFileStream.write(initializationVector);
//call processFile to encrypt the file
Cipher cipherAES = Cipher.getInstance(FileUploadDownloadConstants.AES_TRANSFORMATION);
cipherAES.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
processFile(cipherAES, fileStream, encryptedFileStream);
encryptedFileStream.close();
logger.logExiting(METHOD_NAME);
return encryptedFileStream;
}catch(Exception e) {
e.printStackTrace();
}
}
Это трассировка стека, которую я получаю:
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 java.io.OutputStream.write(OutputStream.java:75)
У меня ошибка нехватки памяти в строке if ( tempOutputBuffer != null ) outputStream.write(tempOutputBuffer);
Что-то не так со способом записи в выходной поток? И это происходит, когда я пытаюсь увеличить файлы, например, около 15 Мб. Буду очень признателен за вашу помощь и заранее спасибо.
На первый взгляд, то, что вы делаете, выглядит правильно с точки зрения реализации потоковой передачи. Но имейте в виду, что ByteArrayOutputStream записывает содержимое внутри кучи. Следовательно, пока вы удерживаете эту переменную, у вас есть большой (например, 15 МБ) фрагмент памяти, который остается в памяти. Это может складываться. Что вы делаете с возвращенным encryptedFileStream, который сохраняет этот фрагмент памяти? А откуда берется InputStream?
Я вижу, что InoutFileStream - это аргумент для encryptFile. Сколько файлов вы открываете одновременно?
Вы пробовали увеличить кучу, как пример это?
Ваш код копирует содержимое файла в массив байтов, хранящийся в памяти. Однако 15 Мб - это не так уж и много. Вы обрабатываете много файлов одновременно / параллельно?
Да, этот код обрабатывает поток только для одного файла за раз.
@GPI зашифрованный поток - это то, что я возвращаю вызывающей функции, которая, в свою очередь, сохраняет зашифрованный поток файла в файловой системе.
@Jake В случаях, когда мне нужно зашифровать много файлов, я просто передаю поток одного файла, шифрую его, а затем отправляю поток следующего.
Я предполагаю, что у вас все еще есть ссылка на ранее обработанные файлы или потоки в другом месте вашего кода.
@AravindS в любом случае возвращает выходной поток не имеет реального смысла (если вызывающий также не записывает в него). Вы должны либо принять выходной поток для записи в качестве аргумента, либо вернуть простой byte []. Увеличение кучи, безусловно, поможет, но с точки зрения программиста необходимо знать, где находится память. Проследите, что такое входной поток и когда он собирается сборщиком мусора, и то же самое для вывода.
@GPI на самом деле интерфейсу нужен выходной поток. Я предполагаю, что ссылки на эти переменные хранятся в куче через цикл, в котором идет процесс шифрования.
@AravindS у вас может не быть шанса, кроме как выполнить то, что просит "интерфейс". Но каковы методы OutputStream? write(). Что может делать фронтенд с write? Ничего, потому что, конечно, он не перезаписывает / после процесса шифрования. Так что с вероятностью 99% то, что он делает, - это toByteArray. Это означает, что на самом деле фронт хочет байтов, а не потоков. Возврат ByteArrayOutputStream - это своего рода запах кода: 1) потому что это реализация, а не интерфейс, 2) потому что возвращение outputStream имеет смысл только тогда, когда вы хотите вызвать write(). А фронтенд, я уверен, не делает.
@GPI Вы правы. И подождите, я неправильно заполнил комментарий, этот метод, который я написал, будет только шифровать и возвращать зашифрованный поток. Вызывающий метод будет использовать этот выходной поток и сохранять его в файловой системе. Раньше я ошибался, извините за это.




Вы пробовали установить минимальный размер кучи для вашей JVM? См. Этот связанный вопрос:
Каковы параметры Xms и Xmx при запуске JVM?
Значение по умолчанию может быть слишком низким для вашего примера.
Да, я только что исследовал это, но я пытаюсь запустить эту программу, максимально не изменяя конфигурации.
В этом случае вы можете рассмотреть возможность записи в FileOutputStream вместо этого, потому что это не будет потреблять столько памяти кучи.
Как вы вызываете эту функцию? Какой OutputStream вы передаете