【问题标题】:Encrypt and decrypt large file with AES使用 AES 加密和解密大文件
【发布时间】:2020-04-17 17:11:55
【问题描述】:

我正在尝试使用 AES 加密一个大文件,然后对其解密并与原始文件进行比较。

本课总结了这项工作。它适用于 .txt 文件,但不是适用于 .mp3、.pdf 等。

我们将不胜感激。

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

public class LargeFileEncryptionTest7 {

    protected static String FOLDER_PATH = "C:/temp/";
    protected static String FILE = "some-large-file";
    protected static String EXT = ".mp3"; //Works for .txt, but not for .mp3 or .pdf

    public static void main(String[] args) throws Exception {

        //Load file to encrypt
        byte[] largeFileBytes = loadFile(FOLDER_PATH + FILE + EXT);
        String largeFileString = new String(largeFileBytes);

        //Encrypt file with AES
        AESUtils aesUtils = new AESUtils();
        byte[] secretKey = aesUtils.generateSecretKey();
        aesUtils.setSecretKey(secretKey);
        byte[] largeFileEncBytes = aesUtils.encrypt(largeFileString);

        //Save encrypted file
        saveFile(largeFileEncBytes, FOLDER_PATH + FILE + "-encrypted" + EXT);

        //Load encrypted file
        byte[] largeFileEncBytesToCheck = loadFile(FOLDER_PATH + FILE + "-encrypted" + EXT);

        //Decrypt file      
        byte[] largeFileBytesToCheck = aesUtils.decrypt(largeFileEncBytesToCheck);
        String largeFileStringToCheck = new String(largeFileBytesToCheck);

        //Save decrypted file
        saveFile(largeFileBytesToCheck, FOLDER_PATH + FILE + "-decrypted" + EXT);

        //Check strings
        //System.out.println("Original content: " + largeFileStringToCheck);
        if (largeFileStringToCheck.equals(largeFileString)) {
            System.out.println("OK  :-) ");
        } else {
            System.out.println("KO  :-( ");
        }                       
    }

    private static void saveFile(byte[] bytes, String fileName) throws Exception {
        FileOutputStream fos = new FileOutputStream(fileName);
        fos.write(bytes);
        fos.close();
    }

    private static byte[] loadFile(String fileName) throws Exception {
        FileInputStream fis = new FileInputStream(fileName);
        int numBtyes = fis.available();
        byte[] bytes = new byte[numBtyes];
        fis.read(bytes);
        fis.close();
        return bytes;
    }

}

【问题讨论】:

  • 您应该排除编码作为一个因素。要么在任何地方明确提及编码,要么不再使用旧的繁琐文件 api,而是使用 NIO。以您的loadFile 方法为例,将其替换为Files.readAllBytes(Paths.get(fileName)),就是这样。并且它将始终使用 UTF-8。与saveFile 类似,只是Files.write(Paths.get(fileName), bytes),完成。在创建字符串时还要提及编码new String(bytes, StandardCharset.UTF_8)。只要确保它在所有地方都是相同的编码,否则你会遇到问题。
  • 最后,不要比较字符串。比较原始数据byte[]s,否则您将再次依赖编码。如果字节仍然相等,那么加密/解密很顺利,只是你没有为字符串表示指定正确的编码。
  • 尝试将任意二进制数据转换为String 似乎是个坏主意。 AESUtils 是否有接受 byte[] 作为输入的方法?如果没有,你能加一个吗?
  • 正如迈克尔所说。而且“坏主意”是轻描淡写。任何承诺在Strings 上执行加密的库/实用程序要么注定会让你失败(例如,由不知情的人提供),要么通过阅读文档做出您应该了解的假设(例如,它仅在字符串输入 Base64 编码字符串时才有效)。加密是字节级别的过程,而不是字符级别的。再次强调前一点,将任何二进制文件转换为字符串(甚至不指定编码)是要求数据损坏,并且有一天会崩溃。不要。

标签: java encryption aes


【解决方案1】:

我发现您的解决方案存在 2 个问题:

