【问题标题】:Java Cipher Decryption on large files大文件的Java密码解密
【发布时间】:2020-07-15 19:25:55
【问题描述】:

我正在尝试通过 Java 和以下代码解密文件。但是在解密大文件时会出现OutOfMemory 异常。我尝试更改为cipher.update,但程序将冻结而没有任何响应。

如何将其从 doFinal 更改为 update

public File decryptDataFile(File inputFile, File outputFile, File keyFile, String correlationId) {
        
        try {
            
            Security.addProvider(new BouncyCastleProvider());
            
            String key = new String(Files.readAllBytes(keyFile.toPath())).trim();
            
            byte[] byteInput = this.getFileInBytes(inputFile);

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
            
            byte[] salt = new byte[8];
            System.arraycopy(byteInput, 8, salt, 0, 8);
            SecretKeyFactory fact = SecretKeyFactory.getInstance("PBEWITHMD5AND256BITAES-CBC-OPENSSL", "BC");
            cipher.init(Cipher.DECRYPT_MODE, fact.generateSecret(new PBEKeySpec(key.toCharArray(), salt, 100)));

            byte[] data = cipher.doFinal(byteInput, 16, byteInput.length-16);
            
            OutputStream os = new FileOutputStream(outputFile);
            os.write(data);
            os.close();
            
            if(outputFile.exists()) {
                return outputFile;
            } else {
                return null;
            }
            
        } catch (IOException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException | InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException e) {
            logger.WriteLog(appConfig.getPlatform(), "INFO", alsConfig.getProjectCode(), correlationId, alsConfig.getFunctionId(), "SCAN_DECRYPT", e.getClass().getCanonicalName() + " - " + e.getMessage() );
            return null;
        }

    }

我的非工作版本:

Security.addProvider(new BouncyCastleProvider());
            
String key = new String(Files.readAllBytes(keyFile.toPath())).trim();
            
byte[] byteInput = this.getFileInBytes(inputFile);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
            
byte[] salt = new byte[8];
System.arraycopy(byteInput, 8, salt, 0, 8);
SecretKeyFactory fact = SecretKeyFactory.getInstance("PBEWITHMD5AND256BITAES-CBC-OPENSSL", "BC");
cipher.init(Cipher.DECRYPT_MODE, fact.generateSecret(new PBEKeySpec(key.toCharArray(), salt, 100)));
            
FileInputStream fis = new FileInputStream(inputFile);
FileOutputStream fos = new FileOutputStream(outputFile);
            
CipherInputStream cis = new CipherInputStream(fis, cipher);
            
int b;
byte[] d = new byte[8];
while((b = cis.read(d)) != -1) {
   fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
if(outputFile.exists()) {
    return outputFile;
} else {
    return null;
}

【问题讨论】:

  • 你读取了内存中的整个文件。你应该使用CipherOutputStream / CipherInputStream
  • 在 Baeldung 中找到了一个示例,但我无法弄清楚如何在我的代码中实现。如何使用CipherOutputStream / CipherInputStream
  • 请提供 encryptDataFile-method 以及我不知道 byteInput 的前 8 字节数据(您正在读取从 pos 8 到 16 = 8 字节的盐)中的内容并使用其余的作为 cipher.Update 的输入)
  • @MichaelFehr 感谢您的回复。加密实际上是由这个 shell 脚本完成的。 openssl enc -aes-256-cbc -e -salt -in ${tarFile} -out ${encTarFile} -pass file:./${KEY_RANDOM}
  • 请不要将更长的代码作为评论发布,而是编辑您的问题并在此处添加代码。您是否将 OpenSSL 前缀 (Salted__) 和盐与 FileInputStream 分开?此外,您的缓冲区太小了。

标签: java encryption


【解决方案1】:

前言:我无法使用您的原始方法解密使用您的 openssl-command 加密的文件

openssl enc -aes-256-cbc -e -salt -in ${tarFile} -out ${encTarFile} -pass file:./${KEY_RANDOM}

但以下方法甚至可以解码类似于原始方法的大文件 - 我测试了最大 1 GB 的文件。

编辑: 关于 OpenSSL 声明,值得一提的是,从 v1.1.0 开始,默认摘要已从 MD5 更改为 SHA256, 因此对于更高版本,必须显式设置 -md MD5 选项以与 Java 代码兼容。(感谢@Topaco)。

请记住,我不关心正确的文件路径

new FileInputStream(inputFile.toPath().toString())
and
new FileOutputStream(outputFile.toPath().toString())

