You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用BouncyCastle实现兼容OpenSSL的Java AES256解密报错求助

使用BouncyCastle解密OpenSSL AES256加密文件时出现填充块损坏错误

问题描述

我正在编写Java代码,通过BouncyCastle解密AES256加密文件,要求兼容OpenSSL的解密方式:

  • 密钥存储在s_key文件中,需先读取该密钥
  • 最终要完成指定加密文件的解密操作

以下是我编写的代码,运行时抛出填充块损坏的错误:

import java.io.*;
import java.nio.charset.StandardCharsets;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import org.apache.commons.io.FileUtils;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;

import javax.crypto.NoSuchPaddingException;


public class test5_encrypt {

    public static void main(String[] args) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException {


        File file = new File("/home/roxane/key");
        String passwordStr = FileUtils.readFileToString(file, "UTF-8");

        String outputPath = "/home/roxane/test1";
        String inputPath = "/home/roxane/test";


        SecureRandom random = new SecureRandom();
        byte salt[] = new byte[8];
        random.nextBytes(salt);

// Derive 32 bytes key (AES_256) and 16 bytes IV
        byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
        OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
        pbeGenerator.init(password, salt);
        ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128);// keySize, ivSize in bits
        System.out.println(parameters.getIV());


// Decrypt with AES-256
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {

           // Encrypt chunkwise (for large data)
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
            cipher.init(false, parameters);
            try (FileInputStream fis = new FileInputStream(inputPath);
                 CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {
                int bytesRead = -1;
                byte[] buffer = new byte[64 * 1024 * 1024];
                while ((bytesRead = fis.read(buffer)) != -1) {
                    cos.write(buffer, 0, bytesRead);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);

        }


    }
}

错误信息

Exception in thread "main" java.lang.RuntimeException: org.bouncycastle.crypto.io.InvalidCipherTextIOException: Error finalising cipher data
    at decrypt.test5_encrypt.main(test5_encrypt.java:61)
Caused by: org.bouncycastle.crypto.io.InvalidCipherTextIOException: Error finalising cipher data
    at org.bouncycastle.crypto.io.CipherOutputStream.close(Unknown Source)
    at decrypt.test5_encrypt.main(test5_encrypt.java:59)
Caused by: org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted

问题原因与修复方案

pad block corrupted错误的核心原因是解密时生成了随机盐值,但OpenSSL加密的文件会将盐值固定存储在文件开头,解密时必须读取该盐值而非随机生成。同时要匹配OpenSSL的密钥派生规则,具体修复如下:

关键修复点

  1. 读取加密文件内置的盐值:OpenSSL AES加密文件格式为Salted__(8字节ASCII) + 8字节盐值 + 密文,解密时需先读取前16字节,提取后8字节作为盐。
  2. 匹配OpenSSL版本的密钥派生算法:OpenSSL 1.1.0及以后默认用SHA256做密钥派生,之前版本用MD5,需根据加密环境选择对应的Digest。
  3. 更换解密流实现:用CipherInputStream替代CipherOutputStream,更适配解密场景,避免填充收尾错误。

修正后的完整代码

import java.io.*;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;

public class AESDecryptor {

    public static void main(String[] args) throws IOException {
        // 读取密钥文件,注意去除可能的换行符/空格
        File keyFile = new File("/home/roxane/key");
        String passwordStr = FileUtils.readFileToString(keyFile, StandardCharsets.UTF_8).trim();
        byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);

        String encryptedFilePath = "/home/roxane/test";
        String decryptedFilePath = "/home/roxane/test1";

        try (FileInputStream fis = new FileInputStream(encryptedFilePath);
             FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {

            // 读取OpenSSL加密文件的盐值头部
            byte[] saltHeader = new byte[16];
            fis.read(saltHeader);
            byte[] salt = new byte[8];
            System.arraycopy(saltHeader, 8, salt, 0, 8);

            // 初始化密钥派生器:根据OpenSSL版本选择Digest
            // OpenSSL 1.0.x版本用MD5Digest,1.1.0+版本用SHA256Digest
            OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest());
            // OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new SHA256Digest());

            pbeGenerator.init(password, salt);
            ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128);

            // 初始化解密器
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
            cipher.init(false, parameters);

            // 使用CipherInputStream处理解密流
            try (CipherInputStream cis = new CipherInputStream(fis, cipher)) {
                byte[] buffer = new byte[64 * 1024];
                int bytesRead;
                while ((bytesRead = cis.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

额外说明

  • 密钥读取时调用trim(),避免文件中多余的换行符或空格导致密钥不匹配。
  • 如果不确定加密时的OpenSSL版本,可以先尝试MD5,若失败再换SHA256。
  • 大文件解密时,64KB的缓冲区大小足够平衡性能与内存占用,无需用64MB的超大缓冲区。

内容的提问来源于stack exchange,提问作者RoxaneFelton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 06:48:26