BouncyCastle is the cryptography library that fills in everything the Java standard library leaves out. The Java Cryptography Architecture (JCA) ships with a fairly conservative algorithm set: AES, RSA, DSA, ECDSA, EC key agreement, SHA-2, and a few extras. BouncyCastle is the third-party Java provider that has shipped, on time and ahead of the JDK, every cryptographic algorithm that Java developers actually need. Hash trees? Yes. ChaCha20? Yes. ARIA, Camellia, GOST, Kuznyechik? Yes. Post-quantum algorithms? Yes, and broadly so.
The Legion of the Bouncy Castle, the volunteer organization at bouncycastle.org, has a reputation for adopting algorithms before they appear in the JDK. Post-quantum cryptography is no exception. BouncyCastle has post-quantum support that exceeds anything yet shipped in the OpenJDK standard library.
This article walks through what BouncyCastle covers for post-quantum, how to use it from Java code, and how the BouncyCastle approach compares to the in-progress JEP-based work for OpenJDK.
What BouncyCastle Is
BouncyCastle Java is two things:
- A lightweight cryptography API that lives in
org.bouncycastle.crypto.*packages - A JCA Provider that hooks into Java's standard cryptography framework
Most applications use the JCA Provider path. They call standard Java APIs like KeyPairGenerator.getInstance("ML-KEM-768", "BC") and BouncyCastle handles the rest. This means existing Java code can adopt post-quantum algorithms with minimal restructuring, just by adding the BouncyCastle provider to the JVM.
BouncyCastle also ships a TLS implementation called BCTLS (org.bouncycastle.tls.*) that some applications use as a TLS stack, separate from JSSE. BCTLS gets post-quantum support faster than JSSE because it ships with each BouncyCastle release.
For broader context on Java cryptography, see openssl PQC status 2026.
Where BouncyCastle Stands on Post-Quantum
BouncyCastle's post-quantum coverage is more comprehensive than any other major Java library. Here is the snapshot.
| Algorithm | Status in BouncyCastle |
|---|---|
| ML-KEM (FIPS 203) | All parameter sets: 512, 768, 1024 |
| ML-DSA (FIPS 204) | All parameter sets: 44, 65, 87 |
| SLH-DSA (FIPS 205) | All parameter sets: 128s, 128f, 192s, 192f, 256s, 256f |
| FN-DSA / Falcon | Falcon-512 and Falcon-1024 |
| LMS / HSS | Yes, per NIST SP 800-208 |
| XMSS / XMSS^MT | Yes |
| HQC | Implementation present |
| BIKE | Implementation present |
| FrodoKEM | Implementation present |
| Picnic | Implementation present (legacy) |
| NTRU and NTRU Prime | Implementation present (legacy) |
This breadth is partly historical. BouncyCastle implemented many algorithms during the NIST PQC competition rounds. After NIST selected its standards, BouncyCastle kept the older algorithms available for users who needed them while adding the standardized variants. The result is a library that works across the entire NIST PQC menu plus stateful hash-based signatures.
How to Use ML-KEM in Java
A typical Java code path with BouncyCastle:
- Register the BouncyCastle provider with
Security.addProvider(new BouncyCastlePQCProvider()) - Generate keys via
KeyPairGenerator.getInstance("ML-KEM-768", "BCPQC") - Use the
org.bouncycastle.pqc.jcajce.spec.MLKEMParameterSpecto configure parameters - Encapsulate with
KeyGenerator.getInstance("ML-KEM-768")for receiver-side wrapping - Decapsulate with
CipherorKeyAgreementAPIs depending on use case
The exact API surface has evolved over BouncyCastle releases. The 1.78 release line consolidated the post-quantum APIs around the NIST-standard algorithms. The 1.80 line continues the consolidation with API stability promises for ML-KEM, ML-DSA, and SLH-DSA.
For background on the algorithms themselves, see ML-KEM explained.
How to Use ML-DSA in Java
ML-DSA is similar:
- Generate keys via
KeyPairGenerator.getInstance("ML-DSA-65", "BCPQC") - Sign via
Signature.getInstance("ML-DSA-65", "BCPQC")withinitSign(privateKey)andupdate(data) - Verify via the same Signature class with
initVerify(publicKey)
Because BouncyCastle plugs into JCA, these Signature objects can be passed to existing Java code that expects a java.security.Signature. Existing code paths for digital signing (X.509 certificates, JWT signatures, code signing) work with ML-DSA without rewriting.
The JCA provider system also lets you use ML-DSA from Java's KeyStore APIs, including PKCS#12 keystores. This means existing keystore-based deployments can rotate from RSA or ECDSA keys to ML-DSA keys with the same Java code that has always handled keystore operations.
TLS Through BCTLS
BouncyCastle ships its own TLS stack, BCTLS. BCTLS supports post-quantum TLS groups including:
- X25519MLKEM768 hybrid (matching the IETF draft used by Chrome, Firefox, OpenSSL)
- ML-KEM-only groups in experimental forms
- Hybrid signature schemes for TLS 1.3 client authentication
Because BCTLS ships with BouncyCastle releases, it gets PQC algorithms before JSSE. Applications that need post-quantum TLS in Java today and cannot wait for OpenJDK's JSSE updates often switch to BCTLS.
The cost of BCTLS is that you give up some integration with the standard JSSE world. Most Java HTTP clients (Apache HttpClient, OkHttp) integrate with JSSE; using BCTLS requires adapter code. For server-side use cases like custom servers or microservices, BCTLS works well.
For comparison with native Java TLS, see the openssl PQC reference noted above.
Stateful Hash-Based Signatures: BouncyCastle Strengths
BouncyCastle has long supported LMS, HSS, XMSS, and XMSS^MT. These are stateful hash-based signature schemes covered by NIST SP 800-208. They are not part of FIPS 204 or 205, but they are approved for specific use cases like firmware signing.
The BouncyCastle implementations have been used in firmware signing pipelines for years. The state-management problem (each LMS signature consumes a one-time key, so signing twice from the same state breaks security) is solved by integrating BouncyCastle with HSMs that handle state durably. AWS CloudHSM and YubiHSM both provide LMS support that pairs with BouncyCastle clients.
For algorithm comparison, see ML-DSA vs SLH-DSA.
Comparing BouncyCastle to OpenJDK
OpenJDK has a JEP (JDK Enhancement Proposal) process for adding post-quantum cryptography to the standard library. Several JEPs have been proposed and accepted. The pacing is slower than BouncyCastle because:
- OpenJDK changes go through extensive review across multiple stewards
- API stability commitments are stronger
- FIPS validation pathways for OpenJDK builds (Adoptium, Red Hat, Azul) require specific module structuring
- Backwards compatibility constraints are tighter
The result is that OpenJDK ML-KEM and ML-DSA support is appearing in JDK 24 and 25 preview features, with stabilization in JDK 26 and beyond. BouncyCastle has had production-ready support for these algorithms for longer.
In practice, many Java teams use BouncyCastle today and plan to migrate to JDK-native APIs once those stabilize. The migration is straightforward because BouncyCastle's JCA provider exposes the same Java standard APIs the OpenJDK eventually exposes.
Performance and FIPS
BouncyCastle has separate FIPS-validated and non-FIPS distributions. The FIPS distribution (BCFIPS) goes through CMVP validation periodically. FIPS-validated builds with post-quantum algorithms are progressing through CMVP through 2026. The non-FIPS general distribution gets new algorithms first.
Performance is generally adequate for non-hot-path use. Java performance for cryptography depends heavily on the JIT and on whether native acceleration is available. BouncyCastle's PQC implementations are pure Java, so they do not benefit from CPU-specific assembly the way C libraries do. For most application workloads (signing JWTs, validating certificates, encrypting files), the JVM-level performance is fine. For TLS termination at extreme scale, BCTLS may not match a tuned C library.
For broader migration considerations, see AWS KMS quantum migration.
What's Not Covered
BouncyCastle is comprehensive, but a few items are still not present:
- Hybrid certificates carrying classical and PQ signatures: Awaiting IETF stabilization
- Specific HSM integration paths: PKCS#11 PQC mechanism numbers are still settling at OASIS
- Quantum-safe TLS 1.2 cipher suites: TLS 1.3 is the focus
BouncyCastle's API Surface for PQC
The BouncyCastle PQC API surface mirrors the JCA surface developers already know. For ML-KEM:
KeyPairGeneratorwith parameter sets like ML-KEM-512, ML-KEM-768, ML-KEM-1024Cipherfor encapsulation operations, when using the KEM as a key transport mechanismKeyAgreementfor combining KEM-derived material with other key agreement outputKeyFactoryfor converting between encoded and structured key representations
For ML-DSA:
KeyPairGeneratorfor ML-DSA-44, ML-DSA-65, ML-DSA-87Signaturefor sign and verify, integrated with java.security.SignatureKeyFactoryandCertificateFactoryfor parsing X.509 certificates with ML-DSA keys
For SLH-DSA:
- Similar pattern with parameter sets like SLH-DSA-128s, 128f, 192s, 192f, 256s, 256f
- Larger signature sizes are exposed; applications should account for storage
These APIs work uniformly through Java's standard cryptography framework. Existing code that accepts a Signature or KeyPairGenerator instance will accept BouncyCastle PQC instances without modification.
ASN.1 and Certificate Encoding
X.509 certificates carrying ML-DSA public keys use specific ASN.1 OIDs:
- 2.16.840.1.101.3.4.3.17 for ML-DSA-44 signatures
- 2.16.840.1.101.3.4.3.18 for ML-DSA-65 signatures
- 2.16.840.1.101.3.4.3.19 for ML-DSA-87 signatures
These OIDs are NIST-assigned and used in the FIPS 204 specification context. BouncyCastle's certificate parsing recognizes these OIDs and extracts the algorithm parameters correctly.
For ML-KEM, similar OID assignments exist for use in PKCS#8 private-key encoding and SubjectPublicKeyInfo public-key encoding. BouncyCastle's PKCS#8 import/export routines handle these.
Code Signing With ML-DSA in Java
A practical use case for BouncyCastle PQC is signing JAR files and other artifacts with ML-DSA. The flow:
- Generate an ML-DSA-65 keypair using BouncyCastle
- Wrap the public key in an X.509 certificate signed by a trusted authority (which itself uses ML-DSA or hybrid signatures)
- Sign the JAR using jarsigner or programmatic signing with ML-DSA as the algorithm
- Verify on the consumer side using the same certificate path
This pattern is being adopted in enterprise Java environments where firmware and code signing pipelines need post-quantum protection. The state-management problem of LMS (each signature consumes one-time material) does not apply to ML-DSA, making it more practical for general code signing.
For broader signing context, see hybrid encryption.
Best Practices for Java Developers
For Java applications adopting post-quantum cryptography:
- Add the BouncyCastle provider to your JVM and update your dependency to current BouncyCastle (1.78 series or later)
- Use Java standard APIs (
KeyPairGenerator,Signature,Cipher) and pass the provider name "BCPQC" or "BC" - Test interoperability with peer implementations (OpenSSL, Go, Rust) by validating wire formats
- For TLS, evaluate whether BCTLS or JSSE meets your needs; BCTLS for cutting-edge PQC, JSSE for ecosystem fit
- Plan for rotation: using JCA APIs makes algorithm migration mostly mechanical
For library authors, expose BouncyCastle as one possible provider and let users plug in alternatives.
Frequently Asked Questions
Is BouncyCastle production-ready for post-quantum?
Yes for non-FIPS deployments. BouncyCastle's ML-KEM, ML-DSA, and SLH-DSA implementations are mature and have been deployed by various organizations. For FIPS-validated production, the BCFIPS distribution is the answer, with PQC validation progressing.
Can I use BouncyCastle and JCA together?
Yes. BouncyCastle is a JCA provider. You register it once at JVM startup, then use standard Java cryptography APIs.
Should I use BCTLS or JSSE for TLS?
BCTLS gives you the latest PQC algorithms today. JSSE has broader Java ecosystem integration. For new Java services that need PQC, evaluate both. For existing services, BCTLS may require adapter code.
Is BouncyCastle slow?
Pure Java cryptography is slower than tuned C with assembly, but for most application workloads BouncyCastle is fast enough. Profile your specific use case before optimizing.
Where can I find the source?
The repository is github.com/bcgit/bc-java. The post-quantum modules live in org.bouncycastle.pqc.crypto. and org.bouncycastle.pqc.jcajce..
How does BouncyCastle handle stateful hash-based signature state?
For LMS and HSS, BouncyCastle expects the integrating application to manage state. The library provides primitives for signing and verifying; the application is responsible for storing the LMS state and not reusing it. In practice, signing pipelines route LMS operations through HSMs that handle state durably.
Can BouncyCastle and JSSE coexist?
Yes. BouncyCastle is a JCA provider; JSSE is the standard Java SSL/TLS framework. They handle different layers and can coexist. BouncyCastle can also provide the JSSE provider via its BCJSSE module if you want BouncyCastle-implemented TLS.
Is BouncyCastle's ML-KEM faster or slower than the JDK native APIs?
JDK native APIs (when available) typically use HotSpot intrinsics or platform-specific libraries that benefit from optimization. BouncyCastle is pure Java and slightly slower in benchmarks. For most applications the difference is negligible.
Sources
- BouncyCastle repository, github.com/bcgit/bc-java
- BouncyCastle project, bouncycastle.org
- NIST FIPS 203, 204, 205 (post-quantum standards), 2024
- NIST SP 800-208 (Stateful Hash-Based Signatures)
- JDK Enhancement Proposals for PQC, openjdk.org/jeps
- IETF draft-kwiatkowski-tls-ecdhe-mlkem
Related Articles
- What is post-quantum cryptography
- ML-KEM explained
- ML-DSA vs SLH-DSA
- Hybrid encryption
- OpenSSL PQC status 2026
Protect Your Data Before Q-Day Arrives
QNSQY's NIST-standardized post-quantum encryption protects files against both current and quantum-era threats.