使用 JAVA 使用 AES 加密大文件

Posted

技术标签:

【中文标题】使用 JAVA 使用 AES 加密大文件【英文标题】:Encrypting a large file with AES using JAVA 【发布时间】:2016-03-30 13:24:57 【问题描述】:

我已经用小于这个(10mb、100mb、500mb)的文件测试了我的代码,并且加密有效。但是,我遇到了大于 1gb 的文件的问题。 我生成了一个大文件(大约 2gb),我想使用 JAVA 使用 AES 对其进行加密,但我遇到了这个错误:

“线程“主”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();

据我所知,它这样做的原因是它试图一次读取整个文件,对其进行加密,并将其存储到另一个字节数组中,而不是缓冲和流式传输。可以有人帮我提供一些代码提示吗?

我是编码初学者,所以我不太了解,任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

甚至不要尝试将整个大文件读入内存。一次加密一个缓冲区。只需使用适当初始化的CipherOutputStream 围绕FileOutputStream 进行标准复制循环。您可以将其用于所有文件,无需对其进行特殊处理。使用 8k 或更大的缓冲区。

编辑 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).

【讨论】:

“一次加密一个字节”:使用块密码加密是按块(AES:16 字节)。 阅读 EJP 的回答后,我仍然不确定“标准复制循环”是什么意思。我知道我需要一次读取一个字节或分块读取输入。我不确定如何处理循环。有人可以指出我开始搜索的方向吗?至于它的密码输出流部分,它应该是这样的: CipherOutputStream cos = new CipherOutputStream(FileOutputStream(selectedFile.getAbsolutePath()); @zaph 糟糕,“缓冲区”的拼写错误,但Cipher 会为您处理底层块大小。【参考方案2】:

您还可以使用我编写的 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

【讨论】:

【参考方案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
    

【讨论】:

安全警告 - 代码使用 UNSECURE AES ECB 模式和静态/硬编码密钥 - 请勿在现实世界中使用此代码。

以上是关于使用 JAVA 使用 AES 加密大文件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 AES 加密创建 Java 密钥库 (.jks) 文件

使用 openssl smime 加密大文件

用php加密大文件的最佳方法

你如何在 Go 中加密大文件/字节流?

如何使用 AES 在 Java 中加密文件 [重复]

java中的Aes解密-填充问题