使用JAVA对大文件进行AES加密

8
我已经测试过小于10mb、100mb和500mb的文件,加密工作正常。但是,当我想要加密1gb以上的大文件时出现了问题。 我使用JAVA生成了一个大文件(约2gb),并想使用AES加密它,但是我遇到了以下错误: "Exception in thread "main" java.lang.OutOfMemoryError: Java heap space" 我尝试通过使用-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个回答

13

不要试图将整个大文件读入内存。每次加密一个缓冲区。只需使用适当初始化的 CipherOutputStream 包装 FileOutputStream 进行标准复制循环即可。您可以将其用于所有文件,无需将其作为特殊情况处理。使用8k或更大的缓冲区。

EDIT 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)


1
“逐字节加密”:使用块密码加密是按块(AES:16字节)进行的。 - zaph
阅读了EJP的答案后,我仍然不确定“标准复制循环”是什么意思。我知道需要逐字节或块读取输入,但不确定如何处理循环。有人能指点我开始搜索的方向吗?至于密码输出流部分,它应该类似于以下内容:CipherOutputStream cos = new CipherOutputStream(FileOutputStream(selectedFile.getAbsolutePath()); - halcyondayz
@zaph 抱歉,我打错了,应该是“buffer”,但是 Cipher 会为您处理底层块大小。 - user207421

3
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
    }
}

安全警告 - 该代码使用了不安全的AES ECB模式和静态/硬编码密钥 - 不要在现实世界中使用此代码。 - Michael Fehr

3
你可以使用我编写的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#file-encryption

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接