📦 PQC Libraries

PQC Open Source Libraries

Which libraries to use for post-quantum cryptography — by language, algorithm support, audit status, and production readiness.

liboqs
Open Quantum Safe — Primary Reference Library
C GitHub ↗

The primary open-source reference library for post-quantum cryptography. Maintained by the Open Quantum Safe project at the University of Waterloo. Used as the foundation for OQS-OpenSSL, OQS-BoringSSL, and bindings in Python, Java, .NET, Go, and Rust.

Algorithms:
ML-KEM (FIPS 203)ML-DSA (FIPS 204)SLH-DSA (FIPS 205)FN-DSA (FIPS 206)Classic McElieceHQCBIKE
Language bindings:
CC++Python (oqs-python)Java (liboqs-java)Go (liboqs-go)Rust (oqs-rs).NET (liboqs-dotnet)
Security audits: NCC Group (2020)Trail of Bits (2021)
→ Foundation library — use this unless you have a language-specific reason to use something else. All major PQC protocol implementations build on liboqs.
OQS-OpenSSL (oqs-provider)
liboqs Provider for OpenSSL 3.x
C GitHub ↗

Integrates liboqs into OpenSSL as a provider. Enables PQC key exchange and signatures in OpenSSL 3.x for TLS, certificates, and key generation. Used with Nginx, Apache, and curl for PQC-enabled TLS.

Algorithms:
ML-KEM hybrid (X25519MLKEM768)ML-DSA certificatesSLH-DSAAll liboqs KEMs and signatures
Language bindings:
CAny language using OpenSSLNginxApachecurl
Security audits: NCC Group audit of underlying liboqs
→ Required for enabling PQC in Nginx, Apache, and OpenSSL-based applications. Install alongside OpenSSL 3.x.
OQS-BoringSSL
Google BoringSSL with OQS extensions
C/C++ GitHub ↗

Fork of Google's BoringSSL (used in Chrome and Android) with OQS post-quantum algorithms. Used for applications that already depend on BoringSSL rather than OpenSSL.

Algorithms:
X25519Kyber768ML-KEMML-DSAAll OQS algorithms
Language bindings:
CC++Go (via BoringSSL)Chrome/Chromium integration
Security audits: Google internal BoringSSL auditNCC Group for OQS components
→ Use when your application already uses BoringSSL. Especially relevant for Android apps, Chrome extensions, and Go programs using crypto/tls.
PQClean
Clean reference implementations
C GitHub ↗

Clean, portable, standalone C implementations of NIST PQC algorithms. No dependencies — designed for integration into other projects and for audit purposes. Each implementation is self-contained. Not optimised for performance (reference implementations) but excellent for correctness verification.

Algorithms:
ML-KEM (Kyber)ML-DSA (Dilithium)SLH-DSA (SPHINCS+)FN-DSA (FALCON)Classic McElieceHQCBIKE
Language bindings:
C (primary)Rust (pqcrypto crate wraps PQClean)Go (via cgo)
Security audits: Multiple academic auditsUsed as reference for NIST competition
→ Use when you need auditable reference implementations for security review, or as the basis for a custom integration. Not for high-performance production use without optimised builds.
Bouncy Castle (PQC)
Java/C# PQC library
Java / C# GitHub ↗

The established Java cryptography library has added comprehensive PQC support. Bouncy Castle 1.78+ includes FIPS 203, 204, 205 algorithms. The Java Bouncy Castle PQC API is mature and production-ready. C# port (The Legion of the Bouncy Castle) has parallel support.

Algorithms:
ML-KEM (FIPS 203)ML-DSA (FIPS 204)SLH-DSA (FIPS 205)FN-DSA (FIPS 206)BIKEHQCClassic McEliece
Language bindings:
Java (primary)KotlinScalaC# (.NET)Android
Security audits: Multiple commercial audits over 20+ yearsWidely reviewed in academic literature
→ Primary choice for Java and .NET PQC implementations. Drop-in via JCE provider API — minimal code changes for applications already using Bouncy Castle.
OpenSSL 3.2+ (native)
OpenSSL native PQC support
C GitHub ↗

