ecdsa.com

Verify a signature · Python

How to verify an ECDSA signature in Python

The cryptography package (pyca) is the standard way to verify an ECDSA signature in Python. Its verify method is strict by design: DER-encoded signatures only, an exception instead of a return value, and the hash wrapped in ec.ECDSA.

Tested with Python 3.14 and cryptography 50.0.

Same recipe in:Node.jsPythonGoOpenSSLWebCrypto
verify.py — verify a DER signature
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_public_key
 
public_key = load_pem_public_key(open("pub.pem", "rb").read())
message = open("message.txt", "rb").read()
signature = open("sig.der", "rb").read() # ASN.1 DER
 
try:
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
print("valid")
except InvalidSignature:
print("INVALID")
raw 64-byte signatures: re-encode to DER first
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
 
raw = open("sig.raw", "rb").read() # r‖s, 64 bytes on P-256
half = len(raw) // 2
der = encode_dss_signature(
int.from_bytes(raw[:half], "big"),
int.from_bytes(raw[half:], "big"),
)
public_key.verify(der, message, ec.ECDSA(hashes.SHA256()))
print("valid")

How it works

  1. load_pem_public_key reads the standard BEGIN PUBLIC KEY (SPKI) block and returns an EllipticCurvePublicKey; the curve comes from the key itself.
  2. Argument order is verify(signature, message, algorithm) — signature first.
  3. ec.ECDSA(hashes.SHA256()) makes the library hash the message. If you already hold the digest, wrap it as ec.ECDSA(utils.Prehashed(hashes.SHA256())) instead.
  4. Success returns None; failure raises InvalidSignature. Catch that specific exception — a bare except hides real bugs.

Gotchas

  • verify accepts only ASN.1 DER. Raw 64-byte signatures from WebCrypto or a JWT must be rebuilt with encode_dss_signature (second snippet) — fed directly, they fail exactly like a forged signature.
  • Don't confuse pyca cryptography with the older ecdsa package: that one defaults to raw signatures and raises BadSignatureError. Recipes for one do not transfer to the other.
  • Nothing enforces the conventional curve/hash pairing — a P-256 signature made over SHA-384 verifies only with hashes.SHA384(). Ask the signer what they hashed with.

Related recipes