由于我在本地工作并使用我的文件夹,也许您必须更改代码以“查找”您的文件。在这个例子中也没有异常处理。

代码行

byte[] ibuf = new byte[8096];

正在定义使用的缓冲区 - 较大的缓冲区使解密速度更快,但会消耗更多内存(8096 表示 8096 字节,而将完整文件读入内存并导致内存不足错误时为 1 GB)。

public static File decryptDataFileBuffered(File inputFile, File outputFile, File keyFile, String correlationId) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
        Security.addProvider(new BouncyCastleProvider());
        String key = new String(Files.readAllBytes(keyFile.toPath())).trim();
        byte[] salt = new byte[8];
        byte[] salted = new byte[8]; // text SALTED__
        try (FileInputStream in = new FileInputStream(inputFile.toPath().toString()); // i don't care about the path as all is lokal
             FileOutputStream out = new FileOutputStream(outputFile.toPath().toString())) // i don't care about the path as all is lokal
        {
            byte[] ibuf = new byte[8096]; // thats the buffer used - larger is faster
            int len;
            in.read(salted);
            in.read(salt);
            SecretKeyFactory fact = SecretKeyFactory.getInstance("PBEWITHMD5AND256BITAES-CBC-OPENSSL", "BC");
            SecretKey secretKey = fact.generateSecret(new PBEKeySpec(key.toCharArray(), salt, 100));
            System.out.println("secretKey length: " + secretKey.getEncoded().length + " data: " + bytesToHex(secretKey.getEncoded()));
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            while ((len = in.read(ibuf)) != -1) {
                byte[] obuf = cipher.update(ibuf, 0, len);
                if (obuf != null)
                    out.write(obuf);
            }
            byte[] obuf = cipher.doFinal();
            if (obuf != null)
                out.write(obuf);
         } catch (IOException | BadPaddingException | IllegalBlockSizeException e) {
            e.printStackTrace();
         }
        if (outputFile.exists()) {
            return outputFile;
        } else {
            return null;
        }
    }

Edit2: 正如@Topaco 所评论的那样,使用 CipherInput/OutputStream 缩短了代码并使其更具可读性,所以这里是代码:

public static File decryptDataFileBufferedCipherInputStream (File inputFile, File outputFile, File keyFile, String correlationId) throws
            IOException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidKeyException
    {
        Security.addProvider(new BouncyCastleProvider());
        String key = new String(Files.readAllBytes(keyFile.toPath())).trim();
        byte[] salt = new byte[8];
        byte[] salted = new byte[8]; // text SALTED__
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        try (FileInputStream in = new FileInputStream(inputFile.toPath().toString()); // i don't care about the path as all is lokal
             CipherInputStream cipherInputStream = new CipherInputStream(in, cipher);
             FileOutputStream out = new FileOutputStream(outputFile.toPath().toString())) // i don't care about the path as all is lokal
        {
            byte[] buffer = new byte[8192];
            in.read(salted);
            in.read(salt);
            SecretKeyFactory fact = SecretKeyFactory.getInstance("PBEWITHMD5AND256BITAES-CBC-OPENSSL", "BC");
            SecretKey secretKey = fact.generateSecret(new PBEKeySpec(key.toCharArray(), salt, 100));
            System.out.println("secretKey length: " + secretKey.getEncoded().length + " data: " + bytesToHex(secretKey.getEncoded()));
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            int nread;
            while ((nread = cipherInputStream.read(buffer)) > 0) {
                out.write(buffer, 0, nread);
            }
            out.flush();
        }
        if (outputFile.exists()) {
            return outputFile;
        } else {
            return null;
        }
    }

【讨论】:

  • 谢谢,它有效!我试图自己实现,但我解密的文件已损坏。我把我的非工作版本放在这里。我可以知道哪一部分出了问题吗?
  • 工作正常。最后一部分可以用CipherInputStream 更紧凑地实现。迭代次数(此处为 100)被忽略,因为隐式使用了 1。关于 OpenSSL 声明,值得一提的是,从 v1.1.0 开始,默认摘要已从 MD5 更改为 SHA256,因此对于更高版本,必须显式设置 -md MD5 选项以与 Java 代码兼容。
  • @Felix Wong:“你的非工作版本”仍然将完整的“inputFile”读取到“byteInput”,并且在流部分中它没有读取前 2 * 8 个字节的“标题”所以 CipherInputStream 也在尝试解密它 - 在 Edit2 中查看我的版本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-22
  • 1970-01-01
  • 2012-03-03
  • 1970-01-01
  • 2015-06-13
  • 2017-04-02
相关资源
最近更新 更多