您的代码:

   int numBtyes = fis.available();
    byte[] bytes = new byte[numBtyes];
    fis.read(bytes);

这实际上并不能保证阅读全部内容。同样,在加密大文件时(当不能保证它可以放入内存时),您可能不想将所有内容读入内存。

在加密/解密大型内容(无限制)时,您可能需要使用以下内容:

byte[] buff = new byte[BUFFERSIZE];
for(int readBytes=in.read(buff); readBytes>-1;readBytes=in.read(buff)) {
  out.write(cipher.update(buff,0, readBytes);
}
out.write(cipher.doFinal());

或查看 CipherOutputStream 和 CipherInputStream

另一个问题是比较:

String largeFileStringToCheck = new String(largeFileBytesToCheck);

如前所述,这是一种比较内容的糟糕方式。在 Java 中,字符串仅用于可打印字符,当尝试“字符串化”任何字节数组时,将应用编码并且不可打印字符可能会被“丢弃”。

为了简单的比较(有字节数组),你可以使用Arrays.equals方法

在比较非常大的内容时(当您可能不确定它是否适合您的 RAM 内存时),通常最好创建一个 message hash 并比较哈希值

编辑:如果您真的想查看/打印/比较密文为字符串,您可以对二进制数据进行编码,您可以查看Base64 encoding

【讨论】:

    【解决方案2】:

    如果有人感兴趣,我会在这里提出最终解决方案。

    它的灵感来自于人们所做的一些 cmets。主要避免使用Strings,使用byte[]:

    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.Arrays;
    
    import javax.crypto.Cipher;
    import javax.crypto.KeyGenerator;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    
    public class LargeFileEncryptionTest11 {
    
        private static final String FOLDER_PATH = "C:/temp/";
        private static final String FILE = "some-large-file";
        private static final String EXT = ".pdf";
    
        private static final String ENCRYPTION_ALGORITHM = "AES";
        private static final int KEY_SIZE = 128; // 192 and 256 bits may not be available
    
        public static void main(String[] args) throws Exception {
            //Common stuff to encrypt/decrypt
            KeyGenerator kgen = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM);
            kgen.init(KEY_SIZE); 
            SecretKey skey = kgen.generateKey();
            byte[] secretKey = skey.getEncoded();
            SecretKeySpec skeySpec = new SecretKeySpec(secretKey, ENCRYPTION_ALGORITHM);
            Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
    
            //Load file to encrypt
            byte[] largeFileBytes = Files.readAllBytes(Paths.get(FOLDER_PATH + FILE + EXT));
    
            //Encrypt file
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
            byte[] largeFileEncBytes = cipher.doFinal(largeFileBytes);
    
            //Save encrypted file
            Files.write(Paths.get(FOLDER_PATH + FILE + "-encrypted" + EXT), largeFileEncBytes);
    
            //Load encrypted file
            byte[] largeFileEncBytesToCheck = Files.readAllBytes(Paths.get(FOLDER_PATH + FILE + "-encrypted" + EXT));
    
            //Decrypt file      
            cipher.init(Cipher.DECRYPT_MODE, skeySpec);
            byte[] largeFileBytesToCheck = cipher.doFinal(largeFileEncBytesToCheck);
    
            //Save decrypted file
            Files.write(Paths.get(FOLDER_PATH + FILE + "-decrypted" + EXT), largeFileBytesToCheck);
    
            //Compare results
            if (Arrays.equals(largeFileBytes, largeFileBytesToCheck)) {
                System.out.println("OK  :-) ");
            } else {
                System.out.println("KO  :-( ");
            }                       
        }
    
    }
    

    【讨论】:

    • 此解决方案的主要缺点是它需要至少两倍于 RAM 中文件的大小。因此,考虑到问题的标题(“大”可能意味着几 GB 甚至更多),这个解决方案的设计并不好。
    • 对于我们的项目,我认为它可以工作。我们只需要加密最长约 15 分钟的 mp3 录音。
    猜你喜欢
    • 2021-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-20
    • 2014-01-05
    • 2019-01-06
    相关资源
    最近更新 更多