使用Bouncy Castle在Java中验签失败问题求助
操作背景
我用OpenSSL生成了证书test.cer和密钥库test.p12,接着用Bouncy Castle完成签名流程并生成signed.p7s文件,但验签时始终返回false,已替换{JAVA_HOME}/lib/security下的local_policy.jar,求排查原因。
签名流程代码
- 加载X.509证书
Security.addProvider(new BouncyCastleProvider()); CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC"); X509Certificate certificate = (X509Certificate) certFactory .generateCertificate(new FileInputStream("test.cer"));
- 读取源文件与私钥
byte[] fileContent = Files.readAllBytes(new File("source.txt").toPath()); char[] keystorePassword = "password".toCharArray(); char[] keyPassword = "password".toCharArray(); KeyStore keystore = KeyStore.getInstance("PKCS12"); keystore.load(new FileInputStream("test.p12"), keystorePassword); PrivateKey privateKey = (PrivateKey) keystore.getKey("archive", keyPassword);
- 生成签名文件
signed.p7s
byte[] signedMessage = null; List<X509Certificate> certList = new ArrayList<X509Certificate>(); CMSTypedData cmsData = new CMSProcessableByteArray(data); certList.add(signingCertificate); Store certs = new JcaCertStore(certList); CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator(); ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(signingKey); cmsGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder().setProvider("BC") .build()).build(contentSigner, signingCertificate)); cmsGenerator.addCertificates(certs); CMSSignedData cms = cmsGenerator.generate(cmsData, true); byte[] signedMessage = cms.getEncoded();
File targetFile = new File(Paths.get(fileDir).getParent().toString(), "/" + "signed.p7s"); targetFile.createNewFile(); Files.write(Paths.get(targetFile.getAbsolutePath()), signedMessage);
验签代码
byte[] signedData = Files.readAllBytes(new File("signed.p7s").toPath()); ByteArrayInputStream bIn = new ByteArrayInputStream(signedData); ASN1InputStream aIn = new ASN1InputStream(bIn); CMSSignedData s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject())); aIn.close(); bIn.close(); Store certs = s.getCertificates(); SignerInformationStore signers = s.getSignerInfos(); Collection<SignerInformation> c = signers.getSigners(); SignerInformation signer = c.iterator().next(); Collection<X509CertificateHolder> certCollection = certs.getMatches(signer.getSID()); Iterator<X509CertificateHolder> certIt = certCollection.iterator(); X509CertificateHolder certHolder = certIt.next(); boolean verifResult = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(certHolder));
几个可能的问题点和解决办法
1. 签名用的数据源不匹配
签名代码里用了CMSProcessableByteArray(data),但前面明明读取了源文件内容到fileContent变量,如果data不是fileContent,等于签名的内容和验签预期的源内容不一致,直接导致验签失败。
修改方式:将data替换为fileContent:
CMSTypedData cmsData = new CMSProcessableByteArray(fileContent);
2. 签名代码变量名不对应
你前面加载的证书是certificate、私钥是privateKey,但签名代码里用了未定义的signingCertificate和signingKey,如果这些变量没有正确赋值,等于用错误的证书/私钥签名,验签肯定失败。
修改方式:替换为正确的变量名:
certList.add(certificate); ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey); cmsGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()) .build(contentSigner, certificate));
3. 验签未指定Bouncy Castle Provider
验签代码里new JcaSimpleSignerInfoVerifierBuilder()没有指定Provider,可能默认Provider无法正确识别Bouncy Castle生成的签名格式。
修改方式:添加Provider指定:
boolean verifResult = signer.verify(new JcaSimpleSignerInfoVerifierBuilder() .setProvider("BC") .build(certHolder));
4. 签名文件写入路径错误
生成signed.p7s时的路径拼接用了"/" + "signed.p7s",可能导致路径格式错误(比如出现重复斜杠),进而导致写入的签名文件损坏。
修改方式:用更可靠的路径生成方式,且Files.write会自动创建文件,无需手动调用createNewFile():
File targetFile = new File(Paths.get(fileDir).getParent().resolve("signed.p7s").toString()); Files.write(targetFile.toPath(), signedMessage);
5. 证书与私钥不配对
先确认test.cer的证书和test.p12中alias="archive"的私钥是配对的,用OpenSSL命令验证:
# 查看证书的公钥 openssl x509 -in test.cer -pubkey -noout # 查看p12中私钥对应的公钥 openssl pkcs12 -in test.p12 -nocerts -nodes | openssl rsa -pubout
如果两个公钥不一致,说明证书和私钥不匹配,需要重新生成。
内容的提问来源于stack exchange,提问作者Jelly

