Skip to content
ecdsa.com
Java · Kotlin

Bouncy Castle (Java)

The Legion of the Bouncy Castle's Java provider and PKIX toolkit: every curve the JDK dropped, deterministic ECDSA on request, PEM parsing, and the widest set of post-quantum signatures on the JVM.
License
MIT
Checked version
1.86 ·
Source
github.com/bcgit/bc-java
Documentation
www.bouncycastle.org/documentation/documentation-java/

Compatibility at a glance

ECDSAEdDSAPost-quantum

ECDSA curves
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Post-quantum algorithms
ML-KEMML-DSASLH-DSAFalconLMSXMSS
Public key formats
spki-pemspki-der
Private key formats
pkcs8-dersec1-pem

Bouncy Castle is two things at once: a JCA provider (BC) that plugs into Signature, KeyPairGenerator and KEM, and lightweight APIs such as ECDSASigner that work without the JCA. bcpkix adds certificates, CMS, OCSP and the PEMParser that the JDK still lacks. Most JVM code that touches secp256k1, Brainpool, PGP or certificates depends on it.

Its ECDSA defaults match the JDK's: SHA256withECDSA produces DER with a random nonce and leaves s unnormalized, and verification accepts both halves. The differences are opt-in algorithm names. SHA256withPLAIN-ECDSA produces the 64-byte raw r‖s that JWS expects; SHA256withECDDSA signs deterministically, and the lightweight ECDSASigner with an HMacDSAKCalculator reproduced the RFC 6979 test vector exactly. As with SunEC, a raw signature handed to the DER algorithm throws SignatureException instead of returning false.

Curve coverage is what brings most people here: P-224 through P-521, secp256k1 and the Brainpool r1 curves all signed and verified, along with Ed25519 and Ed448. Register the provider and name it — KeyPairGenerator.getInstance("EC", "BC") — because the JDK's SunEC is consulted first otherwise and rejects secp256k1, as the sample avoids.

Post-quantum coverage in 1.86 is the broadest of the libraries checked: ML-KEM through javax.crypto.KEM, ML-DSA, SLH-DSA and LMS in the BC provider, and Falcon and XMSS in BCPQC. Try the algorithms in the post-quantum signature playground and inspect hybrid certificates with the PQ certificate inspector.

JcaPEMWriter writes EC private keys as SEC1 -----BEGIN EC PRIVATE KEY-----, which PEMParser reads back as a PEMKeyPair; tools that expect PKCS#8 need the key converted first. The DER ⇄ raw converter helps when a PLAIN and a DER signature get mixed up.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/bouncy-castle-java/run.sh ran Bouncy Castle (Java) 1.86 in the container image maven:3.9.16-eclipse-temurin-25 on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • ECDSA signature encoding

    der

    SHA256withECDSA returns DER; SHA256withPLAIN-ECDSA returns 64 bytes of raw r‖s.

    Recorded by the check
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: SHA256withPLAIN-ECDSA output is 64 bytes, parses as DER: false
  • ECDSA nonce

    rfc6979-opt-in

    Random by default; SHA256withECDDSA gave identical signatures for the same input, and ECDSASigner with HMacDSAKCalculator reproduced RFC 6979 appendix A.2.5.

    Recorded by the check
    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 when signing

    either

    s is not normalized: high-S values appeared among 64 signatures.

    Recorded by the check
    fact ecdsa-low-s-sign = either
  • High-S when verifying

    accepts-high-s

    A DER signature with s replaced by n − s verified.

    Recorded by the check
    fact ecdsa-low-s-verify = accepts-high-s
  • What the signing call hashes

    hashes-message-or-digest

    SHA256withECDSA hashes; NONEwithECDSA signs a digest, and that signature verified against the message.

    Recorded by the check
    fact ecdsa-prehash = hashes-message-or-digest
  • Malformed ECDSA signature

    throws

    A raw r‖s signature and a 3-byte blob passed to SHA256withECDSA throw SignatureException.

    Recorded by the check
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: raw r||s to SHA256withECDSA: throws SignatureException; 3 bytes: throws SignatureException
  • ECDSA curves

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

    P-224, P-256, P-384, P-521, secp256k1 and brainpoolP256r1/P384r1/P512r1 signed and verified.

    Recorded by the check
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 and Ed448.

    Recorded by the check
    fact eddsa = Ed25519, Ed448
  • Post-quantum algorithms

    ML-KEMML-DSASLH-DSAFalconLMSXMSS

    ML-KEM (through javax.crypto.KEM), ML-DSA, SLH-DSA and LMS in the BC provider, Falcon and XMSS in BCPQC, all round-tripped in the check.

    Recorded by the check
    fact pqc = ML-KEM, ML-DSA, SLH-DSA, Falcon, LMS, XMSS
  • Public key formats

    spki-pemspki-der

    Public keys read from SPKI PEM through PEMParser and from SPKI DER.

    Recorded by the check
    fact public-key-formats = spki-pem, spki-der
  • Private key formats

    pkcs8-dersec1-pem

    JcaPEMWriter writes EC private keys as SEC1 PEM (EC PRIVATE KEY), which PEMParser reads as a PEMKeyPair; PKCS#8 DER loads through the JCA.

    Recorded by the check
    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

Usage

Generate a secp256k1 key in the BC provider, sign deterministically with SHA256withECDDSA and verify.
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));
    }
}

The snippet harness runs every sample verbatim in the container image maven:3.9.16-eclipse-temurin-25, and the run fails unless the output matches.

Setup
mvn -q -B dependency:copy -Dartifact=org.bouncycastle:bcprov-jdk18on:1.86 -DoutputDirectory=lib >/dev/null
Command
java -cp lib/bcprov-jdk18on-1.86.jar Usage.java
Output must match (regular expression)
verified: true, DER starts with 0x30

Libraries