使用BouncyCastle内存生成公私钥并签名及证书解析报错解决
问题描述
我希望使用依赖org.bouncycastle:bcprov-jdk15on:1.54,通过BouncyCastle生成公私钥。无需使用CLI,仅用Java代码在内存中完成操作并获取签名,不需要生成.cer或.csr文件。尝试了如下代码后出现错误:
import io.jsonwebtoken.lang.Assert; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.Test; import javax.xml.bind.DatatypeConverter; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.Signature; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.Base64; public class CertificateGenerateTestApiTest { String data; // complete certificate and metadata. We need to get of when we generate a certificate String signature; // signature of the certificate @Test public void certificateGenerationTest() { // Generate private and public keys KeyPair keyPair = generateCertificate(); PublicKey publicCertificate = keyPair.getPublic(); // Problem 1 - think how to populate "data" variable from public certificate // Problem 2 - think how to populate "signature" variable from public certificate // Get public key as byte[] in order later on to be stored into DB byte[] originalPublicCertificate = privateCertificateToByte(publicCertificate); // Let's say we received some certificate by some web client, and we want to verify it against our original certificate byte[] secondPublicCertificateReceivedByClient = cloneByteArrayData(originalPublicCertificate); // Verify certificate Assert.isTrue(verifyCertificate(secondPublicCertificateReceivedByClient)); } private KeyPair generateCertificate() { try { Security.addProvider(new BouncyCastleProvider()); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); keyPairGenerator.initialize(2048, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); return keyPair; } catch (NoSuchAlgorithmException | NoSuchProviderException e) { e.printStackTrace(); } return null; } private boolean verifyCertificate(byte[] secondPublicCertificateReceivedByClient) { PublicKey publicKey = getKeyPayload(byteToString(secondPublicCertificateReceivedByClient)); boolean verified = false; try { verified = verifySignature(data.getBytes("UTF8"), publicKey, DatatypeConverter.parseHexBinary(signature)); } catch (SignatureException | UnsupportedEncodingException e) { } catch (NoSuchAlgorithmException | InvalidKeyException e) { e.printStackTrace(); } return verified; } private boolean verifySignature(byte[] data, PublicKey key, byte[] signature) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { Signature signer = Signature.getInstance("SHA1withDSA"); signer.initVerify(key); signer.update(data); try { if (signer.verify(signature)) { return true; } } catch (SignatureException e) { e.printStackTrace(); } return false; } private PublicKey getKeyPayload(String certificateString) { try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); ByteArrayInputStream bis = new ByteArrayInputStream(certificateString.getBytes(StandardCharsets.UTF_8)); Certificate certificate = null; while (bis.available() > 0) { certificate = cf.generateCertificate(bis); } return (certificate == null) ? null : certificate.getPublicKey(); } catch (CertificateException e) { e.printStackTrace(); } return null; } private String byteToString(byte[] publicKeyBytes) { return Base64.getEncoder().encodeToString(publicKeyBytes); } private byte[] privateCertificateToByte(PublicKey publicCertificate) { return publicCertificate.getEncoded(); } /** * Clone byte[] data */ private byte[] cloneByteArrayData(byte[] sourceArray) { int sourceOffset = 0; /* Starting index in the source array */; int destinationOffset = 0; // Starting index in the destination array byte[] destinationArray = new byte[sourceArray.length]; for (int i = 0; i < sourceArray.length; i++) { destinationArray[destinationOffset + i] = sourceArray[sourceOffset + i]; } return destinationArray; } }
报错信息:
java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: Empty input
请问如何解决该错误,实现内存中的公私钥生成与签名验证功能?
问题分析与解决方案
核心问题梳理
- 混淆公钥与证书:公钥的
getEncoded()返回的是SubjectPublicKeyInfo格式数据,不是完整的X.509证书,你强行用证书解析逻辑处理公钥,直接导致解析失败。 - 签名算法不匹配:生成的是RSA密钥对,但签名用了
SHA1withDSA,DSA与RSA算法体系不兼容。 - 核心变量未赋值:
data和signature始终为空,验证逻辑根本无法执行。
修正后的完整代码
import io.jsonwebtoken.lang.Assert; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; public class KeyPairSignatureTest { private String signedData; // 待签名的原始数据 private String signature; // Base64编码的签名 @Test public void keyPairAndSignatureTest() { // 生成RSA公私钥对 KeyPair keyPair = generateRsaKeyPair(); Assert.notNull(keyPair, "密钥对生成失败"); // 定义待签名数据 signedData = "需要签名的测试内容"; // 用私钥生成签名 signature = generateSignature(signedData, keyPair.getPrivate()); Assert.notNull(signature, "签名生成失败"); // 模拟接收的公钥(直接存储公钥编码) byte[] receivedPublicKeyBytes = keyPair.getPublic().getEncoded(); PublicKey receivedPublicKey = restorePublicKey(receivedPublicKeyBytes); Assert.notNull(receivedPublicKey, "公钥还原失败"); // 验证签名 boolean isVerified = verifySignature(signedData, receivedPublicKey, signature); Assert.isTrue(isVerified, "签名验证失败"); } private KeyPair generateRsaKeyPair() { try { // 避免重复注册BouncyCastle提供者 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC"); keyGen.initialize(2048, new SecureRandom()); return keyGen.generateKeyPair(); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { throw new RuntimeException("生成密钥对失败", e); } } private String generateSignature(String data, PrivateKey privateKey) { try { // 使用SHA256withRSA替代不安全的SHA1 Signature signer = Signature.getInstance("SHA256withRSA", "BC"); signer.initSign(privateKey); signer.update(data.getBytes(StandardCharsets.UTF_8)); byte[] signatureBytes = signer.sign(); return Base64.getEncoder().encodeToString(signatureBytes); } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException e) { throw new RuntimeException("生成签名失败", e); } } private boolean verifySignature(String data, PublicKey publicKey, String signatureBase64) { try { Signature verifier = Signature.getInstance("SHA256withRSA", "BC"); verifier.initVerify(publicKey); verifier.update(data.getBytes(StandardCharsets.UTF_8)); byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64); return verifier.verify(signatureBytes); } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException e) { throw new RuntimeException("验证签名失败", e); } } private PublicKey restorePublicKey(byte[] publicKeyBytes) { try { // 直接用KeyFactory还原公钥,无需证书解析 KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); return keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes)); } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) { throw new RuntimeException("还原公钥失败", e); } } }
关键修正说明
- 移除证书逻辑:直接通过
KeyFactory解析公钥编码数据,无需依赖X.509证书。 - 匹配算法体系:使用
SHA256withRSA签名算法,和RSA密钥对完全兼容,同时替换不安全的SHA1。 - 完善签名流程:明确待签名数据,用私钥生成Base64编码的签名,再用公钥验证有效性。
- 优化异常处理:替换
printStackTrace为抛出运行时异常,确保测试能及时感知错误。 - 避免重复注册Provider:添加判断逻辑,防止多次注册BouncyCastle提供者。
内容的提问来源于stack exchange,提问作者Peter Penzov
相关产品推荐
相关产品推荐

