跳到主要内容
ecdsa.com
Python

pyca/cryptography

Python Cryptographic Authority 的 cryptography 包,Python 的标准密码学库:基于 OpenSSL 的 ECDSA,签名为 DER 格式,RFC 6979 需显式启用;支持 EdDSA、ML-KEM 和 ML-DSA,不支持 JWK。
许可证
Apache-2.0 OR BSD-3-Clause
检查的版本
50.0.2 ·
源代码
github.com/pyca/cryptography
文档
cryptography.io/en/latest/

兼容性概览

ECDSAEdDSA后量子

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

大多数 Python 代码——PyJWT、Authlib、paramiko、certbot,以及 AWS 和 Google 的 SDK——在执行真正的密码学运算时都使用 cryptography。它的 hazmat 层直接暴露密钥和签名;wheel 包自带 OpenSSL,因此行为取决于包的版本,而不是操作系统(50.0.2 自带 OpenSSL 4.0.3)。

ECDSA 签名输出为 ASN.1 DER,verify() 也按 DER 解析。WebCrypto、JWS 和大多数硬件令牌使用 64 字节的原始 r‖s 形式,verify() 会拒绝它,抛出的 InvalidSignature 与伪造签名的相同。decode_dss_signature 和 encode_dss_signature 在 DER 与整数对之间转换;定宽填充需要你自己补上,做法与示例完全一样,也可以用 DER ⇄ 原始格式转换器手动检查。

除非你要求使用 RFC 6979,否则 nonce 是随机的:ec.ECDSA(hashes.SHA256(), deterministic_signing=True) 完全复现了该 RFC 的 P-256 测试向量——包括其中的 high-S 值,因为这个包不会对 s 做任何规范化,验证时两个半区的 s 都接受。要交给 libsecp256k1 或 @noble/curves 等要求 low-S 的验证器的签名,需要先把 s 替换为 n − s。sign() 和 verify() 会自行对数据计算哈希;如果你手上已经是摘要,要传入 Prehashed(hashes.SHA256()),否则会计算两次哈希。

曲线覆盖面很广——P-224 到 P-521、secp256k1 和 Brainpool r1 曲线——50.0.2 还支持 Ed25519、Ed448、ML-KEM-768/1024 和 ML-DSA-44/65/87。密钥可从 PEM 和 DER(SPKI、PKCS#8、加密的 PKCS#8、SEC1)、OpenSSH 以及裸 SEC1 点加载;它没有 JWK 读取器,因此 JWK 需要借助 PyJWT、Authlib 或 jwcrypto,或者 JWK 转换器来处理。

人们搜索的大多数错误来自密钥加载,而不是签名:把公钥传给了 load_pem_private_key、加密的密钥没有提供密码,或者点不在指定的命名曲线上。当来自其他技术栈的签名无法验证通过时,签名验证工具和库互操作矩阵可以帮上忙。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/pyca-cryptography/run.sh 于2026年10月20日在容器镜像 python:3.14.8-slim-trixie 中运行了 pyca/cryptography 50.0.2(运行时为 OpenSSL 4.0.3 29 Sep 2026);本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • ECDSA 签名编码

    der

    sign() 返回 ASN.1 DER,verify() 也按 DER 解析。

    检查记录
    fact ecdsa-signature-encoding = der
  • 签名时的 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
  • ECDSA 的 nonce

    rfc6979-opt-in

    默认随机;deterministic_signing=True 完全复现了 RFC 6979 附录 A.2.5,包括其中的 high-S 值。

    检查记录
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: default repeats: False; deterministic_signing=True reproduces RFC 6979 A.2.5 (r and high s): True
  • 签名调用对哪些内容做哈希

    hashes-message-or-digest

    sign() 会先对数据计算哈希;使用 Prehashed(hashes.SHA256()) 时,它对你自行计算的摘要签名,该签名针对原消息验证通过。

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

    throws

    原始 r‖s 签名、3 字节的数据块和针对另一条消息的签名都会抛出 InvalidSignature;异常不会说明是哪一种情况。

    检查记录
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: raw r||s: InvalidSignature
    evidence ecdsa-malformed-signature: 3 bytes: InvalidSignature
    evidence ecdsa-malformed-signature: wrong message: InvalidSignature
  • ECDSA 曲线

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

    P-224、P-256、P-384、P-521、secp256k1 和 Brainpool r1 曲线的签名与验证均成功。

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

    ML-KEM-768 和 ML-KEM-1024 的封装与解封装、ML-DSA-44/65/87 的签名与验证均成功;没有 ML-KEM-512 类,也没有 SLH-DSA。

    检查记录
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: ML-KEM-512 class present: False; SLH-DSA module present: False
  • 公钥格式

    spki-pemspki-dersec1-uncompressedsec1-compressedopenssh

    公钥可从 SPKI PEM 和 DER、OpenSSH 加载,也可通过 from_encoded_point 从 SEC1 点(压缩点或未压缩点)加载;不带前缀的 64 字节 x‖y 被拒绝,也不支持 JWK。

    检查记录
    fact public-key-formats = spki-pem, spki-der, sec1-uncompressed, sec1-compressed, openssh
    evidence public-key-formats: no JWK reader or writer in the package
  • 私钥格式

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derraw-scalaropenssh

    私钥可从 PKCS#8 PEM 和 DER、加密的 PKCS#8、SEC1 PEM 和 DER、OpenSSH 加载,也可通过 derive_private_key 从原始标量加载。

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

用法

用 P-256 签名,把 DER 签名转换为原始 r‖s 再转换回来,然后验证。
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature

key = ec.generate_private_key(ec.SECP256R1())
message = b"order #1042 approved"

# DER out of sign(); turn it into the 64-byte r||s that WebCrypto and JOSE expect.
der = key.sign(message, ec.ECDSA(hashes.SHA256()))
r, s = decode_dss_signature(der)
raw = r.to_bytes(32, "big") + s.to_bytes(32, "big")

# A raw signature from WebCrypto goes the other way before verify().
back = encode_dss_signature(int.from_bytes(raw[:32], "big"), int.from_bytes(raw[32:], "big"))
try:
    key.public_key().verify(back, message, ec.ECDSA(hashes.SHA256()))
    print("verified: True, raw bytes:", len(raw))
except InvalidSignature:
    print("verified: False")

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

环境准备
pip install --quiet --root-user-action=ignore cryptography==50.0.2
命令
python usage.py
输出必须匹配(正则表达式)
verified: True, raw bytes: 64

库