跳到主要内容
ecdsa.com
.NET

.NET System.Security.Cryptography

.NET 的 System.Security.Cryptography:ECDSA 默认生成原始 r‖s(IEEE P1363)签名,这与所有基于 OpenSSL 的技术栈都不同;不内置 Ed25519;后量子类是否可用取决于操作系统。
许可证
MIT
检查的版本
10.0.13 ·
源代码
github.com/dotnet/runtime
文档
learn.microsoft.com/dotnet/api/system.security.cryptography.ecdsa

兼容性概览

ECDSA

ECDSA 曲线
P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
none
公钥格式
spki-pemspki-derraw-xy
私钥格式
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derraw-scalar

.NET 用同一套 API 封装各平台的密码学实现——Windows 上是 CNG,Linux 上是 OpenSSL,macOS 上是 Apple 的库:ECDsa、RSA、MLDsa、MLKem 等。本次检查在 Ubuntu 24.04 上运行 .NET 10.0.13,其底层的 OpenSSL 为 3.0.13,这个版本影响了多项结果。

默认的签名编码与大多数服务端库正好相反。ECDsa.SignData 返回 64 字节的 IEEE P1363 r‖s,VerifyData 也按这种格式解析;要使用 DER,两个调用都需要传入 DSASignatureFormat.Rfc3279DerSequence。因此 .NET 开箱即与 JWS 和 WebCrypto 一致,而与 OpenSSL、Java 的 SHA256withECDSA 和 Go 的 SignASN1 不一致。任何格式错误或不匹配的签名都会让 VerifyData 返回 false,所以用默认格式检查 DER 签名时,结果与伪造签名毫无二致——示例展示了这两种结果,DER ⇄ 原始格式转换器则能看出你手里的是哪一种。

在 Linux 上,ECDSA 支持 P-256 到 P-521、secp256k1 和 Brainpool r1 曲线,全部通过 OpenSSL 实现。SignHash 对你自行计算的摘要签名,nonce 是随机的,s 不做规范化,验证时接受 high-S。基类库中没有 Ed25519 或 Ed448 类;EdDSA 需要借助面向 .NET 的 Bouncy Castle 或其他包。

MLKem、MLDsa、SlhDsa 和 CompositeMLDsa 在 .NET 10 中都存在,但各自的 IsSupported 取决于平台。在 OpenSSL 3.0.13 上,它们全部为 false;OpenSSL 在 3.5 中才加入 ML-KEM 和 ML-DSA,因此在 Linux 上需要提供该版本 OpenSSL 的发行版,在 Windows 上则需要相应的 CNG 支持。代码必须检查 IsSupported,不能想当然;本页尚未在任何结果为 true 的平台上做过验证。

密钥可以从 SPKI 和 PKCS#8(PEM 或 DER,加密与否均可)、SEC1 EC PRIVATE KEY 以及 ECParameters 中的原始坐标导入;不支持导入 SEC1 点或 JWK。可以用 PFX 查看器查看 PFX 文件包。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/dotnet-crypto/run.sh 于2026年10月20日在容器镜像 mcr.microsoft.com/dotnet/sdk:10.0.402-noble 中运行了 .NET System.Security.Cryptography 10.0.13(运行时为 .NET 10.0.13 on Ubuntu 24.04.5 LTS);本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • ECDSA 签名编码

    raw

    SignData 默认返回 64 字节的 IEEE P1363 r‖s;DSASignatureFormat.Rfc3279DerSequence 生成 DER。

    检查记录
    fact ecdsa-signature-encoding = raw
    evidence ecdsa-signature-encoding: SignData default: 64 bytes; with Rfc3279DerSequence: parses as DER True
  • ECDSA 的 nonce

    random

    随机 nonce:用 RFC 6979 的密钥对同一条消息签名两次,结果不同。

    检查记录
    fact ecdsa-nonce = random
  • 签名时的 low-S

    either

    s 没有经过规范化:64 个签名中出现了 high-S 值。

    检查记录
    fact ecdsa-low-s-sign = either
  • 验证时的 high-S

    accepts-high-s

    把 s 替换为 n − s 的签名验证通过。

    检查记录
    fact ecdsa-low-s-verify = accepts-high-s
  • 签名调用对哪些内容做哈希

    hashes-message-or-digest

    SignData 会先计算哈希;SignHash 对你自行计算的摘要签名,该签名针对原消息验证通过。

    检查记录
    fact ecdsa-prehash = hashes-message-or-digest
  • 格式错误的 ECDSA 签名

    returns-false

    按 P1363 检查的 DER 签名、按 DER 检查的原始签名,以及 3 字节的数据块,都返回 false。

    检查记录
    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 曲线

    P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    在 Linux 上,P-256、P-384、P-521、secp256k1 和 brainpoolP256r1/P384r1/P512r1 的签名与验证均成功。

    检查记录
    fact ecdsa-curves = P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    none

    System.Security.Cryptography 中没有 Ed25519 类。

    检查记录
    fact eddsa = none
    evidence eddsa: System.Security.Cryptography.Ed25519 type present: False
  • 后量子算法

    none

    在该平台上,MLKem.IsSupported、MLDsa.IsSupported、SlhDsa.IsSupported 以及复合 ML-DSA 的支持情况均为 false。

    检查记录
    fact pqc = none
    evidence pqc: MLKem.IsSupported False, MLDsa.IsSupported False, SlhDsa.IsSupported False, CompositeMLDsa.IsSupported(MLDsa65WithECDsaP256) False
  • 平台自带的 OpenSSL

    3.0.13

    在 Linux 上,这些类使用系统的 OpenSSL,这里是 Ubuntu 24.04 自带的 3.0.13,该版本早于 ML-KEM 和 ML-DSA 的加入。

    检查记录
    fact platform-openssl = 3.0.13
    evidence platform-openssl: OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
  • 公钥格式

    spki-pemspki-derraw-xy

    公钥可从 SPKI PEM(ImportFromPem)、SPKI DER 以及 ECParameters 中的原始 X、Y 坐标导入。

    检查记录
    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
  • 私钥格式

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

    私钥可从 PKCS#8 PEM 和 DER、加密的 PKCS#8、SEC1 PEM 和 DER,以及 ECParameters 中的原始标量导入。

    检查记录
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, raw-scalar

用法

分别以默认的 P1363 格式和 DER 格式签名,并演示验证 DER 签名时需要传入匹配的格式参数。
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]);

代码片段测试工具会在容器镜像 mcr.microsoft.com/dotnet/sdk:10.0.402-noble 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
dotnet new console --force -o . >/dev/null
命令
rm -f Program.cs && DOTNET_NOLOGO=1 dotnet run 2>/dev/null
输出必须匹配(正则表达式)
raw: 64 bytes, DER verified: True, DER as raw: False\n-----BEGIN PUBLIC KEY-----

库