跳到主要内容
ecdsa.com
C · OpenSSL · 命令行

OpenSSL

OpenSSL,支撑互联网上大部分 TLS 和许多签名场景的 C 库与命令行工具:DER 签名,随机 nonce(可按需使用 RFC 6979),支持所有常见曲线,并自 3.5 起支持 ML-KEM、ML-DSA 和 SLH-DSA。
许可证
Apache-2.0
检查的版本
3.5.9 ·
源代码
github.com/openssl/openssl
文档
docs.openssl.org/3.5/

兼容性概览

ECDSAEdDSA后量子

ECDSA 曲线
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
后量子算法
ML-KEMML-DSASLH-DSA
公钥格式
spki-pemspki-derx509
私钥格式
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-der

OpenSSL 为 C 提供 libcrypto 和 libssl,并通过绑定为 Node.js、Python、PHP 和 Ruby 提供它们,此外还有大多数人最先接触到的 openssl 命令。本页的事实基于 Alpine 3.24.2 发行版的软件包 OpenSSL 3.5.9(3.5 长期支持版本线)检查得出;当时上游最新的版本是 3.6.5 和 4.0.3,这些版本的行为可能有所不同。

EVP_DigestSign 和 openssl dgst -sign 生成的 ECDSA 签名都是 ASN.1 DER——在 P-256 上为 70 到 72 字节。nonce 是随机的,除非把 nonce-type 参数设为 1(OpenSSL 3.2+),这样能复现 RFC 6979 的 P-256 测试向量;在命令行上,只能像示例那样通过 pkeyutl 使用该参数。s 没有经过规范化,验证时接受 high-S。EVP_PKEY_sign 对你自行计算的摘要签名。

格式错误的输入会报错,而不是返回验证失败:对于 64 字节的原始 r‖s 签名,EVP_DigestVerify 返回 -1,CLI 也如实报告了这一点。openssl dgst -verify 对正确的签名输出 Verified OK,对针对其他数据的有效签名输出 Verification failure,对不是 DER 的 64 字节数据输出 Error verifying data——因此在 3.5.9 中,可以根据措辞区分格式问题与伪造签名。可以用 DER ⇄ 原始格式转换器转换原始签名,或者让 OpenSSL 命令生成器组装正确的调用命令。

曲线涵盖 P-224 到 P-521、secp256k1 和 Brainpool r1 曲线,另有 Ed25519 和 Ed448。自 3.5 起,默认提供程序还实现了 ML-KEM、ML-DSA 和 SLH-DSA,以及 TLS 混合组 X25519MLKEM768。私钥格式为 PKCS#8(未加密或加密)或 SEC1,公钥格式为 SPKI 或证书:genpkey 写出 -----BEGIN PRIVATE KEY-----,旧式的 openssl ec 写出 -----BEGIN EC PRIVATE KEY-----——标为 EC 的文件始终是私钥。

