ecdsa.com

Generate a key pair · Python

How to generate an ECDSA key pair in Python

The cryptography package generates an ECDSA key pair in Python with one call — the real decisions are the curve class and the serialization options. This recipe produces the two PEM files the rest of the ecosystem expects: PKCS#8 private, SPKI public.

Tested with Python 3.14 and cryptography 50.0.

Same recipe in:Node.jsPythonGoOpenSSLWebCrypto
generate_key.py — P-256 pair, PEM output
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import (
Encoding,
NoEncryption,
PrivateFormat,
PublicFormat,
)
 
private_key = ec.generate_private_key(ec.SECP256R1()) # P-256
 
priv_pem = private_key.private_bytes(
Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()
)
pub_pem = private_key.public_key().public_bytes(
Encoding.PEM, PublicFormat.SubjectPublicKeyInfo
)
 
open("key.pem", "wb").write(priv_pem)
open("pub.pem", "wb").write(pub_pem)
print(pub_pem.decode(), end="") # -----BEGIN PUBLIC KEY-----
encrypted private key instead
from cryptography.hazmat.primitives.serialization import BestAvailableEncryption
 
priv_pem = private_key.private_bytes(
Encoding.PEM, PrivateFormat.PKCS8, BestAvailableEncryption(b"passphrase")
)

How it works

  1. ec.SECP256R1() is P-256 / prime256v1. The library names curves by their SEC designation — SECP384R1, SECP521R1, and SECP256K1 for the Bitcoin curve.
  2. PrivateFormat.PKCS8 yields BEGIN PRIVATE KEY; PrivateFormat.TraditionalOpenSSL yields the legacy BEGIN EC PRIVATE KEY.
  3. The public half always travels as PublicFormat.SubjectPublicKeyInfo — the BEGIN PUBLIC KEY container other languages load directly.
  4. For debugging, private_key.private_numbers().private_value exposes the raw scalar d as a Python int — never log it outside a throwaway test key.

Gotchas

  • SECP256**R**1 and SECP256**K**1 differ by one letter and are entirely different curves. Signatures made on one never verify on the other, and the error says nothing about curves.
  • NoEncryption() writes the private key in the clear — deliberate for CI fixtures, dangerous for anything durable. BestAvailableEncryption costs one line.
  • If another tool refuses your private key, check the header first: some older stacks want TraditionalOpenSSL (SEC1), others only PKCS#8. Re-serialize rather than re-generate.

Related recipes