跳到主要内容
ecdsa.com
命令行

GnuPG (gpg)

GnuPG 2.4,gpg 背后的 OpenPGP 实现:默认生成三年后过期的 Ed25519 密钥,在 NIST、Brainpool 和 secp256k1 曲线上采用确定性的 RFC 6979 ECDSA,Ed25519 签名的对象是摘要,并且尚不支持后量子算法。
许可证
GPL-3.0-or-later
检查的版本
2.4.9 ·
源代码
dev.gnupg.org/source/gnupg/
文档
www.gnupg.org/documentation/manuals/gnupg/

兼容性概览

ECDSAEdDSA

ECDSA 曲线
P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
公钥格式
opensshopenpgp

GnuPG 实现了 OpenPGP,用于文件和电子邮件的签名与加密、许多 Linux 发行版中的软件包签名,以及 Git 提交签名。本页的事实基于 Alpine 3.24.2 的软件包检查得出,即 GnuPG 2.4.9 搭配 libgcrypt 1.12.2。只给出用户 ID 时,gpg --quick-gen-key 会创建一个用于签名和认证的 Ed25519 主密钥,外加一个 Curve25519(cv25519)加密子密钥,两者都是版本 4 的密钥;除非指定过期时间,主密钥会在 1095 天——即三年——后过期。可以用 OpenPGP 查看器查看密钥和签名。

ECDSA 密钥可以在 P-256、P-384、P-521、secp256k1 以及三条 Brainpool r1 曲线上创建;nistp224 会报错“Unknown elliptic curve”。签名包把 r 和 s 作为两个 OpenPGP MPI 携带,既不是 DER,也不是定宽的 r‖s。P-256 签名默认使用 SHA-256;对 P-384 密钥指定 SHA-256 会报错“signing failed: Invalid length”,而对 P-256 密钥指定 SHA-512 则可以接受。

nonce 是确定性的:冻结时钟(--faked-system-time)后,对同一文件的两次签名逐字节相同,r 也与一个独立的 RFC 6979 实现一致,因此对同一数据签名两次不会以危险的方式重用 nonce。s 没有经过规范化,把 s 替换为 n − s 的签名也被判定为有效——凡是要求签名唯一的场合,这一点都需要注意。

Ed25519 签名使用旧版 EdDSA 格式(算法 22):R 和 S 是 MPI,而 Ed25519 签名的对象是对文件和签名中参与哈希的字段计算的 SHA-512 摘要,而不是文件本身。把 R 和 S 各自左侧填充到 32 字节后,R‖S 用 OpenSSL 针对该摘要验证通过,针对文件则验证失败——用通用的 Ed25519 库验证 gpg 签名,必须先计算 OpenPGP 摘要。Ed448 密钥以版本 5 的密钥包创建,这是 GnuPG 所遵循的 LibrePGP 草案的格式,而本次检查的默认密钥、ECDSA 密钥和 Ed25519 密钥都是版本 4;选择 Ed448 之前,应确认对方能读取版本 5。此版本没有后量子算法:公钥算法列表为 RSA、ELG、DSA、ECDH、ECDSA 和 EDDSA,ky768_cv25519 会作为未知曲线被拒绝。

