Saltar al contenido
ecdsa.com
Python

pyca/cryptography

El paquete cryptography de la Python Cryptographic Authority, la biblioteca criptográfica estándar de Python: ECDSA sobre OpenSSL con firmas DER y RFC 6979 opcional, EdDSA, ML-KEM y ML-DSA, y sin compatibilidad con JWK.
Licencia
Apache-2.0 OR BSD-3-Clause
Versión comprobada
50.0.2 ·
Código fuente
github.com/pyca/cryptography
Documentación
cryptography.io/en/latest/

Compatibilidad de un vistazo

ECDSAEdDSACriptografía poscuántica

Curvas ECDSA
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Formatos de clave pública
spki-pemspki-dersec1-uncompressedsec1-compressedopenssh
Formatos de clave privada
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derraw-scalaropenssh

cryptography es lo que usa la mayor parte del código Python —PyJWT, Authlib, paramiko, certbot, los SDK de AWS y de Google— para la criptografía propiamente dicha. Su capa hazmat expone directamente las claves y las firmas; los wheels incluyen su propio OpenSSL, así que el comportamiento depende de la versión del paquete y no del sistema operativo (la 50.0.2 incluye OpenSSL 4.0.3).

Las firmas ECDSA salen en DER ASN.1, y verify() espera DER. WebCrypto, JWS y la mayoría de los tokens de hardware usan la forma r‖s en bruto de 64 bytes, que verify() rechaza con el mismo InvalidSignature que una firma falsificada. decode_dss_signature y encode_dss_signature convierten entre DER y el par de enteros; el relleno a ancho fijo te toca añadirlo a ti, tal como muestra el ejemplo, o usa el conversor DER ⇄ en bruto para comprobarlo a mano.

El nonce es aleatorio salvo que pidas RFC 6979: ec.ECDSA(hashes.SHA256(), deterministic_signing=True) reprodujo exactamente el vector de prueba P-256 del RFC, incluido su s alto (high-S), porque nada en el paquete normaliza s, y la verificación acepta ambas mitades. En las firmas destinadas a un verificador low-S como libsecp256k1 o @noble/curves, hay que sustituir antes s por n − s. sign() y verify() calculan el hash de los datos por su cuenta; pasa Prehashed(hashes.SHA256()) cuando ya tengas un resumen, o calcularás el hash dos veces.

La cobertura de curvas es amplia —de P-224 a P-521, secp256k1 y las curvas Brainpool r1—, además de Ed25519, Ed448, ML-KEM-768/1024 y ML-DSA-44/65/87 en la 50.0.2. Las claves se cargan desde PEM y DER (SPKI, PKCS#8, PKCS#8 cifrado, SEC1), OpenSSH y puntos SEC1 sin envoltorio; no hay lector de JWK, así que las JWK pasan por PyJWT, Authlib o jwcrypto, o por el conversor de JWK.

La mayoría de los errores que la gente busca vienen de la carga de claves y no de la firma: una clave pública pasada a load_pem_private_key, una clave cifrada sin contraseña o un punto que no está en la curva con nombre. El verificador de firmas y la matriz de interoperabilidad de bibliotecas ayudan cuando una firma procedente de otro stack no se verifica.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/pyca-cryptography/run.sh ejecutó pyca/cryptography 50.0.2 en OpenSSL 4.0.3 29 Sep 2026 en la imagen de contenedor python:3.14.8-slim-trixie el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Codificación de la firma ECDSA

    der

    sign() devuelve DER ASN.1 y verify() espera DER.

    Registrado por la comprobación
    fact ecdsa-signature-encoding = der
  • Low-S al firmar

    either

    s no se normaliza: aparecieron valores high-S entre 64 firmas.

    Registrado por la comprobación
    fact ecdsa-low-s-sign = either
  • High-S al verificar

    accepts-high-s

    Una firma con s sustituido por n − s se verificó.

    Registrado por la comprobación
    fact ecdsa-low-s-verify = accepts-high-s
  • Nonce de ECDSA

    rfc6979-opt-in

    Aleatorio por defecto; deterministic_signing=True reprodujo exactamente el apéndice A.2.5 del RFC 6979, incluido su s alto (high-S).

    Registrado por la comprobación
    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
  • De qué calcula el hash la llamada de firma

    hashes-message-or-digest

    sign() calcula el hash de los datos; con Prehashed(hashes.SHA256()) firma un resumen calculado de antemano, y esa firma se verificó contra el mensaje original.

    Registrado por la comprobación
    fact ecdsa-prehash = hashes-message-or-digest
  • Firma ECDSA mal formada

    throws

    Una firma r‖s en bruto, un blob de 3 bytes y una firma sobre otro mensaje lanzan todos InvalidSignature; la excepción no dice de cuál se trata.

    Registrado por la comprobación
    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
  • Curvas ECDSA

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

    Con P-224, P-256, P-384, P-521, secp256k1 y las curvas Brainpool r1 se firmó y se verificó.

    Registrado por la comprobación
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 y Ed448.

    Registrado por la comprobación
    fact eddsa = Ed25519, Ed448
  • Algoritmos poscuánticos

    ML-KEMML-DSA

    Con ML-KEM-768 y ML-KEM-1024 se encapsuló y desencapsuló, y con ML-DSA-44/65/87 se firmó y verificó; no hay clase ML-KEM-512 ni SLH-DSA.

    Registrado por la comprobación
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: ML-KEM-512 class present: False; SLH-DSA module present: False
  • Formatos de clave pública

    spki-pemspki-dersec1-uncompressedsec1-compressedopenssh

    Las claves públicas se cargaron desde PEM y DER SPKI, OpenSSH y puntos SEC1 (comprimidos o sin comprimir) mediante from_encoded_point; un x‖y de 64 bytes sin el prefijo se rechazó, y no hay compatibilidad con JWK.

    Registrado por la comprobación
    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
  • Formatos de clave privada

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

    Las claves privadas se cargaron desde PEM y DER PKCS#8, PKCS#8 cifrado, PEM y DER SEC1, OpenSSH y un escalar en bruto mediante derive_private_key.

    Registrado por la comprobación
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, raw-scalar, openssh

Uso

Firma con P-256, convierte la firma DER a r‖s en bruto y de nuevo a DER, y verifícala.
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")

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor python:3.14.8-slim-trixie, y la ejecución falla si la salida no coincide.

Configuración
pip install --quiet --root-user-action=ignore cryptography==50.0.2
Comando
python usage.py
La salida debe coincidir (expresión regular)
verified: True, raw bytes: 64

Bibliotecas