Aller au contenu
ecdsa.com
Python

python-ecdsa

La bibliothèque ECDSA et EdDSA en pur Python de tlsfuzzer : signatures r‖s brutes sur SHA-1 sauf indication contraire, nonces aléatoires par défaut, la RFC 6979 étant accessible par une simple méthode, et clés publiques sous forme de x‖y nu.
Licence
MIT
Version vérifiée
0.19.2 ·
Code source
github.com/tlsfuzzer/python-ecdsa
Documentation
ecdsa.readthedocs.io/en/latest/

Compatibilité en un coup d’œil

ECDSAEdDSA

Courbes ECDSA
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Formats de clé publique
spki-pemspki-dersec1-uncompressedsec1-compressedraw-xy
Formats de clé privée
pkcs8-pemsec1-pemsec1-derraw-scalar

ecdsa implémente ECDSA et EdDSA en pur Python, sans OpenSSL en dessous. Il s’installe donc partout et se prête bien aux vecteurs de test et à l’enseignement, et il reste une dépendance d’anciennes piles JOSE comme python-jose. Ses mainteneurs déclarent qu’il n’est pas conçu pour résister aux attaques par canal auxiliaire : en production, la signature relève de pyca/cryptography ou de coincurve.

Trois valeurs par défaut causent presque tous les échecs d’interopérabilité. La fonction de hachage par défaut est SHA-1 : une SigningKey créée sans hashfunc signe des condensats SHA-1, ce que les essais ont confirmé en vérifiant la signature par défaut avec pyca/cryptography (elle a réussi avec SHA-1 et échoué avec SHA-256). La signature est en r‖s brut (64 octets sur P-256), pas en DER. Enfin, VerifyingKey.to_string() renvoie le x‖y nu de 64 octets sans préfixe 0x04, qu’OpenSSL, Go et WebCrypto ne lisent pas comme un point.

La signature utilise un nonce aléatoire, sauf si vous appelez sign_deterministic(), qui a reproduit le vecteur de test P-256 de la RFC 6979. s est laissé tel que calculé (élevé environ une fois sur deux), sauf si vous choisissez l’un des encodeurs *_canonize, et la vérification accepte l’une ou l’autre moitié. Chaque choix d’encodage se fait à chaque appel via sigencode= et sigdecode=, si bien que les côtés signature et vérification doivent désigner la même paire ; une signature DER passée au décodeur brut par défaut lève BadSignatureError: Malformed formatting of signature.

La prise en charge des courbes est la plus large des bibliothèques Python (de P-224 à P-521, secp256k1 et Brainpool), avec en plus Ed25519 et Ed448. Les clés s’importent en SPKI PEM ou DER, en points SEC1 et en scalaires bruts ; to_pem() écrit du SEC1 EC PRIVATE KEY, sauf si vous passez format="pkcs8".

