Skip to content
ecdsa.com
C · OpenSSL · Command line

OpenSSL

OpenSSL, the C library and command-line tool under most of the internet's TLS and much of its signing: DER signatures, random nonces with RFC 6979 on request, every common curve, and ML-KEM, ML-DSA and SLH-DSA since 3.5.
License
Apache-2.0
Checked version
3.5.9 ·
Source
github.com/openssl/openssl
Documentation
docs.openssl.org/3.5/

Compatibility at a glance

ECDSAEdDSAPost-quantum

ECDSA curves
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Post-quantum algorithms
ML-KEMML-DSASLH-DSA
Public key formats
spki-pemspki-derx509
Private key formats
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-der

OpenSSL provides libcrypto and libssl for C and, through bindings, for Node.js, Python, PHP and Ruby, plus the openssl command most people meet first. The facts here were checked on the Alpine 3.24.2 distribution package, OpenSSL 3.5.9 (the 3.5 long-term-support line); the newest upstream releases at the time were 3.6.5 and 4.0.3, so behaviour may differ in those.

ECDSA signatures are ASN.1 DER — 70 to 72 bytes on P-256 — from EVP_DigestSign and from openssl dgst -sign. Nonces are random unless you set the nonce-type parameter to 1 (OpenSSL 3.2+), which reproduced RFC 6979's P-256 test vector; on the command line that parameter is only reachable through pkeyutl, as the sample shows. s is not normalized and verification accepts high-S. EVP_PKEY_sign signs a digest you computed.

Malformed input is an error, not a false result: EVP_DigestVerify returned -1 for a raw 64-byte r‖s signature, and the CLI said so. openssl dgst -verify printed Verified OK for a good signature, Verification failure for a valid signature over other data, and Error verifying data for 64 bytes that are not DER — so in 3.5.9 a format problem can be told apart from a forgery by the wording. Convert raw signatures with the DER ⇄ raw converter, or let the OpenSSL command builder assemble the right invocation.

Curves include P-224 to P-521, secp256k1 and the Brainpool r1 curves, plus Ed25519 and Ed448. Since 3.5 the default provider also implements ML-KEM, ML-DSA and SLH-DSA, and the TLS hybrid group X25519MLKEM768. Keys come as PKCS#8 (plain or encrypted) or SEC1 private keys and SPKI or certificate public keys: genpkey writes -----BEGIN PRIVATE KEY-----, the legacy openssl ec writes -----BEGIN EC PRIVATE KEY----- — a file labelled EC is always a private key.

