Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Tiny ECDH in Python

⚠️ Educational only — do not use in production

This package exists to demonstrate how Elliptic-Curve Diffie–Hellman is put together. It is not production cryptography and must never be shipped as such:

  • ~80-bit curve strength. The curve is sect163r2 (NIST B-163), far below the 112-bit floor any current guidance requires.
  • Deprecated curve. NIST SP 800-186 keeps the binary-field curves in the specification but marks them deprecated for new use; sect163r2 is still specified in SEC 2 v2.0 (§3.2.3).
  • Timing side channels. The pure-Python field arithmetic cannot be made timing-uniform; secret-dependent timing is present and documented, not fixed.
  • No zeroisation. Secret material is never wiped from memory.
  • No independent review. No audit, no formal analysis.

For real key agreement use an audited library: cryptography (cryptography.hazmat.primitives.asymmetric.x25519) or PyNaCl.

What this is

A Python port of kokke/tiny-ECDH-c, a small implementation of the Elliptic-Curve Diffie–Hellman key agreement algorithm. The code follows the C original closely rather than being idiomatic Python, and it is much slower than the C version.

Elliptic-curve Diffie–Hellman (ECDH) is an anonymous key agreement protocol that allows two parties, each having an elliptic-curve public–private key pair, to establish a shared secret over an insecure channel.

The curve

The implemented curve is sect163r2, also published as NIST B-163: a pseudo-random (not Koblitz) curve over the binary field GF(2^163). Its parameters are taken from SEC 2: Recommended Elliptic Curve Domain Parameters, Version 1.0 (§3.4.3), consistent with FIPS 186-4 Appendix D. They are pinned by tests/test_curve_parameters.py. SEC 2 v2.0 (§3.2.3) still specifies the same curve.

The curve offers roughly 80 bits of security, below the 112-bit minimum of current guidance. NIST SP 800-186 still specifies the binary-field curves but marks them deprecated for new use.

Attribution and licence

This is a derivative work of kokke/tiny-ECDH-c, which is released into the public domain under the Unlicense. Public-domain dedication imposes no conditions, so redistributing this port under the MIT LICENSE is compatible. Curve parameters are from SEC 2 / FIPS 186-4 as cited above.

Suggested reading order

The package is meant to be read. A useful path through it:

  1. tiny_ecdh/utils.py — the curve definition and GF(2^m) field and point arithmetic (gf2field_*, gf2point_*), including the branch-free point operations.
  2. tiny_ecdh/keys.py — the PrivateKey, PublicKey and SharedSecret types and the validation a peer key must pass.
  3. tiny_ecdh/entropy.py — how the private scalar is drawn from OS entropy.
  4. tiny_ecdh/ecdh.py — the two entry points, ecdh_generate_keys and ecdh_shared_secret, tied together from the pieces above.
  5. tiny_ecdh/kdf.py — turning the shared point's x-coordinate into a usable key.
  6. examples/ecdh_example.py — a full exchange end to end.
  7. tests/ — known-answer vectors and property tests, sourced independently of this implementation.

Usage

Install with pip install tiny-ecdh-python and import from tiny_ecdh. Releases are pre-1.0 and pre-release: the interface and the shared-secret format are not stable and may change without a deprecation period. See CHANGELOG.md for what changed in each version and RELEASING.md for how releases are built and published.

from tiny_ecdh import ecdh_generate_keys, ecdh_shared_secret

The point that comes out of the exchange is not a key: its bits carry the algebraic structure of the curve equation, not uniform randomness. Always derive a key from it, as below.

  1. Alice and Bob each generate a key pair. The private scalar is drawn from the operating system's entropy source; the result is typed PrivateKey / PublicKey.
alice_priv, alice_pub = ecdh_generate_keys()
bob_priv, bob_pub = ecdh_generate_keys()
  1. They exchange public keys over the insecure channel.

  2. Each side computes the shared point and derives a key from it, bound to a context string that names what the key is for:

from tiny_ecdh import constant_time_compare

context = b"my-app: session key v1"

# raises InvalidPublicKeyError if the peer key fails validation
alice_key = ecdh_shared_secret(alice_priv, bob_pub).derive_key(context)
bob_key = ecdh_shared_secret(bob_priv, alice_pub).derive_key(context)
  1. Both sides now hold the same fixed-length derived key. Compare derived keys in constant time, never with ==:
assert constant_time_compare(alice_key, bob_key)

A different context on the same key pair yields a completely different key, so one pair can serve independent purposes without them sharing key material. The shared point's x-coordinate is available as SharedSecret.raw_x for comparison against the C original; it is deliberately named so that reading it instead of calling derive_key is a visible decision, not the normal path.

Deriving the key does not authenticate who you share it with: ECDH here is unauthenticated, so a peer key could belong to an impersonator. Verify the peer's public key and the handshake transcript through some other channel (a signature, a certificate, an out-of-band fingerprint) before trusting a derived key.

Timing limitations

Scalar multiplication always runs the same fixed number of double-and-add-always iterations, and each iteration performs the same sequence of point operations regardless of the scalar: point doubling and addition use branch-free variants that always compute every case and select the result arithmetically. So the implementation no longer leaks the scalar's bit length through the iteration count, or the position of coincidences between the running total and the added point through which case is taken. This is a fixed schedule of point operations, not constant-time execution, and the package must not be described that way — several point operations are still data-dependent underneath:

  • Field inversion (gf2field_inv) is variable-time extended-Euclid; its running time depends on the element being inverted, and it runs three times per iteration.
  • Field multiplication (gf2field_mul) loops once per bit of one operand and branches on each bit, so both its iteration count and branch choice depend on an operand derived from the secret scalar.
  • Python's arbitrary-precision integers do not run in fixed time for a fixed bit width. This cannot be closed from pure Python.

See SECURITY.md for the full threat model and the list of known, accepted weaknesses.

Development checks

Run the complete local gate with:

python -m pip install tox
python -m tox

This runs the test suite on Python 3.9–3.12 and, in the quality environment, checks linting, formatting, the source and wheel builds, and artifact metadata.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tiny_ecdh_python-0.1.0a1.tar.gz (28.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tiny_ecdh_python-0.1.0a1-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

Details for the file tiny_ecdh_python-0.1.0a1.tar.gz.

File metadata

  • Download URL: tiny_ecdh_python-0.1.0a1.tar.gz
  • Upload date:
  • Size: 28.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tiny_ecdh_python-0.1.0a1.tar.gz
Algorithm Hash digest
SHA256 d4218f5893b62b680996177e4cdb1cbdbdc4072cb652fa3ffde0ada086e21a5e
MD5 e924eebf631394b794e0056134bdf9f4
BLAKE2b-256 a290e0da09bc9c40f8cad1ffb8bc73ffd9357f0d500d1c5389816295f6325a3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for tiny_ecdh_python-0.1.0a1.tar.gz:

Publisher: release.yml on danielsousaoliveira/tiny-ECDH-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tiny_ecdh_python-0.1.0a1-py3-none-any.whl.

File metadata

File hashes

Hashes for tiny_ecdh_python-0.1.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 060ada952fb0a640c10610078158194d3782849e58577f805f4098216be3145e
MD5 4bcaa9ca15bccdfa7a70efb4159fc478
BLAKE2b-256 6fc025d84f73eecc790e831ead09d2aee760f9c8b6084bed3123207792160710

See more details on using hashes here.

Provenance

The following attestation bundles were made for tiny_ecdh_python-0.1.0a1-py3-none-any.whl:

Publisher: release.yml on danielsousaoliveira/tiny-ECDH-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0a1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page