Skip to content
ecdsa.com

Convert DER ⇄ raw · Rust

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

To convert an ECDSA signature between DER and raw in Rust there is no byte juggling to write: RustCrypto's ecdsa::Signature type parses both encodings and serializes to both — from_der followed by to_bytes, or from_slice followed by to_der. The type also validates r and s on the way through, which a hand-written converter rarely does.

Tested with p256 (RustCrypto) 0.14.0 (Rust 1.99.1, ecdsa 0.17.0, hex 0.4.3).

Cargo.toml — p256 for the signature type, hex for the demo
[dependencies]
p256 = { version = "=0.14.0", features = ["ecdsa"] }
hex = "=0.4.3"
src/main.rs — DER ⇄ r‖s through p256::ecdsa::Signature
use p256::ecdsa::Signature;
/// DER (SEQUENCE of two INTEGERs) to fixed-width r‖s: 64 bytes on P-256.
fn der_to_raw(der: &[u8]) -> Result<[u8; 64], p256::ecdsa::Error> {
Ok(Signature::from_der(der)?.to_bytes().into())
}
/// Fixed-width r‖s to minimal ASN.1 DER (70–72 bytes on P-256).
fn raw_to_der(raw: &[u8]) -> Result<Vec<u8>, p256::ecdsa::Error> {
Ok(Signature::from_slice(raw)?.to_der().as_bytes().to_vec())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let der = hex::decode("30450221009f4118b059a34a2f344c39a1d085d93bab560402938281dc1cb2f3dfb8eee58602200bd54b34a72042551c3316e10f461a1d69036ba4f0d72d95d42465340b56def8")?;
let raw = der_to_raw(&der)?;
println!("raw: {}", hex::encode(raw));
println!("round trip: {}", raw_to_der(&raw)? == der);
Ok(())
}

How it works

  1. Signature::from_der parses strict DER, strips the sign bytes and left-pads r and s to 32 bytes; to_bytes() returns the fixed 64-byte r‖s array.
  2. Signature::from_slice takes exactly 64 bytes; to_der() emits minimal DER INTEGERs with a 0x00 sign byte only where the top bit is set.
  3. Both constructors reject values that are not valid scalars (zero, or not below the group order), so a converted signature is at least structurally a P-256 signature.
  4. The tests in the run convert two real signatures whose r or s is only 31 bytes long, and check that each format is refused where the other is expected.

Gotchas

  • The signature type is per curve: p256::ecdsa::Signature only handles 32-byte scalars. Use p384::ecdsa::Signature (96-byte raw) or p521::ecdsa::Signature (132-byte raw, long-form DER length) for the other curves — a P-384 DER fails to parse here instead of being truncated.
  • Conversion keeps s as it is. If the consumer demands low-S, call normalize_s() before to_der() or to_bytes().
  • Converting does not prove the signature verifies — only that it is well-formed. A failing verification after a correct conversion points at the key, the hash or the message bytes.
  • Often no conversion is needed at all: VerifyingKey::verify accepts a Signature built from either encoding, so parse with the matching constructor and verify directly.

Related recipes