Zum Inhalt springen
ecdsa.com
Python

pyca/cryptography

Das Paket cryptography der Python Cryptographic Authority, die Standard-Kryptobibliothek für Python: ECDSA auf OpenSSL-Basis mit DER-Signaturen und RFC 6979 auf Wunsch, EdDSA, ML-KEM und ML-DSA – und keine Unterstützung für JWK.
Lizenz
Apache-2.0 OR BSD-3-Clause
Geprüfte Version
50.0.2 ·
Quellcode
github.com/pyca/cryptography
Dokumentation
cryptography.io/en/latest/

Kompatibilität auf einen Blick

ECDSAEdDSAPost-Quanten-Verfahren

ECDSA-Kurven
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Formate öffentlicher Schlüssel
spki-pemspki-dersec1-uncompressedsec1-compressedopenssh
Formate privater Schlüssel
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derraw-scalaropenssh

Auf cryptography greift der meiste Python-Code – PyJWT, Authlib, paramiko, certbot, die SDKs von AWS und Google – für echte Kryptografie zurück. Die Schicht hazmat macht Schlüssel und Signaturen direkt zugänglich; die Wheels bringen ein eigenes OpenSSL mit, sodass sich das Verhalten nach der Paketversion richtet und nicht nach dem Betriebssystem (50.0.2 liefert OpenSSL 4.0.3 mit).

ECDSA-Signaturen kommen als ASN.1-DER heraus, und verify() erwartet DER. WebCrypto, JWS und die meisten Hardware-Tokens verwenden die 64 Byte lange rohe Form r‖s, die verify() mit derselben Ausnahme InvalidSignature ablehnt wie eine gefälschte Signatur. decode_dss_signature und encode_dss_signature konvertieren zwischen DER und dem Paar ganzer Zahlen; das Padding auf feste Breite fügst du selbst hinzu, genau wie es das Beispiel zeigt, oder du prüfst von Hand mit dem Konverter DER ⇄ Raw.

Die Nonce ist zufällig, sofern du nicht RFC 6979 anforderst: ec.ECDSA(hashes.SHA256(), deterministic_signing=True) reproduzierte den P-256-Testvektor des RFC exakt – einschließlich seines High-S-Werts, denn nichts im Paket normalisiert s, und die Prüfung akzeptiert beide Hälften. Bei Signaturen, die an einen Low-S-Verifizierer wie libsecp256k1 oder @noble/curves gehen, musst du s zuerst durch n − s ersetzen. sign() und verify() hashen die Daten selbst; übergib Prehashed(hashes.SHA256()), wenn du bereits einen Digest hast, sonst hashst du doppelt.

Die Kurvenabdeckung ist breit – P-224 bis P-521, secp256k1 und die Brainpool-r1-Kurven –, dazu kommen in 50.0.2 Ed25519, Ed448, ML-KEM-768/1024 und ML-DSA-44/65/87. Schlüssel lassen sich aus PEM und DER (SPKI, PKCS#8, verschlüsseltes PKCS#8, SEC1), OpenSSH und bloßen SEC1-Punkten laden; einen JWK-Leser gibt es nicht, JWKs laufen daher über PyJWT, Authlib oder jwcrypto oder über den JWK-Konverter.

Die meisten Fehler, nach denen gesucht wird, entstehen beim Laden von Schlüsseln, nicht beim Signieren: ein öffentlicher Schlüssel, der an load_pem_private_key übergeben wird, ein verschlüsselter Schlüssel ohne Passwort oder ein Punkt, der nicht auf der benannten Kurve liegt. Der Signatur-Verifizierer und das Interop-Cheatsheet für Bibliotheken helfen, wenn eine Signatur aus einem anderen Stack die Prüfung einfach nicht bestehen will.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/pyca-cryptography/run.sh hat pyca/cryptography 50.0.2 unter OpenSSL 4.0.3 29 Sep 2026 am 20. Oktober 2026 im Container-Image python:3.14.8-slim-trixie ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • Kodierung von ECDSA-Signaturen

    der

    sign() liefert ASN.1-DER, und verify() erwartet DER.

    Von der Prüfung aufgezeichnet
    fact ecdsa-signature-encoding = der
  • Low-S beim Signieren

    either

    s wird nicht normalisiert: Unter 64 Signaturen traten High-S-Werte auf.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-sign = either
  • High-S bei der Prüfung

    accepts-high-s

    Eine Signatur, in der s durch n − s ersetzt war, bestand die Prüfung.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
  • ECDSA-Nonce

    rfc6979-opt-in

    Standardmäßig zufällig; deterministic_signing=True reproduzierte Anhang A.2.5 von RFC 6979 exakt, einschließlich seines High-S-Werts.

    Von der Prüfung aufgezeichnet
    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
  • Was der Signaturaufruf hasht

    hashes-message-or-digest

    sign() hasht die Daten; mit Prehashed(hashes.SHA256()) signiert die Methode einen selbst berechneten Digest, und diese Signatur bestand die Prüfung gegen die ursprüngliche Nachricht.

    Von der Prüfung aufgezeichnet
    fact ecdsa-prehash = hashes-message-or-digest
  • Fehlerhafte ECDSA-Signatur

    throws

    Eine rohe r‖s-Signatur, ein 3-Byte-Blob und eine Signatur über eine andere Nachricht lösen alle InvalidSignature aus; die Ausnahme verrät nicht, welcher Fall vorliegt.

    Von der Prüfung aufgezeichnet
    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-Kurven

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

    Signieren und Prüfen funktionierten mit P-224, P-256, P-384, P-521, secp256k1 und den Brainpool-r1-Kurven.

    Von der Prüfung aufgezeichnet
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 und Ed448.

    Von der Prüfung aufgezeichnet
    fact eddsa = Ed25519, Ed448
  • Post-Quanten-Algorithmen

    ML-KEMML-DSA

    Kapselung und Entkapselung funktionierten mit ML-KEM-768 und ML-KEM-1024, Signieren und Prüfen mit ML-DSA-44/65/87; eine Klasse für ML-KEM-512 gibt es nicht, ebenso wenig SLH-DSA.

    Von der Prüfung aufgezeichnet
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: ML-KEM-512 class present: False; SLH-DSA module present: False
  • Formate öffentlicher Schlüssel

    spki-pemspki-dersec1-uncompressedsec1-compressedopenssh

    Öffentliche Schlüssel wurden aus SPKI-PEM und -DER, OpenSSH sowie über from_encoded_point aus SEC1-Punkten (komprimiert oder unkomprimiert) geladen; ein 64 Byte langes x‖y ohne Präfix wurde abgelehnt, und JWK wird nicht unterstützt.

    Von der Prüfung aufgezeichnet
    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
  • Formate privater Schlüssel

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

    Private Schlüssel wurden aus PKCS#8-PEM und -DER, verschlüsseltem PKCS#8, SEC1-PEM und -DER, OpenSSH sowie über derive_private_key aus einem rohen Skalar geladen.

    Von der Prüfung aufgezeichnet
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, raw-scalar, openssh

Verwendung

Signiere mit P-256, konvertiere die DER-Signatur in rohes r‖s und zurück und prüfe sie.
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")

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image python:3.14.8-slim-trixie aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Einrichtung
pip install --quiet --root-user-action=ignore cryptography==50.0.2
Befehl
python usage.py
Ausgabe muss passen (regulärer Ausdruck)
verified: True, raw bytes: 64

Bibliotheken