mrkeyoor.com_
Sun 20 Sept 12:44 UTC
PyPISecurityupdated 20 Sept 2026

ecdsa review

Our Python 3.12 sandbox loaded ecdsa 0.19.2 in 0.11 seconds, but pip-audit still found one known vulnerability. This is a pure-Python toolkit for ECDSA signatures, Ed25519 and Ed448 signatures, ECDH, curve arithmetic, and PEM, DER, raw-point, or OpenSSH serialization. Its readable internals suit protocol tests and teaching. Version 0.19.2 fixes CVE-2026-33936, a truncated-DER bug that could make high-level loaders throw unexpected exceptions. Upstream explicitly warns that private operations have no side-channel defense, which rules out production secrets.

Verdict

Our ecdsa 0.19.2 install took 0.3 seconds and 1 MB, yet pip-audit found 1 known vulnerability and upstream rejects production private-key use. Keep it for curve experiments and interoperability fixtures where readable pure Python is the reason for choosing it.

We installed it

Lab card: what happened when we installed ecdsaScreenshot of ecdsa documentation
Install✓ · 0.3s2 packages on disk · 1 MB
Importimport ecdsa in 0.11s · pure Python · requires Python >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
Known vulns1(pip-audit)

Answers from our run

Does ecdsa install cleanly?

Yes. In a fresh container with an empty cache, pip install ecdsa finished in 0.3s, leaving 2 packages and 1 MB on disk. pip-audit reported 1 known vulnerability.

What does ecdsa need to run?

Python >=2.6, !=3.0., !=3.1., !=3.2., !=3.3., !=3.4., !=3.5., and nothing compiled: it is pure Python. In our run import ecdsa succeeded in 0.11s.

ecdsa or cryptography: which should you use?

cryptography: Use it for production ECDSA, certificate work, and OpenSSL-backed key handling. Our ecdsa 0.19.2 install took 0.3 seconds and 1 MB, yet pip-audit found 1 known vulnerability and upstream rejects production private-key use.

When should you not use ecdsa?

The private key protects accounts, funds, identities, or confidential data; upstream says one observed private operation may expose the key through side channels

API stability4/5The current API still revolves around SigningKey, VerifyingKey, ECDH, curve objects, and explicit serializer helpers, so established call sites remain recognizable. Release 0.19 added to_ssh for Ed25519 and marked int_to_string, string_to_int, and digest_integer for later removal instead of deleting them immediately. The 0.x version and old defaults still warrant pinned parameters and upgrade tests around encoding and exception behavior.
Docs4/5The README names every supported curve family, demonstrates raw, PEM, DER, signature, and ECDH flows, and explains the SHA-1 and NIST192p defaults. Its security section gives a direct list of timing, co-resident-code, power, and RF threats, then points production users to an OpenSSL wrapper. Read the Docs covers the API, though a caller must connect several pages to map malformed keys to UnexpectedDER or MalformedPointError.
Maintenance4/5Version 0.19.2 was published on March 26, 2026 specifically to fix CVE-2026-33936 in DER parsing, and GitHub records another push on June 8, 2026. The repository is not archived and currently reports 20 issues and pull requests combined. CI badges cover ordinary tests, condition coverage, mutation testing, and CodeQL. Continued Python 2.6 and 2.7 compatibility does leave the project carrying constraints that most current libraries dropped.
Ecosystem4/5The registry snapshot records 13,192,785 weekly downloads, while GitHub reports 974 stars. Support for OpenSSL-compatible PEM and DER, compressed points, OpenSSH Ed25519 output, and several curve families makes the package useful in compatibility suites. That reach stops at the primitive layer: certificate chains, TLS configuration, JWT validation rules, and wallet protocols still require separate libraries and separate security decisions.

Use it if

  • You need to step through elliptic-curve arithmetic in Python for teaching, debugging, or a reproducible experiment
  • Your interoperability tests must produce uncommon curves, point encodings, or deliberately malformed DER that a high-level crypto API hides
  • A compiler-free test environment needs ECDSA, EdDSA, or ECDH and the material has no production value
  • You need NIST, SEC, Brainpool, secp256k1, Ed25519, and Ed448 operations behind one Python package
Skip it if

Setup reality

We installed ecdsa 0.19.2 on Python 3.12 in 0.3 seconds. The sandbox ended with 2 packages taking 1 MB, and import ecdsa worked in 0.11 seconds. Its metadata lists 3 direct dependency entries, including optional arithmetic extras. The code is pure Python, carries an MIT license, and has no py.typed marker. pip-audit reported 1 known vulnerability, so the successful install is not a clean security bill.

There is no account or configuration file. Curve, hash, and representation live in your code. SigningKey.generate() defaults to NIST192p, and sign() defaults to SHA-1. Raw private or public bytes omit the curve identifier, while PEM and DER retain it. External tools commonly expect DER signatures; ecdsa's default signature encoding is the fixed-width r value followed by s, so OpenSSL interop needs sigencode_der and sigdecode_der.

Version 0.19.2 detects truncated data inside three DER helpers used by loaders such as SigningKey.from_der(). Treat 0.19.1 and earlier as unsuitable for supplied key material. Verification has another control-flow surprise: verify() returns True when the signature matches and raises BadSignatureError when it does not. Parsing failures use exceptions such as UnexpectedDER or MalformedPointError, so distinguish bad signatures from bad encodings at the input boundary.

