使用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的密钥派生规则,具体修复如下:
关键修复点
- 读取加密文件内置的盐值:OpenSSL AES加密文件格式为
Salted__(8字节ASCII) + 8字节盐值 + 密文,解密时需先读取前16字节,提取后8字节作为盐。 - 匹配OpenSSL版本的密钥派生算法:OpenSSL 1.1.0及以后默认用SHA256做密钥派生,之前版本用MD5,需根据加密环境选择对应的Digest。
- 更换解密流实现:用
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
相关产品推荐
相关产品推荐