Most error messages people search for come from key loading — no start line, header too long, unsupported from the decoder — and are covered on the linked error pages.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/openssl/run.sh ran OpenSSL 3.5.9 in the container image alpine:3.24.2 on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • ECDSA signature encoding

    der

    EVP_DigestSign and openssl dgst -sign produce ASN.1 DER.

    Recorded by the check
    fact ecdsa-signature-encoding = der
  • ECDSA nonce

    rfc6979-opt-in

    Random by default; the nonce-type signature parameter set to 1 reproduced RFC 6979 appendix A.2.5.

    Recorded by the check
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: default signatures repeat: no; nonce-type 1 (deterministic) reproduces the RFC 6979 A.2.5 r: yes
  • Low-S when signing

    either

    s is not normalized: high-S values appeared among 64 signatures.

    Recorded by the check
    fact ecdsa-low-s-sign = either
  • High-S when verifying

    accepts-high-s

    A DER signature with s replaced by n − s verified.

    Recorded by the check
    fact ecdsa-low-s-verify = accepts-high-s
  • What the signing call hashes

    hashes-message-or-digest

    EVP_DigestSign hashes the data; EVP_PKEY_sign signs a precomputed digest, and that signature verified against the message.

    Recorded by the check
    fact ecdsa-prehash = hashes-message-or-digest
  • Malformed ECDSA signature

    returns-error

    EVP_DigestVerify returned -1 (an error, not 0) for a raw r‖s signature and for a 3-byte blob.

    Recorded by the check
    fact ecdsa-malformed-signature = returns-error
    evidence ecdsa-malformed-signature: EVP_DigestVerify returned -1 for raw r||s and -1 for 3 bytes (1 = valid, 0 = invalid, negative = error)
  • Verification output on failure

    distinguishable

    openssl dgst -verify printed Error verifying data for a non-DER signature and Verification failure for a valid signature over other data.

    Recorded by the check
    fact cli-verify-output = distinguishable
    evidence cli-verify-output: valid: Verified OK; 64 zero bytes: Error verifying data; valid signature over other data: Verification failure
  • ECDSA curves

    P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    P-224, P-256, P-384, P-521, secp256k1 and brainpoolP256r1/P384r1/P512r1 signed and verified.

    Recorded by the check
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 and Ed448.

    Recorded by the check
    fact eddsa = Ed25519, Ed448
  • Post-quantum algorithms

    ML-KEMML-DSASLH-DSA

    ML-KEM-768 encapsulated and decapsulated, and ML-DSA-65 and SLH-DSA-SHA2-128s signed and verified, in the default provider.

    Recorded by the check
    fact pqc = ML-KEM, ML-DSA, SLH-DSA
    evidence pqc: TLS groups with ML-KEM: MLKEM512 MLKEM768 MLKEM1024 SecP256r1MLKEM768 X25519MLKEM768 SecP384r1MLKEM1024 
  • Public key formats

    spki-pemspki-derx509

    SPKI PEM and DER and the public key of an X.509 certificate verified the same signature; an SPKI carrying a compressed point verified too.

    Recorded by the check
    fact public-key-formats = spki-pem, spki-der, x509
    evidence public-key-formats: SPKI carrying a compressed point (openssl ec -conv_form compressed) verifies: yes
  • Private key formats

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-der

    PKCS#8 PEM and DER, encrypted PKCS#8 and SEC1 PEM and DER all signed; genpkey writes PKCS#8, openssl ec writes SEC1.

    Recorded by the check
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der
    evidence private-key-formats: genpkey writes -----BEGIN PRIVATE KEY-----; openssl ec writes -----BEGIN EC PRIVATE KEY-----

Usage

Generate a P-256 key, sign and verify with dgst, sign deterministically through pkeyutl, and create an ML-DSA-65 signature.
# P-256 key as PKCS#8, public key as SPKI.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out key.pem
openssl pkey -in key.pem -pubout -out pub.pem

printf 'order #1042 approved' > msg.txt
openssl dgst -sha256 -sign key.pem -out sig.der msg.txt   # ASN.1 DER, 70-72 bytes
openssl dgst -sha256 -verify pub.pem -signature sig.der msg.txt

# Deterministic RFC 6979 signing (OpenSSL 3.2+) goes through pkeyutl.
openssl pkeyutl -sign -inkey key.pem -rawin -digest sha256 -pkeyopt nonce-type:1 -in msg.txt -out det1.der
openssl pkeyutl -sign -inkey key.pem -rawin -digest sha256 -pkeyopt nonce-type:1 -in msg.txt -out det2.der
cmp -s det1.der det2.der && echo "deterministic: identical signatures"

# Post-quantum: ML-DSA-65 key and signature.
openssl genpkey -algorithm ML-DSA-65 -out mldsa.pem
openssl pkeyutl -sign -inkey mldsa.pem -rawin -in msg.txt -out mldsa.sig
echo "ML-DSA-65 signature: $(wc -c < mldsa.sig) bytes"

The snippet harness runs every sample verbatim in the container image alpine:3.24.2, and the run fails unless the output matches.

Setup
apk add --no-cache -q openssl=3.5.9-r0 >/dev/null
Command
sh usage.sh
Output must match (regular expression)
Verified OK\ndeterministic: identical signatures\nML-DSA-65 signature: 3309 bytes

Libraries