gmpy or gmpy2 is discovered when the module starts and can accelerate the arithmetic. Either extra adds a compiled component to an otherwise pure-Python install. Neither changes the upstream warning about timing, shared hosts, power observation, or RF leakage during private operations. For secrets exposed to an attacker, move the operation to cryptography or another maintained native implementation instead of treating the optional backend as a hardening switch.

Patterns

Generate a P-256 key with explicit defaults generate-p256-key

import hashlib
from ecdsa import NIST256p, SigningKey

signing_key = SigningKey.generate(
    curve=NIST256p,
    hashfunc=hashlib.sha256,
)
verifying_key = signing_key.verifying_key

The no-argument choices are NIST192p and SHA-1. Set the curve and hash beside the key creation.

Create a deterministic ECDSA signature sign-rfc6979

import hashlib

signature = signing_key.sign_deterministic(
    b"invoice:481",
    hashfunc=hashlib.sha256,
)

sign_deterministic() derives the ECDSA nonce through RFC 6979. Plain sign() obtains a fresh random nonce.

Turn signature failure into a boolean verify-and-reject

from ecdsa import BadSignatureError

try:
    verifying_key.verify(signature, b"invoice:481")
except BadSignatureError:
    accepted = False
else:
    accepted = True

A mismatch raises BadSignatureError. verify() returns True only for a matching signature.

Write private and public PEM files save-pem-keys

from pathlib import Path

Path("private.pem").write_bytes(signing_key.to_pem())
Path("public.pem").write_bytes(verifying_key.to_pem())

to_pem() produces bytes. PEM records the curve identifier for the matching loader.

Load a key pair from PEM load-pem-keys

from pathlib import Path
from ecdsa import SigningKey, VerifyingKey

signing_key = SigningKey.from_pem(Path("private.pem").read_bytes())
verifying_key = VerifyingKey.from_pem(Path("public.pem").read_bytes())

Malformed PEM can raise UnexpectedDER or MalformedPointError. Handle parsing separately from signature rejection.

Use DER signatures with external tools exchange-der-signatures

import hashlib
from ecdsa.util import sigdecode_der, sigencode_der

der_signature = signing_key.sign_deterministic(
    payload,
    hashfunc=hashlib.sha256,
    sigencode=sigencode_der,
)
verifying_key.verify(
    der_signature,
    payload,
    hashfunc=hashlib.sha256,
    sigdecode=sigdecode_der,
)

The built-in encoding concatenates fixed-width r and s values. OpenSSL-style consumers generally expect DER.

Round-trip raw private bytes reload-raw-private-key

from ecdsa import NIST256p, SigningKey

raw = signing_key.to_string()
restored = SigningKey.from_string(
    raw,
    curve=NIST256p,
    hashfunc=hashlib.sha256,
)

Raw key bytes contain no curve name. Loading them with the wrong curve fails or describes different key material.

Serialize a compressed public point compress-public-point

from ecdsa import NIST256p, VerifyingKey

compressed = verifying_key.to_string(encoding="compressed")
restored = VerifyingKey.from_string(
    compressed,
    curve=NIST256p,
)

from_string() recognizes compressed point syntax, while the caller still supplies the curve.

Compute an ECDH shared value derive-ecdh-value

from ecdsa import ECDH, NIST256p

exchange = ECDH(curve=NIST256p)
exchange.generate_private_key()
public_pem = exchange.get_public_key().to_pem()
exchange.load_received_public_key_pem(peer_public_pem)
shared = exchange.generate_sharedsecret_bytes()

generate_sharedsecret_bytes() returns raw ECDH output. Derive an encryption key from it with the protocol's KDF.

Sign bytes with Ed25519 sign-ed25519-message

from ecdsa import Ed25519, SigningKey

signing_key = SigningKey.generate(curve=Ed25519)
verifying_key = signing_key.verifying_key
signature = signing_key.sign(b"release-manifest")
assert verifying_key.verify(signature, b"release-manifest")

Ed25519 performs its own hashing and produces deterministic signatures. hashfunc and sign_deterministic() do not apply.

Export an Ed25519 SSH public key export-ed25519-ssh

from ecdsa import Ed25519, SigningKey

public_key = SigningKey.generate(curve=Ed25519).verifying_key
print(public_key.to_ssh().decode("ascii"))

to_ssh() is limited to Ed25519. NIST, SEC, and Brainpool keys use the other serializers.

Prepare one verifier for repeated use precompute-batch-verification

verifying_key.precompute()

for message, signature in signed_rows:
    verifying_key.verify(signature, message)

precompute() spends memory and startup work on one public key. Reserve it for repeated verification with that same key.

Alternatives

PackageRegistryPick it when
cryptographyPyPIUse it for production ECDSA, certificate work, and OpenSSL-backed key handling.
PyNaClPyPIUse it when libsodium's Ed25519 signatures and Curve25519 boxes cover the protocol.
coincurvePyPIUse it for secp256k1 software that specifically wants bindings to libsecp256k1.

More security guides

cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.