ecdsa.com

Convert DER ⇄ raw · Python

How to convert an ECDSA signature between DER and raw in Python

Python's cryptography package ships the two helpers that make DER ⇄ raw signature conversion trivial: decode_dss_signature extracts the r and s integers from DER, and encode_dss_signature rebuilds DER from them. The only thing you supply is the curve's coordinate size.

Tested with Python 3.14 and cryptography 50.0.

Same recipe in:Node.jsPythonGoWebCrypto
convert.py — both directions via the integers
from cryptography.hazmat.primitives.asymmetric.utils import (
decode_dss_signature,
encode_dss_signature,
)
 
SIZE = 32 # coordinate bytes: 32 for P-256, 48 for P-384, 66 for P-521
 
 
def der_to_raw(der: bytes) -> bytes:
r, s = decode_dss_signature(der)
return r.to_bytes(SIZE, "big") + s.to_bytes(SIZE, "big")
 
 
def raw_to_der(raw: bytes) -> bytes:
half = len(raw) // 2
return encode_dss_signature(
int.from_bytes(raw[:half], "big"),
int.from_bytes(raw[half:], "big"),
)
quick check on a real signature
der = open("sig.der", "rb").read()
raw = der_to_raw(der)
assert len(raw) == 2 * SIZE
assert raw_to_der(raw) == der # round-trips byte-for-byte
open("sig.raw", "wb").write(raw)

How it works

  1. decode_dss_signature returns plain Python ints — all DER framing, padding and length rules are handled for you.
  2. int.to_bytes(SIZE, "big") re-adds the fixed-width, big-endian layout that raw (IEEE P1363) format requires.
  3. The round trip is lossless: converting DER → raw → DER reproduces the input exactly, because DER is a canonical encoding.

Gotchas

  • The integers carry no curve information — you must know SIZE from context. If to_bytes raises OverflowError, your SIZE is too small for the curve that made the signature.
  • JWS/JWT ES256 signatures are exactly this raw form (64 bytes) — but base64url-encoded. Decode the text first; der_to_raw on base64 text fails on the first byte.
  • This conversion never fixes an invalid signature: if verification failed before converting, and the format was already right, the problem is the key, hash or message — not the encoding.

Related recipes