人们搜索的错误信息大多来自密钥加载——no start line、header too long,以及解码器报出的 unsupported——本页链接的错误页面都有说明。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/openssl/run.sh 于2026年10月20日在容器镜像 alpine:3.24.2 中运行了 OpenSSL 3.5.9;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • ECDSA 签名编码

    der

    EVP_DigestSign 和 openssl dgst -sign 生成 ASN.1 DER。

    检查记录
    fact ecdsa-signature-encoding = der
  • ECDSA 的 nonce

    rfc6979-opt-in

    默认随机;把签名参数 nonce-type 设为 1 后,复现了 RFC 6979 附录 A.2.5 的结果。

    检查记录
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: default signatures repeat: no; nonce-type 1 (deterministic) reproduces the RFC 6979 A.2.5 r: yes
  • 签名时的 low-S

    either

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

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

    accepts-high-s

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

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

    hashes-message-or-digest

    EVP_DigestSign 会先计算数据的哈希;EVP_PKEY_sign 对预先计算的摘要签名,该签名针对原消息验证通过。

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

    returns-error

    对于原始 r‖s 签名和 3 字节的数据块,EVP_DigestVerify 返回 -1(表示错误,而不是 0)。

    检查记录
    fact ecdsa-malformed-signature = returns-error
    evidence ecdsa-malformed-signature: EVP_DigestVerify returned -1 for raw r||s and -1 for 3 bytes (1 = valid, 0 = invalid, negative = error)
  • 验证失败时的输出

    distinguishable

    对于非 DER 签名,openssl dgst -verify 输出 Error verifying data;对于针对其他数据的有效签名,则输出 Verification failure。

    检查记录
    fact cli-verify-output = distinguishable
    evidence cli-verify-output: valid: Verified OK; 64 zero bytes: Error verifying data; valid signature over other data: Verification failure
  • ECDSA 曲线

    P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

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

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

    Ed25519Ed448

    Ed25519 和 Ed448。

    检查记录
    fact eddsa = Ed25519, Ed448
  • 后量子算法

    ML-KEMML-DSASLH-DSA

    在默认提供程序中,ML-KEM-768 的封装与解封装、ML-DSA-65 和 SLH-DSA-SHA2-128s 的签名与验证均成功。

    检查记录
    fact pqc = ML-KEM, ML-DSA, SLH-DSA
    evidence pqc: TLS groups with ML-KEM: MLKEM512 MLKEM768 MLKEM1024 SecP256r1MLKEM768 X25519MLKEM768 SecP384r1MLKEM1024 
  • 公钥格式

    spki-pemspki-derx509

    PEM 和 DER 格式的 SPKI 以及 X.509 证书中的公钥都验证通过了同一个签名;携带压缩点的 SPKI 也验证通过。

    检查记录
    fact public-key-formats = spki-pem, spki-der, x509
    evidence public-key-formats: SPKI carrying a compressed point (openssl ec -conv_form compressed) verifies: yes
  • 私钥格式

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-der

    PEM 和 DER 格式的 PKCS#8、加密的 PKCS#8,以及 PEM 和 DER 格式的 SEC1 均签名成功;genpkey 写出 PKCS#8,openssl ec 写出 SEC1。

    检查记录
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der
    evidence private-key-formats: genpkey writes -----BEGIN PRIVATE KEY-----; openssl ec writes -----BEGIN EC PRIVATE KEY-----

用法

生成 P-256 密钥,用 dgst 签名并验证,通过 pkeyutl 确定性地签名,并生成 ML-DSA-65 签名。
# P-256 key as PKCS#8, public key as SPKI.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out key.pem
openssl pkey -in key.pem -pubout -out pub.pem

printf 'order #1042 approved' > msg.txt
openssl dgst -sha256 -sign key.pem -out sig.der msg.txt   # ASN.1 DER, 70-72 bytes
openssl dgst -sha256 -verify pub.pem -signature sig.der msg.txt

# Deterministic RFC 6979 signing (OpenSSL 3.2+) goes through pkeyutl.
openssl pkeyutl -sign -inkey key.pem -rawin -digest sha256 -pkeyopt nonce-type:1 -in msg.txt -out det1.der
openssl pkeyutl -sign -inkey key.pem -rawin -digest sha256 -pkeyopt nonce-type:1 -in msg.txt -out det2.der
cmp -s det1.der det2.der && echo "deterministic: identical signatures"

# Post-quantum: ML-DSA-65 key and signature.
openssl genpkey -algorithm ML-DSA-65 -out mldsa.pem
openssl pkeyutl -sign -inkey mldsa.pem -rawin -in msg.txt -out mldsa.sig
echo "ML-DSA-65 signature: $(wc -c < mldsa.sig) bytes"

代码片段测试工具会在容器镜像 alpine:3.24.2 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
apk add --no-cache -q openssl=3.5.9-r0 >/dev/null
命令
sh usage.sh
输出必须匹配(正则表达式)
Verified OK\ndeterministic: identical signatures\nML-DSA-65 signature: 3309 bytes

库