Skip to content
ecdsa.com
Command line

GnuPG (gpg)

GnuPG 2.4, the OpenPGP implementation behind gpg: Ed25519 keys that expire after three years by default, deterministic RFC 6979 ECDSA on NIST, Brainpool and secp256k1 curves, Ed25519 signatures over a digest, and no post-quantum algorithms yet.
License
GPL-3.0-or-later
Checked version
2.4.9 ·
Source
dev.gnupg.org/source/gnupg/
Documentation
www.gnupg.org/documentation/manuals/gnupg/

Compatibility at a glance

ECDSAEdDSA

ECDSA curves
P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Public key formats
opensshopenpgp

GnuPG implements OpenPGP for file and email signing and encryption, package signing in many Linux distributions, and git commit signing. The facts here were checked on the Alpine 3.24.2 package, GnuPG 2.4.9 with libgcrypt 1.12.2. gpg --quick-gen-key with only a user ID creates an Ed25519 primary key for signing and certification plus a Curve25519 (cv25519) encryption subkey, both as version 4 keys, and the primary key expires after 1095 days — three years — unless an expiry is given. Inspect keys and signatures with the OpenPGP inspector.

ECDSA keys can be created on P-256, P-384, P-521, secp256k1 and the three Brainpool r1 curves; nistp224 fails with "Unknown elliptic curve". The signature packet carries r and s as two OpenPGP MPIs, neither DER nor fixed-width r‖s. P-256 signatures use SHA-256 by default; asking for SHA-256 on a P-384 key fails with "signing failed: Invalid length", while SHA-512 on a P-256 key is accepted.

Nonces are deterministic: with the clock frozen (--faked-system-time), two signatures of one file were byte-identical, and r matched an independent RFC 6979 implementation, so signing the same data twice does not reuse a nonce in a dangerous way. s is not normalized, and a signature with s replaced by n − s verified as good — relevant wherever signatures are expected to be unique.

Ed25519 signatures use the legacy EdDSA format (algorithm 22): R and S are MPIs, and what Ed25519 signs is the SHA-512 digest of the file and the signature's hashed fields, not the file itself. Left-padded to 32 bytes each, R‖S verified with OpenSSL against that digest and failed against the file — verifying gpg signatures with a general-purpose Ed25519 library requires computing the OpenPGP digest first. Ed448 keys are created as version 5 key packets, the format of the LibrePGP draft that GnuPG follows, while the default, ECDSA and Ed25519 keys checked were all version 4; make sure the other side reads version 5 before choosing Ed448. This release has no post-quantum algorithm: the public-key list is RSA, ELG, DSA, ECDH, ECDSA and EDDSA, and ky768_cv25519 is rejected as an unknown curve.

