Skip to content
ecdsa.com
.NET

Bouncy Castle for .NET (BouncyCastle.Cryptography)

Bouncy Castle for .NET, the managed crypto library .NET developers add for what the base class library lacks: Ed25519 and Ed448, secp256k1 on every platform, deterministic ECDSA, PGP, PEM, and ML-KEM, ML-DSA and SLH-DSA without depending on the OS.
License
MIT
Checked version
2.7.0 ·
Source
github.com/bcgit/bc-csharp
Documentation
www.bouncycastle.org/documentation/documentation-c/

Compatibility at a glance

ECDSAEdDSAPost-quantum

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

BouncyCastle.Cryptography (the package that replaced Portable.BouncyCastle) is pure managed code: its results do not depend on Windows CNG or the Linux OpenSSL version, which is exactly what makes it useful next to System.Security.Cryptography. It offers a string-based SignerUtilities API and lower-level classes such as ECDsaSigner.

Its ECDSA defaults follow the OpenSSL tradition rather than .NET's: SHA-256withECDSA produces DER, while SHA-256withPLAIN-ECDSA gives the 64-byte raw r‖s that .NET's own ECDsa and JWS use. Mixing them up is the usual interop failure between the two libraries in one application, and every malformed input returns false rather than throwing. Nonces are random through SignerUtilities; ECDsaSigner with an HMacDsaKCalculator reproduced RFC 6979's test vector. s is not normalized and verification accepts high-S.

Coverage is what people add it for: P-224 to P-521, secp256k1 and the Brainpool r1 curves; Ed25519 and Ed448, as in the sample; and ML-KEM, ML-DSA and SLH-DSA through MLKemEncapsulator, MLDsaSigner and SlhDsaSigner — all of which worked on the same Linux image where the base class library reported no post-quantum support at all. Try the algorithms in the post-quantum signature playground.

PemWriter writes EC private keys as SEC1 -----BEGIN EC PRIVATE KEY----- and PemReader returns them as an AsymmetricCipherKeyPair; PKCS#8 goes through PrivateKeyInfoFactory. The assembly's own version is the strong-name 2.0.0 for every 2.x release, so check the package version (here 2.7.0) when comparing behaviour.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/bouncy-castle-csharp/run.sh ran Bouncy Castle for .NET (BouncyCastle.Cryptography) 2.7.0 in the container image mcr.microsoft.com/dotnet/sdk:10.0.402-noble on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • ECDSA signature encoding

    der

    SHA-256withECDSA returns DER; SHA-256withPLAIN-ECDSA returns 64 bytes of raw r‖s.

    Recorded by the check
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: SHA-256withPLAIN-ECDSA output: 64 bytes
  • ECDSA nonce

    rfc6979-opt-in

    Random through SignerUtilities; ECDsaSigner with HMacDsaKCalculator reproduced RFC 6979 appendix A.2.5.

    Recorded by the check
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: SHA-256withECDSA repeats: False; ECDsaSigner with HMacDsaKCalculator reproduces RFC 6979 A.2.5: 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

    SHA-256withECDSA 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

    returns-false

    A raw r‖s signature passed to SHA-256withECDSA and a 3-byte blob return false.

    Recorded by the check
    fact ecdsa-malformed-signature = returns-false
    evidence ecdsa-malformed-signature: raw r||s to SHA-256withECDSA: false; 3 bytes: false
  • 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-DSA

    ML-KEM-768 encapsulated and decapsulated, and ML-DSA-65 and SLH-DSA-SHA2-128f signed and verified, in managed code.

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

    spki-pemspki-der

    SPKI PEM through PemReader and SPKI DER through PublicKeyFactory.

    Recorded by the check
    fact public-key-formats = spki-pem, spki-der
    evidence public-key-formats: PublicKeyFactory.CreateKey(SPKI DER) verifies: True
  • Private key formats

    pkcs8-dersec1-pem

    PemWriter writes and PemReader reads SEC1 PEM; PKCS#8 DER loads through PrivateKeyFactory.

    Recorded by the check
    fact private-key-formats = pkcs8-der, sec1-pem
    evidence private-key-formats: PemWriter writes -----BEGIN EC PRIVATE KEY-----; PemReader returns AsymmetricCipherKeyPair; PKCS#8 DER round trip signs: True

Usage

Sign and verify with Ed25519, which System.Security.Cryptography does not provide.
using System.Text;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;

var message = Encoding.UTF8.GetBytes("order #1042 approved");

// Ed25519, which the .NET base class library does not offer.
var generator = new Ed25519KeyPairGenerator();
generator.Init(new Ed25519KeyGenerationParameters(new SecureRandom()));
var pair = generator.GenerateKeyPair();

var signer = SignerUtilities.GetSigner("Ed25519");
signer.Init(true, pair.Private);
signer.BlockUpdate(message, 0, message.Length);
var signature = signer.GenerateSignature();

var verifier = SignerUtilities.GetSigner("Ed25519");
verifier.Init(false, pair.Public);
verifier.BlockUpdate(message, 0, message.Length);
Console.WriteLine($"signature: {signature.Length} bytes, verified: {verifier.VerifySignature(signature)}");

The snippet harness runs every sample verbatim in the container image mcr.microsoft.com/dotnet/sdk:10.0.402-noble, and the run fails unless the output matches.

Setup
dotnet new console --force -o . >/dev/null && dotnet add package BouncyCastle.Cryptography --version 2.7.0 >/dev/null
Command
rm -f Program.cs && DOTNET_NOLOGO=1 dotnet run 2>/dev/null
Output must match (regular expression)
signature: 64 bytes, verified: True

Libraries