L’exemple corrige les trois valeurs par défaut d’un coup : SHA-256, un nonce déterministe et une signature DER canonique low-S. Pour confronter une signature qu’il a produite à une autre bibliothèque, le convertisseur DER ⇄ brut et les vecteurs de test sont le chemin le plus rapide.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/python-ecdsa/run.sh a exécuté python-ecdsa 0.19.2 dans l’image de conteneur python:3.14.8-slim-trixie le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Encodage des signatures ECDSA

    raw

    sign() renvoie le r‖s brut de 64 octets sur P-256 ; sigencode_der produit du DER, et la vérification a alors besoin de sigdecode_der.

    Relevé par la vérification
    fact ecdsa-signature-encoding = raw
    evidence ecdsa-signature-encoding: sigencode_der output parses as DER: True; verifies with sigdecode_der: verified
  • Fonction de hachage par défaut

    sha1

    Sans hashfunc, les clés signent des condensats SHA-1 : la signature par défaut a été vérifiée dans pyca/cryptography avec SHA-1, pas avec SHA-256.

    Relevé par la vérification
    fact ecdsa-default-hash = sha1
    evidence ecdsa-default-hash: checked by verifying the default signature in pyca/cryptography with SHA-1 and with SHA-256
  • Low-S à la signature

    either

    Les encodeurs simples gardent s tel que calculé (des valeurs high-S sont apparues parmi 64 signatures) ; sigencode_string_canonize et sigencode_der_canonize le normalisent.

    Relevé par la vérification
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: high-S among 64 plain signatures: True; among 64 with sigencode_string_canonize: False
    evidence ecdsa-low-s-sign: sigencode_strings_canonize exists: True
  • High-S à la vérification

    accepts-high-s

    Une signature dont s a été remplacé par n − s a été vérifiée.

    Relevé par la vérification
    fact ecdsa-low-s-verify = accepts-high-s
  • Nonce ECDSA

    rfc6979-opt-in

    sign() utilise un nonce aléatoire ; sign_deterministic() a reproduit l’annexe A.2.5 de la RFC 6979.

    Relevé par la vérification
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: sign() repeats: False; sign_deterministic() reproduces RFC 6979 A.2.5: True
  • Ce que l’appel de signature hache

    hashes-message-or-digest

    sign() hache le message ; sign_digest() signe un condensat calculé par vos soins, et cette signature a été vérifiée sur le message.

    Relevé par la vérification
    fact ecdsa-prehash = hashes-message-or-digest
  • Signature ECDSA mal formée

    throws

    Une entrée de mauvaise longueur lève BadSignatureError: Malformed formatting of signature ; un encodage valide portant sur un autre message lève BadSignatureError: Signature verification failed.

    Relevé par la vérification
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: DER where raw is expected: BadSignatureError: Malformed formatting of signature
    evidence ecdsa-malformed-signature: 3 bytes: BadSignatureError: Malformed formatting of signature
    evidence ecdsa-malformed-signature: wrong message: BadSignatureError: Signature verification failed
  • Courbes ECDSA

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

    P-224, P-256, P-384, P-521, secp256k1 et les courbes Brainpool r1.

    Relevé par la vérification
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 et Ed448.

    Relevé par la vérification
    fact eddsa = Ed25519, Ed448
  • Algorithmes post-quantiques

    none

    Aucun algorithme post-quantique.

    Relevé par la vérification
    fact pqc = none
  • Format de clé publique par défaut

    raw-xy

    VerifyingKey.to_string() renvoie 64 octets de x‖y sans le préfixe 0x04 ; passez "uncompressed" ou "compressed" pour obtenir les encodages SEC1.

    Relevé par la vérification
    fact default-public-key = raw-xy
  • Formats de clé publique

    spki-pemspki-dersec1-uncompressedsec1-compressedraw-xy

    Clés publiques chargées depuis SPKI PEM et DER, des points SEC1 et le x‖y nu.

    Relevé par la vérification
    fact public-key-formats = spki-pem, spki-der, sec1-uncompressed, sec1-compressed, raw-xy
  • Formats de clé privée

    pkcs8-pemsec1-pemsec1-derraw-scalar

    Clés privées chargées depuis PKCS#8 PEM, SEC1 PEM et DER, et des scalaires bruts ; to_pem() écrit -----BEGIN EC PRIVATE KEY----- par défaut.

    Relevé par la vérification
    fact private-key-formats = pkcs8-pem, sec1-pem, sec1-der, raw-scalar
    evidence private-key-formats: to_pem() without format writes -----BEGIN EC PRIVATE KEY-----

Utilisation

Signer avec SHA-256, un nonce RFC 6979 et un encodage DER canonique low-S, puis vérifier.
import hashlib

from ecdsa import NIST256p, SigningKey
from ecdsa.util import sigdecode_der, sigencode_der_canonize

key = SigningKey.generate(curve=NIST256p, hashfunc=hashlib.sha256)  # the default would be SHA-1
public = key.get_verifying_key()
message = b"order #1042 approved"

# RFC 6979 nonce, DER encoding, s normalized to the low half: what OpenSSL-style
# verifiers read and what strict low-S verifiers accept.
signature = key.sign_deterministic(message, sigencode=sigencode_der_canonize)
again = key.sign_deterministic(message, sigencode=sigencode_der_canonize)

print("deterministic:", signature == again)
print("verified:", public.verify(signature, message, sigdecode=sigdecode_der))
print(public.to_pem().decode().splitlines()[0])

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur python:3.14.8-slim-trixie, et l’exécution échoue si la sortie ne correspond pas.

Mise en place
pip install --quiet --root-user-action=ignore ecdsa==0.19.2
Commande
python usage.py
La sortie doit correspondre à cette expression régulière
deterministic: True\nverified: True\n-----BEGIN PUBLIC KEY-----

Bibliothèques