Skip to content
Sign with an ECDSA Key

Sign with an ECDSA Key

Elliptic curve keys are smaller and faster than RSA keys of the same strength. NewAdobePKCS7Detached takes an *rsa.PrivateKey only, so an EC key goes through its sibling, sighandler.NewAdobePKCS7DetachedEcdsa. Everything after the handler is the ordinary signing path, and the result is the same adbe.pkcs7.detached signature with an ECDSA signer inside the PKCS#7 package.

Decoding the key

pfxData, err := os.ReadFile(pfxPath)
if err != nil {
    return err
}

priv, cert, _, err := pkcs12.DecodeChain(pfxData, password)
if err != nil {
    return err
}
ecdsaKey, ok := priv.(*ecdsa.PrivateKey)
if !ok {
    return fmt.Errorf("private key is %T; only ECDSA keys are supported", priv)
}

handler, err := sighandler.NewAdobePKCS7DetachedEcdsa(ecdsaKey, cert)
if err != nil {
    return err
}

The example decodes the container with software.sslmate.com/src/go-pkcs12, which reads the AES-encrypted .pfx files OpenSSL 3 writes by default and files that bundle CA certificates with the signing certificate. The CA certificates are discarded, and only the signing certificate goes into the PKCS#7 package. Given a container with an RSA key, the example exits with private key is *rsa.PrivateKey; only ECDSA keys are supported and points to the PKCS12 example.

Signing

From here it is the same as signing with RSA:

signature := model.NewPdfSignature(handler)
signature.SetName("adbe.pkcs7.detached ECDSA PDF")
signature.SetReason("Test ECDSA")
signature.SetDate(time.Now(), "")

if err := signature.Initialize(); err != nil {
    return err
}

opts := annotator.NewSignatureFieldOpts()
opts.FontSize = 10
opts.Rect = []float64{10, 25, 75, 60}

field, err := annotator.NewSignatureField(
    signature,
    []*annotator.SignatureLine{
        annotator.NewSignatureLine("Name", "John Doe"),
        annotator.NewSignatureLine("Date", "2025.07.28"),
        annotator.NewSignatureLine("Reason", "Test"),
    },
    opts,
)
if err != nil {
    return err
}
field.T = core.MakeString("Self signed PDF")

if err := appender.Sign(1, field); err != nil {
    return err
}
return appender.WriteToFile(outputPath)

Validation needs no special handler. A validation-only sighandler.NewAdobePKCS7Detached(nil, nil) checks ECDSA signatures too, since the algorithm comes from the signature itself.

PDF version

ECDSA in adbe.pkcs7.detached signatures is defined by PDF 2.0. UniPDF does not enforce that and does not raise the version: the appender keeps the input’s header, so signing a PDF 1.7 file produces a PDF 1.7 file with an ECDSA signature in it. UniPDF validates that file without complaint. A validator that checks conformance strictly may not, so when the signed output has to conform, start from a PDF 2.0 document.

Run the example

The example reads the key and certificate from a PKCS12 file and signs the first page with a visible signature box. To make a test key and file with OpenSSL:

openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout key.pem -out cert.pem -days 365 -subj "/CN=ECDSA Test Signer" \
  -addext "keyUsage=digitalSignature"
openssl pkcs12 -export -inkey key.pem -in cert.pem -out signer.pfx -passout pass:test
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures/signing
go run pdf_sign_pkcs7_ecdsa.go <FILE.PFX> <PASSWORD> <INPUT_PDF_PATH> <OUTPUT_PDF_PATH>

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source

A self-signed test certificate like this one verifies, but a trust check refuses it unless that certificate is itself one of your trust roots. See Validating Signer Trust and Revocation.

Last updated on