from cryptography.hazmat.primitives import hashesfrom cryptography.hazmat.primitives.asymmetric import ecfrom cryptography.hazmat.primitives.serialization import load_pem_private_key private_key = load_pem_private_key(open("key.pem", "rb").read(), password=None)message = open("message.txt", "rb").read() signature = private_key.sign(message, ec.ECDSA(hashes.SHA256())) # ASN.1 DERopen("sig.der", "wb").write(signature)print(len(signature), "bytes") # 70-72 on P-256, varies per signaturefrom cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature r, s = decode_dss_signature(signature)raw = r.to_bytes(32, "big") + s.to_bytes(32, "big") # 64 bytes on P-256open("sig.raw", "wb").write(raw)How it works
load_pem_private_keyhandles PKCS#8 and SEC1 PEMs;password=Noneis required (positionally or by name) for unencrypted keys.sign(message, ec.ECDSA(hashes.SHA256()))hashes internally. For a pre-computed digest, useec.ECDSA(utils.Prehashed(hashes.SHA256())).- The result is DER — variable length, starting with
0x30.decode_dss_signatureexposes the integers when a fixed-width encoding is needed.
Gotchas
- Two signatures over the same message will differ: pyca
cryptographyuses a random nonce per signature. Don't write tests that expect byte-identical output. r.to_bytes(32, …)is the P-256 width. On P-384 use 48, on P-521 use 66 —to_bytes(32, …)on a larger curve raisesOverflowError, which is your hint, not a library bug.- The PyPI package named
ecdsais a different library with raw-by-default output and its own API. Copy-pasting between the two ecosystems produces signatures in the wrong format.