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

使用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

请问如何解决该错误,实现内存中的公私钥生成与签名验证功能?


问题分析与解决方案

核心问题梳理

  1. 混淆公钥与证书:公钥的getEncoded()返回的是SubjectPublicKeyInfo格式数据,不是完整的X.509证书,你强行用证书解析逻辑处理公钥,直接导致解析失败。
  2. 签名算法不匹配:生成的是RSA密钥对,但签名用了SHA1withDSA,DSA与RSA算法体系不兼容。
  3. 核心变量未赋值: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 23:45:00