For scripts, read the machine-readable --status-fd output rather than the text: a valid signature gave GOODSIG and VALIDSIG with exit status 0, a signature over other data BADSIG with exit status 1, and a truncated packet NODATA with exit status 2. gpg --export-ssh-key with a fingerprint and ! writes an OpenSSH public key line for ECDSA and Ed25519 keys, while --export and --export-secret-keys write OpenPGP packets.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/gnupg/run.sh ran GnuPG (gpg) 2.4.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.

  • Default key type

    ed25519cv25519

    --quick-gen-key with only a user ID created an ed25519 primary key (usage sc) and a cv25519 subkey (usage e).

    Recorded by the check
    fact default-key-type = ed25519, cv25519
    evidence default-key-type: pub ed25519 (algorithm 22, usage scESC); sub cv25519 (algorithm 18, usage e)
  • Default key expiry

    1095d

    The primary key's expiration was 1095 days after its creation.

    Recorded by the check
    fact default-key-expiry = 1095d
    evidence default-key-expiry: expiration minus creation of the primary key: 1095 days
  • OpenPGP key version

    v4

    The default key was exported as a version 4 key packet.

    Recorded by the check
    fact openpgp-key-version = v4
  • Ed448 key version

    v5

    An ed448 key was exported as a version 5 key packet; the default key and the ECDSA and ed25519 keys as version 4.

    Recorded by the check
    fact ed448-key-version = v5
    evidence ed448-key-version: key packet versions: default v4, nistp256 v4, nistp384 v4, nistp521 v4, secp256k1 v4, brainpoolP256r1 v4, brainpoolP384r1 v4, brainpoolP512r1 v4, ed25519 v4, ed448 v5
  • ECDSA curves

    P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    All seven curves generated keys that signed and verified; nistp224 returned "Unknown elliptic curve".

    Recorded by the check
    fact ecdsa-curves = P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
    evidence ecdsa-curves: nistp224: gpg: Key generation failed: Unknown elliptic curve
  • EdDSA

    Ed25519Ed448

    ed25519 and ed448 keys signed and verified.

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

    none

    The Pubkey: line of gpg --version lists no post-quantum algorithm, and ky768_cv25519 key generation failed.

    Recorded by the check
    fact pqc = none
    evidence pqc: --quick-gen-key with ky768_cv25519: gpg: Key generation failed: Unknown elliptic curve
  • ECDSA signature encoding

    openpgp-mpi

    The P-256 signature packet (algorithm 19) ends in two MPIs, r and s.

    Recorded by the check
    fact ecdsa-signature-encoding = openpgp-mpi
    evidence ecdsa-signature-encoding: P-256 detached signature: version=4 pubkey-algo=19 hash-algo=8 mpis=2
  • Default hash

    sha256

    The default P-256 signature names hash algorithm 8, SHA-256.

    Recorded by the check
    fact ecdsa-default-hash = sha256
  • ECDSA nonce

    rfc6979

    Two signatures with a frozen clock were identical, and r matched the RFC 6979 reference signer.

    Recorded by the check
    fact ecdsa-nonce = rfc6979
    evidence ecdsa-nonce: two signatures of one file with a frozen clock are identical; r equals the RFC 6979 reference signer's r: yes
  • Low-S when signing

    either

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

    Recorded by the check
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 32: yes
  • High-S when verifying

    accepts-high-s

    A signature with s replaced by n − s verified with GOODSIG and exit status 0.

    Recorded by the check
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: s replaced by n - s: exit 0, status NEWSIG KEY_CONSIDERED SIG_ID GOODSIG VALIDSIG TRUST_ULTIMATE
  • Hash bound to the curve

    enforced

    --digest-algo SHA256 with a P-384 key failed with "Invalid length"; SHA-512 with a P-256 key signed.

    Recorded by the check
    fact ecdsa-hash-curve-binding = enforced
    evidence ecdsa-hash-curve-binding: --digest-algo SHA256 with a P-384 key: gpg: signing failed: Invalid length; --digest-algo SHA512 with a P-256 key: signed
  • Verification output on failure

    distinguishable

    BADSIG and exit status 1 for a wrong message; NODATA and exit status 2 for a truncated packet.

    Recorded by the check
    fact cli-verify-output = distinguishable
    evidence cli-verify-output: valid: exit 0, status NEWSIG KEY_CONSIDERED SIG_ID GOODSIG VALIDSIG TRUST_ULTIMATE
    evidence cli-verify-output: wrong message: exit 1, status NEWSIG KEY_CONSIDERED BADSIG FAILURE
    evidence cli-verify-output: truncated signature packet: exit 2, status NODATA FAILURE
  • What the Ed25519 signature covers

    digest

    R‖S verified with openssl pkeyutl over the OpenPGP signature digest and failed over the file.

    Recorded by the check
    fact ed25519-signed-input = digest
    evidence ed25519-signed-input: version=4 pubkey-algo=22 hash-algo=10 mpis=2; R || S checked by openssl pkeyutl over the signature digest: Signature Verified Successfully; over the file: Signature Verification Failure
  • Public key formats

    opensshopenpgp

    gpg --export writes OpenPGP packets; --export-ssh-key wrote ecdsa-sha2-nistp256 and ssh-ed25519 lines.

    Recorded by the check
    fact public-key-formats = openssh, openpgp
    evidence public-key-formats: gpg --export: public key packet; gpg --export-ssh-key: P-256 key ecdsa-sha2-nistp256, Ed25519 key ssh-ed25519
  • Private key formats

    openpgp

    gpg --export-secret-keys writes OpenPGP secret key packets.

    Recorded by the check
    fact private-key-formats = openpgp
    evidence private-key-formats: gpg --export-secret-keys: secret key packet

Usage

Create a default key, make a detached ASCII-armored signature and check it through the status output.
export GNUPGHOME="$(mktemp -d)"
# With only a user ID: an Ed25519 signing key plus a Curve25519 encryption subkey, expiring in 3 years.
gpg --batch --pinentry-mode loopback --passphrase '' --quick-gen-key 'Alice <alice@example.com>'

printf 'order #1042 approved\n' > order.txt
gpg --batch --yes --armor --detach-sign order.txt   # writes order.txt.asc

# Scripts should read --status-fd: GOODSIG and VALIDSIG for a valid signature, BADSIG otherwise.
gpg --batch --status-fd 1 --verify order.txt.asc order.txt 2>/dev/null | grep -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)' | cut -d' ' -f2

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 gnupg=2.4.9-r1 >/dev/null
Command
sh sign.sh
Output must match (regular expression)
GOODSIG\nVALIDSIG

Libraries