OpenSSL 3.2+ has partial native PQC support — X25519MLKEM768 hybrid key exchange for TLS is supported natively without the OQS provider. Full ML-DSA certificate support is being added progressively. Check your version.

Algorithms:
X25519MLKEM768 (TLS hybrid — native 3.2+)ML-KEM (via oqs-provider)ML-DSA (via oqs-provider)
Language bindings:
CAny language with OpenSSL binding
Security audits: OpenSSL is extensively auditedOQS provider separate audit
→ Check: openssl list -kem-algorithms | grep -i mlkem. If X25519MLKEM768 appears, hybrid TLS is supported natively. For full ML-DSA cert support, install oqs-provider.
AWS-LC (AWS LibCrypto)
AWS fork of BoringSSL
C GitHub ↗

AWS's fork of BoringSSL, optimised for AWS infrastructure. Includes ML-KEM support for TLS hybrid key exchange. Used internally in AWS services. Open source.

Algorithms:
ML-KEM (hybrid TLS)ECDH+ML-KEM hybrid
Language bindings:
CAWS SDK integrations
Security audits: AWS internal security reviewFIPS 140-3 validation in progress
→ Use when building AWS-integrated services or when AWS FIPS 140-3 validation is required for your cryptographic module.
pqcrypto (Rust)
Rust PQC crate
Rust GitHub ↗

Rust bindings to PQClean reference implementations. Provides safe Rust APIs for all major PQC algorithms. Part of the pqcrypto family of crates.

Algorithms:
ML-KEMML-DSASLH-DSAFALCONClassic McEliece
Language bindings:
Rust
Security audits: Inherits PQClean audits
→ Best choice for pure Rust implementations. Use with the RustCrypto ecosystem for broader cryptographic operations.

PQC Library Choice — By Language

LanguageRecommended LibraryInstallationNotes
C / C++ liboqs + oqs-provider (for OpenSSL) git clone + cmake build Primary library. Use oqs-provider to plug into existing OpenSSL-based code.
Python oqs-python (liboqs binding) pip install liboqs-python Requires liboqs C library installed. Straightforward API for KEM and signature operations.
Java Bouncy Castle 1.78+ Maven: org.bouncycastle:bcprov-jdk18on:1.78 Best Java option. JCE provider drop-in. Production-ready.
Kotlin Bouncy Castle (same as Java) Same as Java — Kotlin runs on JVM Full Bouncy Castle PQC API available.
Rust pqcrypto crate cargo add pqcrypto pqcrypto-mlkem Clean safe API. Built on PQClean. Use with RustCrypto for broader crypto.
.NET / C# Bouncy Castle C# (1.9.0+) NuGet: BouncyCastle.Cryptography Same feature set as Java port. Production-ready.
Go liboqs-go go get github.com/open-quantum-safe/liboqs-go Requires CGo and liboqs. Wraps the C library with Go-idiomatic API.
Node.js liboqs via native addon npm install node-oqs (or compile from source) Ecosystem still maturing. May need to compile native bindings.
PHP OpenSSL extension (when oqs-provider installed) System OpenSSL + oqs-provider → php openssl fns PHP crypto goes through OpenSSL — install oqs-provider at system level.
Browser / WASM CRYSTALS-Kyber-js or ml-kem npm package npm install @noble/post-quantum @noble/post-quantum is a pure JS implementation of ML-KEM and ML-DSA — audited, no WASM required.

Algorithm Support Matrix

Algorithm (NIST)liboqsPQCleanBouncy CastleOpenSSL 3.2+AWS-LC@noble/pq
ML-KEM-512 (FIPS 203) via oqs-provider
ML-KEM-768 (FIPS 203) ✅ native (hybrid)
ML-KEM-1024 (FIPS 203) via oqs-provider
ML-DSA-44 (FIPS 204) via oqs-provider
ML-DSA-65 (FIPS 204) via oqs-provider
ML-DSA-87 (FIPS 204) via oqs-provider
SLH-DSA (FIPS 205) via oqs-provider
FN-DSA / FALCON (FIPS 206) via oqs-provider
Classic McEliece
BIKE
HQC

Integration Code Examples

Python — ML-KEM key encapsulation (liboqs)

