Java中使用BouncyCastle从PKCS7 PEM证书生成X509Certificate
解决方案
错误原因说明
你原有代码的两个问题根源:
- 直接将PEM字符串的字节传入
CMSSignedData构造方法:CMSSignedData要求输入DER编码的二进制PKCS7数据,而PEM是Base64编码带页眉页脚的文本格式,直接传字节会解析失败 PEMParser.readObject()返回null:大概率是BouncyCastle版本过旧,或者没有正确引入bcpkix依赖,部分旧版本BC将PKCS7解析为ContentInfo对象而不是直接返回CMSSignedData,所以需要做类型兼容判断。
你要实现的openssl pkcs7 -in client-certificate_pkcs7.pem -out client-certificate_chain.pem命令本质是从PEM格式的PKCS7包中提取所有X509证书,拼接成PEM格式的证书链,以下是可直接运行的实现:
前置依赖
首先确保你已经引入了正确的BouncyCastle依赖,以Maven为例:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.77</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.77</version> </dependency>
完整实现代码
import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.asn1.cms.ContentInfo; import java.io.StringReader; import java.io.StringWriter; import java.security.Security; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class Pkcs7Parser { static { // 注册BC安全提供者 Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); } public static String parsePkcs7ToCertChain(String pkcs7Pem) throws Exception { // 1. 解析PEM格式的PKCS7内容 PEMParser parser = new PEMParser(new StringReader(pkcs7Pem)); Object obj = parser.readObject(); parser.close(); CMSSignedData signedData; if (obj instanceof ContentInfo) { signedData = new CMSSignedData((ContentInfo) obj); } else if (obj instanceof CMSSignedData) { signedData = (CMSSignedData) obj; } else { throw new IllegalArgumentException("输入内容不是合法的PKCS7 PEM文件"); } // 2. 提取所有证书 Collection<X509CertificateHolder> certHolders = signedData.getCertificates().getMatches(null); List<X509Certificate> certList = new ArrayList<>(); JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC"); for (X509CertificateHolder holder : certHolders) { certList.add(converter.getCertificate(holder)); } // 3. 导出为PEM格式的证书链,和openssl输出格式完全一致 StringWriter sw = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(sw); for (X509Certificate cert : certList) { pemWriter.writeObject(cert); } pemWriter.close(); return sw.toString(); } public static void main(String[] args) throws Exception { String pkcs7Pem = "-----BEGIN PKCS7-----\nMIIR...WTEA\n-----END PKCS7-----\n"; String certChainPem = parsePkcs7ToCertChain(pkcs7Pem); // 输出的certChainPem就是和openssl命令输出一致的证书链内容 System.out.println(certChainPem); } }
常见问题排查
如果遇到PEMParser.readObject()返回null的情况,按以下顺序排查:
- 确认PEM内容格式正确:页眉
-----BEGIN PKCS7-----和页脚-----END PKCS7-----完整,Base64内容的换行符符合规范 - 升级BouncyCastle到1.70以上版本,旧版本对部分PKCS7标头识别存在兼容性问题
- 确认没有少引入
bcpkix-jdk15on包,PEMParser属于该包的内容
内容的提问来源于stack exchange,提问作者Edmund Ham
相关产品推荐
相关产品推荐

