Шифрование большого файла с помощью AES с использованием JAVA

Я проверил свой код с файлами меньше этого (10 МБ, 100 МБ, 500 МБ), и шифрование работает. Тем не менее, я сталкиваюсь с проблемами с файлами более 1 ГБ. Я создал большой файл (около 2 ГБ) и хочу зашифровать его с помощью AES с использованием JAVA, но я сталкиваюсь с этой ошибкой:

"Исключение в потоке"main" java.lang.OutOfMemoryError: пространство кучи Java"

Я пытался увеличить доступную память с помощью -Xmx8G, но без кубиков. Часть моего кода выглядит следующим образом

    File selectedFile = new File("Z:\\dummy.txt");         
    Path path = Paths.get(selectedFile.getAbsolutePath());       
    byte[] toencrypt = Files.readAllBytes(path);       
    byte[] ciphertext = aesCipherForEncryption.doFinal(toencrypt);
    FileOutputStream fos = new FileOutputStream(selectedFile.getAbsolutePath());
    fos.write(ciphertext);
    fos.close();

Насколько я могу судить, причина такого поведения заключается в том, что он пытается прочитать весь файл сразу, зашифровать его и сохранить в другом байтовом массиве, а не буферизовать и передавать его. Может кто-нибудь помочь мне с некоторыми советами по коду?

Я новичок в программировании, поэтому я не очень много знаю, любая помощь будет оценена.

3 ответа

Решение

Даже не пытайтесь читать целые большие файлы в память. Зашифруйте буфер за раз. Просто сделайте стандартный цикл копирования с соответствующим образом инициализированным CipherOutputStream обернутый вокруг FileOutputStream, Вы можете использовать это для всех файлов, не нужно делать из этого особого случая. Используйте буфер 8 КБ или более.

РЕДАКТИРОВАТЬ "Стандартный цикл копирования" в Java выглядит следующим образом:

byte[] buffer = new byte[8192];
int count;
while ((count = in.read(buffer)) > 0)
{
    out.write(buffer, 0, count);
}

где в этом случае out = new CipherOutputStream(new FileOutputStream(selectedFile), cipher),

      import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Cypher2021 {
    private static final String key = "You're an idiot!";
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES";

    public static void encrypt(File inputFile) {
        File encryptedFile = new File(inputFile.getAbsolutePath() + ".encrypted");
        encryptToNewFile(inputFile, encryptedFile);
        renameToOldFilename(inputFile, encryptedFile);
    }

    public static void decrypt(File inputFile) {
        File decryptedFile = new File(inputFile.getAbsolutePath() + ".decrypted");
        decryptToNewFile(inputFile, decryptedFile);
        renameToOldFilename(inputFile, decryptedFile);
    }

    private static void decryptToNewFile(File input, File output) {
        try (FileInputStream inputStream = new FileInputStream(input); FileOutputStream outputStream = new FileOutputStream(output)) {
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, secretKey);

            byte[] buff = new byte[1024];
            for (int readBytes = inputStream.read(buff); readBytes > -1; readBytes = inputStream.read(buff)) {
                outputStream.write(cipher.update(buff, 0, readBytes));
            }
            outputStream.write(cipher.doFinal());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void encryptToNewFile(File inputFile, File outputFile) {
        try (FileInputStream inputStream = new FileInputStream(inputFile); FileOutputStream outputStream = new FileOutputStream(outputFile)) {
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] inputBytes = new byte[4096];
            for (int n = inputStream.read(inputBytes); n > 0; n = inputStream.read(inputBytes)) {
                byte[] outputBytes = cipher.update(inputBytes, 0, n);
                outputStream.write(outputBytes);
            }
            byte[] outputBytes = cipher.doFinal();
            outputStream.write(outputBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void renameToOldFilename(File oldFile, File newFile) {
        if (oldFile.exists()) {
            oldFile.delete();
        }
        newFile.renameTo(oldFile);
    }
}

И тогда вы можете использовать его следующим образом:

      import java.io.File;

public class Main {

    public static void main(String[] args) {
        File file = new File("text.txt");
        Cypher2021.encrypt(file); // converts "text.txt" into an encrypted file
        Cypher2021.decrypt(file); // converts "text.txt" into an decrypted file
    }
}

Вы также можете еще больше упростить процесс, используя Encryptor4j: https://github.com/martinwithaar/Encryptor4j

File srcFile = new File("original.zip");
File destFile = new File("original.zip.encrypted");
String password = "mysupersecretpassword";
FileEncryptor fe = new FileEncryptor(password);
fe.encrypt(srcFile, destFile);

Эта библиотека использует потоковое шифрование, поэтому она не вызовет OutOfMemoryError даже с большими файлами. Кроме того, вместо использования паролей вы можете использовать свой собственный Key также.

Посмотрите пример на странице Github здесь: https://github.com/martinwithaar/Encryptor4j

Другие вопросы по тегам