跳到主要内容
ecdsa.com
Java · Kotlin

Bouncy Castle (Java)

Legion of the Bouncy Castle 出品的 Java 提供程序和 PKIX 工具包:JDK 移除的所有曲线、按需启用的确定性 ECDSA、PEM 解析,以及 JVM 上最齐全的后量子签名。
许可证
MIT
检查的版本
1.86 ·
源代码
github.com/bcgit/bc-java
文档
www.bouncycastle.org/documentation/documentation-java/

兼容性概览

ECDSAEdDSA后量子

ECDSA 曲线
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
后量子算法
ML-KEMML-DSASLH-DSAFalconLMSXMSS
公钥格式
spki-pemspki-der
私钥格式
pkcs8-dersec1-pem

Bouncy Castle 同时扮演两个角色:一是接入 Signature、KeyPairGenerator 和 KEM 的 JCA 提供程序(BC),二是 ECDSASigner 等无需 JCA 即可使用的轻量级 API。bcpkix 增加了证书、CMS、OCSP 支持,以及 JDK 至今仍缺少的 PEMParser。大多数涉及 secp256k1、Brainpool、PGP 或证书的 JVM 代码都依赖它。

它的 ECDSA 默认行为与 JDK 一致:SHA256withECDSA 使用随机 nonce 生成 DER 签名,不对 s 做规范化,验证时 high-S 和 low-S 都接受。差异体现在需要显式选用的算法名上。SHA256withPLAIN-ECDSA 生成 JWS 所要求的 64 字节原始 r‖s;SHA256withECDDSA 以确定性方式签名,轻量级的 ECDSASigner 搭配 HMacDSAKCalculator 则精确复现了 RFC 6979 的测试向量。与 SunEC 一样,把原始签名交给 DER 算法时会抛出 SignatureException,而不是返回 false。

大多数人选择它,是因为曲线覆盖面:P-224 到 P-521、secp256k1 和 Brainpool r1 曲线的签名与验证均成功,Ed25519 和 Ed448 也是如此。要注册提供程序并显式指定它——KeyPairGenerator.getInstance("EC", "BC")——否则会先查询 JDK 的 SunEC,而它会拒绝 secp256k1;示例正是这样避开了这个问题。

在已检查的库中,1.86 版的后量子覆盖面最广:BC 提供程序中有 ML-KEM(通过 javax.crypto.KEM 使用)、ML-DSA、SLH-DSA 和 LMS,BCPQC 中有 Falcon 和 XMSS。可以在后量子签名工具中试用这些算法,并用后量子证书查看器查看混合证书。

JcaPEMWriter 把 EC 私钥写成 SEC1 格式的 -----BEGIN EC PRIVATE KEY-----,PEMParser 会把它读回为 PEMKeyPair;要求 PKCS#8 的工具需要先转换密钥。PLAIN 签名和 DER 签名混淆时,DER ⇄ 原始格式转换器可以帮上忙。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/bouncy-castle-java/run.sh 于2026年10月20日在容器镜像 maven:3.9.16-eclipse-temurin-25 中运行了 Bouncy Castle (Java) 1.86;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • ECDSA 签名编码

    der

    SHA256withECDSA 返回 DER;SHA256withPLAIN-ECDSA 返回 64 字节的原始 r‖s。

    检查记录
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: SHA256withPLAIN-ECDSA output is 64 bytes, parses as DER: false
  • ECDSA 的 nonce

    rfc6979-opt-in

    默认是随机的;SHA256withECDDSA 对相同输入给出相同的签名,ECDSASigner 搭配 HMacDSAKCalculator 复现了 RFC 6979 附录 A.2.5。

    检查记录
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: SHA256withECDSA repeats: false; ECDSASigner with HMacDSAKCalculator reproduces RFC 6979 A.2.5: true; SHA256withECDDSA repeats: true
  • 签名时的 low-S

    either

    s 没有经过规范化:64 个签名中出现了 high-S 值。

    检查记录
    fact ecdsa-low-s-sign = either
  • 验证时的 high-S

    accepts-high-s

    把 s 替换为 n − s 的 DER 签名验证通过。

    检查记录
    fact ecdsa-low-s-verify = accepts-high-s
  • 签名调用对哪些内容做哈希

    hashes-message-or-digest

    SHA256withECDSA 会先计算哈希;NONEwithECDSA 对摘要签名,该签名针对原消息验证通过。

    检查记录
    fact ecdsa-prehash = hashes-message-or-digest
  • 格式错误的 ECDSA 签名

    throws

    原始 r‖s 签名和 3 字节的数据块传给 SHA256withECDSA 时,都会抛出 SignatureException。

    检查记录
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: raw r||s to SHA256withECDSA: throws SignatureException; 3 bytes: throws SignatureException
  • ECDSA 曲线

    P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    P-224、P-256、P-384、P-521、secp256k1 和 brainpoolP256r1/P384r1/P512r1 的签名与验证均成功。

    检查记录
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 和 Ed448。

    检查记录
    fact eddsa = Ed25519, Ed448
  • 后量子算法

    ML-KEMML-DSASLH-DSAFalconLMSXMSS

    BC 提供程序中的 ML-KEM(通过 javax.crypto.KEM)、ML-DSA、SLH-DSA 和 LMS,以及 BCPQC 中的 Falcon 和 XMSS,在检查中均完成了往返测试。

    检查记录
    fact pqc = ML-KEM, ML-DSA, SLH-DSA, Falcon, LMS, XMSS
  • 公钥格式

    spki-pemspki-der

    公钥可通过 PEMParser 从 SPKI PEM 读取,也可从 SPKI DER 读取。

    检查记录
    fact public-key-formats = spki-pem, spki-der
  • 私钥格式

    pkcs8-dersec1-pem

    JcaPEMWriter 把 EC 私钥写成 SEC1 PEM(EC PRIVATE KEY),PEMParser 将其读取为 PEMKeyPair;PKCS#8 DER 通过 JCA 加载。

    检查记录
    fact private-key-formats = pkcs8-der, sec1-pem
    evidence private-key-formats: JcaPEMWriter writes an EC private key as -----BEGIN EC PRIVATE KEY-----; PEMParser returns PEMKeyPair

用法

在 BC 提供程序中生成 secp256k1 密钥,用 SHA256withECDDSA 做确定性签名,然后验证。
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class Usage {
    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        // secp256k1 is not in the JDK's SunEC; it is in BC.
        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "BC");
        generator.initialize(new ECGenParameterSpec("secp256k1"));
        KeyPair pair = generator.generateKeyPair();
        byte[] message = "order #1042 approved".getBytes(StandardCharsets.UTF_8);

        // ECDDSA = deterministic ECDSA (RFC 6979); PLAIN-ECDSA = raw r||s instead of DER.
        Signature signer = Signature.getInstance("SHA256withECDDSA", "BC");
        signer.initSign(pair.getPrivate());
        signer.update(message);
        byte[] der = signer.sign();

        Signature verifier = Signature.getInstance("SHA256withECDSA", "BC");
        verifier.initVerify(pair.getPublic());
        verifier.update(message);
        System.out.println("verified: " + verifier.verify(der) + ", DER starts with 0x" + Integer.toHexString(der[0] & 0xff));
    }
}

代码片段测试工具会在容器镜像 maven:3.9.16-eclipse-temurin-25 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
mvn -q -B dependency:copy -Dartifact=org.bouncycastle:bcprov-jdk18on:1.86 -DoutputDirectory=lib >/dev/null
命令
java -cp lib/bcprov-jdk18on-1.86.jar Usage.java
输出必须匹配(正则表达式)
verified: true, DER starts with 0x30

库