from cryptography.exceptions import InvalidSignaturefrom cryptography.hazmat.primitives import hashesfrom cryptography.hazmat.primitives.asymmetric import ecfrom 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")from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature raw = open("sig.raw", "rb").read() # r‖s, 64 bytes on P-256half = len(raw) // 2der = 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
load_pem_public_keyreads the standardBEGIN PUBLIC KEY(SPKI) block and returns anEllipticCurvePublicKey; the curve comes from the key itself.- Argument order is
verify(signature, message, algorithm)— signature first. ec.ECDSA(hashes.SHA256())makes the library hash the message. If you already hold the digest, wrap it asec.ECDSA(utils.Prehashed(hashes.SHA256()))instead.- Success returns
None; failure raisesInvalidSignature. Catch that specific exception — a bareexcepthides real bugs.
Gotchas
verifyaccepts only ASN.1 DER. Raw 64-byte signatures from WebCrypto or a JWT must be rebuilt withencode_dss_signature(second snippet) — fed directly, they fail exactly like a forged signature.- Don't confuse pyca
cryptographywith the olderecdsapackage: that one defaults to raw signatures and raisesBadSignatureError. 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.