Skip to content
ecdsa.com
.NET

.NET System.Security.Cryptography

.NET's System.Security.Cryptography: ECDSA that signs raw r‖s (IEEE P1363) by default, unlike every OpenSSL-based stack, no Ed25519 in the box, and post-quantum classes whose availability depends on the operating system.
License
MIT
Checked version
10.0.13 ·
Source
github.com/dotnet/runtime
Documentation
learn.microsoft.com/dotnet/api/system.security.cryptography.ecdsa

Compatibility at a glance

ECDSA

ECDSA curves
P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
none
Public key formats
spki-pemspki-derraw-xy
Private key formats
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derraw-scalar

.NET wraps the platform's cryptography — CNG on Windows, OpenSSL on Linux, Apple's libraries on macOS — behind one API: ECDsa, RSA, MLDsa, MLKem and friends. The check ran .NET 10.0.13 on Ubuntu 24.04, where the OpenSSL underneath is 3.0.13, and that version shapes several results.

The default signature encoding is the opposite of most server-side libraries. ECDsa.SignData returns the 64-byte IEEE P1363 r‖s and VerifyData expects it; DER requires DSASignatureFormat.Rfc3279DerSequence on both calls. That makes .NET match JWS and WebCrypto out of the box and mismatch OpenSSL, Java's SHA256withECDSA and Go's SignASN1. Every malformed or mismatched signature makes VerifyData return false, so a DER signature checked with the default format looks exactly like a forgery — the sample shows both outcomes, and the DER ⇄ raw converter shows which one you have.

On Linux, ECDSA covered P-256 to P-521, secp256k1 and the Brainpool r1 curves, all through OpenSSL. SignHash signs a digest you computed, nonces are random, s is not normalized and verification accepts high-S. There is no Ed25519 or Ed448 class in the base class library; EdDSA needs Bouncy Castle for .NET or another package.

MLKem, MLDsa, SlhDsa and CompositeMLDsa exist in .NET 10, but each reports IsSupported according to the platform. With OpenSSL 3.0.13 every one of them was false; OpenSSL added ML-KEM and ML-DSA in 3.5, so on Linux they need a distribution that ships it, and on Windows the corresponding CNG support. Code must check IsSupported rather than assume, and this page has not verified any platform where the answer is true.

Keys import from SPKI and PKCS#8 (PEM or DER, encrypted or not), SEC1 EC PRIVATE KEY and raw ECParameters coordinates; there is no SEC1-point or JWK import. Inspect PFX bundles with the PFX inspector.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/dotnet-crypto/run.sh ran .NET System.Security.Cryptography 10.0.13 on .NET 10.0.13 on Ubuntu 24.04.5 LTS 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

    raw

    SignData returns 64 bytes of IEEE P1363 r‖s by default; DSASignatureFormat.Rfc3279DerSequence produces DER.

    Recorded by the check
    fact ecdsa-signature-encoding = raw
    evidence ecdsa-signature-encoding: SignData default: 64 bytes; with Rfc3279DerSequence: parses as DER True
  • ECDSA nonce

    random

    Random nonce: the RFC 6979 key signed the same message twice with different results.

    Recorded by the check
    fact ecdsa-nonce = random
  • 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 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

    SignData hashes; SignHash signs a digest you computed, and that signature verified against the message.

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

    returns-false

    A DER signature checked as P1363, a raw signature checked as DER and a 3-byte blob all return false.

    Recorded by the check
    fact ecdsa-malformed-signature = returns-false
    evidence ecdsa-malformed-signature: DER to the default verifier: false; raw to the DER verifier: false; 3 bytes: false
  • ECDSA curves

    P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

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

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

    none

    There is no Ed25519 class in System.Security.Cryptography.

    Recorded by the check
    fact eddsa = none
    evidence eddsa: System.Security.Cryptography.Ed25519 type present: False
  • Post-quantum algorithms

    none

    MLKem.IsSupported, MLDsa.IsSupported, SlhDsa.IsSupported and composite ML-DSA support were all false on this platform.

    Recorded by the check
    fact pqc = none
    evidence pqc: MLKem.IsSupported False, MLDsa.IsSupported False, SlhDsa.IsSupported False, CompositeMLDsa.IsSupported(MLDsa65WithECDsaP256) False
  • Platform OpenSSL

    3.0.13

    On Linux the classes use the system OpenSSL, here 3.0.13 from Ubuntu 24.04, which predates ML-KEM and ML-DSA.

    Recorded by the check
    fact platform-openssl = 3.0.13
    evidence platform-openssl: OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
  • Public key formats

    spki-pemspki-derraw-xy

    Public keys import from SPKI PEM (ImportFromPem), SPKI DER and the raw X and Y coordinates in ECParameters.

    Recorded by the check
    fact public-key-formats = spki-pem, spki-der, raw-xy
    evidence public-key-formats: ImportParameters takes Q.X and Q.Y separately; there is no SEC1 point or JWK import in the BCL
  • Private key formats

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derraw-scalar

    Private keys import from PKCS#8 PEM and DER, encrypted PKCS#8, SEC1 PEM and DER, and the raw scalar in ECParameters.

    Recorded by the check
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, raw-scalar

Usage

Sign in the default P1363 format and in DER, and show that verifying DER needs the matching format argument.
using System.Security.Cryptography;
using System.Text;

using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var message = Encoding.UTF8.GetBytes("order #1042 approved");

// .NET signs raw r||s (IEEE P1363) by default; OpenSSL, Java and Go expect DER.
var raw = key.SignData(message, HashAlgorithmName.SHA256);
var der = key.SignData(message, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);

// Verifying a DER signature needs the same format argument, or VerifyData returns false.
var ok = key.VerifyData(message, der, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
var wrongFormat = key.VerifyData(message, der, HashAlgorithmName.SHA256);
Console.WriteLine($"raw: {raw.Length} bytes, DER verified: {ok}, DER as raw: {wrongFormat}");
Console.WriteLine(key.ExportSubjectPublicKeyInfoPem().Split('\n')[0]);

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
Command
rm -f Program.cs && DOTNET_NOLOGO=1 dotnet run 2>/dev/null
Output must match (regular expression)
raw: 64 bytes, DER verified: True, DER as raw: False\n-----BEGIN PUBLIC KEY-----

Libraries