# pip install liboqs-python # Requires: liboqs C library installed at system level import oqs # ML-KEM-768 Key Encapsulation kem = oqs.KeyEncapsulation("ML-KEM-768") # Recipient: generate key pair public_key = kem.generate_keypair() # Sender: encapsulate — produces ciphertext + shared secret ciphertext, shared_secret_sender = kem.encap_secret(public_key) # Recipient: decapsulate — recovers shared secret shared_secret_recipient = kem.decap_secret(ciphertext) assert shared_secret_sender == shared_secret_recipient print(f"Shared secret established: {shared_secret_sender.hex()[:32]}...") kem.free() # ML-DSA-65 Signatures signer = oqs.Signature("ML-DSA-65") public_key = signer.generate_keypair() message = b"sign this message" signature = signer.sign(message) verifier = oqs.Signature("ML-DSA-65") is_valid = verifier.verify(message, signature, public_key) print(f"Signature valid: {is_valid}") signer.free()

Java — ML-KEM with Bouncy Castle

// Maven: org.bouncycastle:bcprov-jdk18on:1.78 import org.bouncycastle.pqc.crypto.mlkem.*; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import java.security.SecureRandom; SecureRandom random = new SecureRandom(); MLKEMKeyPairGenerator gen = new MLKEMKeyPairGenerator(); gen.init(new MLKEMKeyGenerationParameters(random, MLKEMParameters.ml_kem_768)); AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); MLKEMPublicKeyParameters publicKey = (MLKEMPublicKeyParameters) keyPair.getPublic(); MLKEMPrivateKeyParameters privateKey = (MLKEMPrivateKeyParameters) keyPair.getPrivate(); // Encapsulate MLKEMExtractor extractor = new MLKEMExtractor(privateKey); MLKEMGenerator generator = new MLKEMGenerator(random); SecretWithEncapsulation encapsulated = generator.generateEncapsulated(publicKey); byte[] ciphertext = encapsulated.getEncapsulation(); byte[] sharedSecretSender = encapsulated.getSecret(); // Decapsulate byte[] sharedSecretRecipient = extractor.extractSecret(ciphertext); // sharedSecretSender.equals(sharedSecretRecipient) == true

Rust — ML-KEM with pqcrypto

// Cargo.toml: pqcrypto = "0.17" pqcrypto-mlkem = "0.8" use pqcrypto_mlkem::mlkem768::*; use pqcrypto_traits::kem::{PublicKey, SecretKey, SharedSecret, Ciphertext}; // Generate key pair let (pk, sk) = keypair(); // Sender: encapsulate shared secret let (ss_sender, ct) = encapsulate(&pk); // Recipient: decapsulate let ss_recipient = decapsulate(&ct, &sk); assert_eq!(ss_sender.as_bytes(), ss_recipient.as_bytes()); println!("KEM exchange successful — shared secret established"); // ML-DSA signature (Dilithium) use pqcrypto_mldsa::mldsa65::*; let (pk, sk) = keypair(); let message = b"authenticate this"; let sm = sign(message, &sk); let verified = open(&sm, &pk).expect("signature invalid"); assert_eq!(verified, message);

JavaScript — ML-KEM with @noble/post-quantum

// npm install @noble/post-quantum import { ml_kem768 } from '@noble/post-quantum/ml-kem'; import { ml_dsa65 } from '@noble/post-quantum/ml-dsa'; // ML-KEM-768 Key Exchange const { publicKey, secretKey } = ml_kem768.keygen(); // Sender encapsulates const { cipherText, sharedSecret: ssA } = ml_kem768.encapsulate(publicKey); // Recipient decapsulates const ssB = ml_kem768.decapsulate(cipherText, secretKey); console.log('Secrets match:', ssA.toString() === ssB.toString()); // ML-DSA-65 Signatures const seed = crypto.getRandomValues(new Uint8Array(32)); const { publicKey: sigPub, secretKey: sigSec } = ml_dsa65.keygen(seed); const msg = new TextEncoder().encode('sign me'); const sig = ml_dsa65.sign(sigSec, msg); const valid = ml_dsa65.verify(sigPub, msg, sig); console.log('Signature valid:', valid);