在脚本中,应读取机器可读的 --status-fd 输出,而不是面向人的文本:有效签名给出 GOODSIG 和 VALIDSIG,退出状态为 0;针对其他数据的签名给出 BADSIG,退出状态为 1;截断的包给出 NODATA,退出状态为 2。gpg --export-ssh-key 加上指纹和 ! 时,会为 ECDSA 和 Ed25519 密钥写出一行 OpenSSH 公钥,而 --export 和 --export-secret-keys 写出的是 OpenPGP 包。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/gnupg/run.sh 于2026年10月20日在容器镜像 alpine:3.24.2 中运行了 GnuPG (gpg) 2.4.9;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • 默认密钥类型

    ed25519cv25519

    只给出用户 ID 时,--quick-gen-key 创建了一个 ed25519 主密钥(用途 sc)和一个 cv25519 子密钥(用途 e)。

    检查记录
    fact default-key-type = ed25519, cv25519
    evidence default-key-type: pub ed25519 (algorithm 22, usage scESC); sub cv25519 (algorithm 18, usage e)
  • 默认密钥过期时间

    1095d

    主密钥在创建 1095 天后过期。

    检查记录
    fact default-key-expiry = 1095d
    evidence default-key-expiry: expiration minus creation of the primary key: 1095 days
  • OpenPGP 密钥版本

    v4

    默认密钥导出为版本 4 的密钥包。

    检查记录
    fact openpgp-key-version = v4
  • Ed448 密钥版本

    v5

    ed448 密钥导出为版本 5 的密钥包;默认密钥、ECDSA 密钥和 ed25519 密钥则为版本 4。

    检查记录
    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 曲线

    P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    全部七条曲线生成的密钥均签名并验证成功;nistp224 返回“Unknown elliptic curve”。

    检查记录
    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 和 ed448 密钥的签名与验证均成功。

    检查记录
    fact eddsa = Ed25519, Ed448
  • 后量子算法

    none

    gpg --version 的 Pubkey: 行没有列出任何后量子算法,生成 ky768_cv25519 密钥也失败了。

    检查记录
    fact pqc = none
    evidence pqc: --quick-gen-key with ky768_cv25519: gpg: Key generation failed: Unknown elliptic curve
  • ECDSA 签名编码

    openpgp-mpi

    P-256 签名包(算法 19)以 r 和 s 两个 MPI 结尾。

    检查记录
    fact ecdsa-signature-encoding = openpgp-mpi
    evidence ecdsa-signature-encoding: P-256 detached signature: version=4 pubkey-algo=19 hash-algo=8 mpis=2
  • 默认哈希

    sha256

    默认的 P-256 签名标明的哈希算法为 8,即 SHA-256。

    检查记录
    fact ecdsa-default-hash = sha256
  • ECDSA 的 nonce

    rfc6979

    冻结时钟后的两个签名完全相同,r 也与 RFC 6979 参考签名实现一致。

    检查记录
    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

    either

    s 没有经过规范化:32 个签名中出现了 high-S 值。

    检查记录
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 32: yes
  • 验证时的 high-S

    accepts-high-s

    把 s 替换为 n − s 的签名验证通过,给出 GOODSIG,退出状态为 0。

    检查记录
    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
  • 哈希与曲线绑定

    enforced

    对 P-384 密钥使用 --digest-algo SHA256 时报错“Invalid length”;对 P-256 密钥使用 SHA-512 则签名成功。

    检查记录
    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
  • 验证失败时的输出

    distinguishable

    消息不符时给出 BADSIG,退出状态为 1;包被截断时给出 NODATA,退出状态为 2。

    检查记录
    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
  • Ed25519 签名覆盖的内容

    digest

    用 openssl pkeyutl 针对 OpenPGP 签名摘要验证 R‖S 成功,针对文件则失败。

    检查记录
    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
  • 公钥格式

    opensshopenpgp

    gpg --export 写出 OpenPGP 包;--export-ssh-key 写出了 ecdsa-sha2-nistp256 行和 ssh-ed25519 行。

    检查记录
    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
  • 私钥格式

    openpgp

    gpg --export-secret-keys 写出 OpenPGP 私钥包。

    检查记录
    fact private-key-formats = openpgp
    evidence private-key-formats: gpg --export-secret-keys: secret key packet

用法

创建默认密钥,生成 ASCII 装甲格式的分离签名,并通过状态输出检查签名。
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

代码片段测试工具会在容器镜像 alpine:3.24.2 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
apk add --no-cache -q gnupg=2.4.9-r1 >/dev/null
命令
sh sign.sh
输出必须匹配(正则表达式)
GOODSIG\nVALIDSIG

库