ecdsa.com

Convert DER ⇄ raw · Go

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

Go converts between DER and raw ECDSA signatures with two standard-library pieces: encoding/asn1 to parse or build the SEQUENCE of two INTEGERs, and big.Int's FillBytes to lay the values out at fixed width. No third-party packages needed.

Tested with Go 1.26 (standard library only).

Same recipe in:Node.jsPythonGoWebCrypto
convert.go — both directions
package main
 
import (
"encoding/asn1"
"fmt"
"math/big"
)
 
type dsaSignature struct {
R, S *big.Int
}
 
const size = 32 // 32 for P-256, 48 for P-384, 66 for P-521
 
func derToRaw(der []byte) ([]byte, error) {
var sig dsaSignature
rest, err := asn1.Unmarshal(der, &sig)
if err != nil {
return nil, err
}
if len(rest) != 0 {
return nil, fmt.Errorf("%d trailing bytes after signature", len(rest))
}
raw := make([]byte, 2*size)
sig.R.FillBytes(raw[:size]) // FillBytes left-pads with zeros
sig.S.FillBytes(raw[size:])
return raw, nil
}
 
func rawToDer(raw []byte) ([]byte, error) {
half := len(raw) / 2
return asn1.Marshal(dsaSignature{
R: new(big.Int).SetBytes(raw[:half]),
S: new(big.Int).SetBytes(raw[half:]),
})
}

How it works

  1. asn1.Unmarshal maps the DER SEQUENCE { INTEGER, INTEGER } straight onto a struct of two *big.Int fields — field order is what binds R and S.
  2. Check the rest return value: bytes after the SEQUENCE mean the input was not just a signature (or was concatenated with something).
  3. FillBytes writes the integer right-aligned into a zero-filled slice — the safe way to get fixed-width output.

Gotchas

  • Bytes() instead of FillBytes drops leading zeros and shifts the layout roughly 1 time in 256 — always use FillBytes for raw signature output.
  • size is per-curve. Hard-coding 32 while handling P-384 signatures truncates integers via FillBytes panicking — treat the panic as the safety net it is, not as a crash to suppress.
  • For verification you rarely need this file at all: ecdsa.VerifyASN1 takes DER directly, and ecdsa.Verify takes the big.Ints — convert only at true format boundaries (JWS, WebCrypto, hardware).

Related recipes