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

GnuTLS

GnuTLS,GNU 项目的 TLS 与 X.509 库:在 NIST 曲线上生成 DER 编码的 ECDSA 签名,通过标志启用 RFC 6979,支持 Ed25519 和 Ed448——而在本次检查的发行版构建中,不支持 secp256k1,也没有可用的后量子算法。
许可证
LGPL-2.1-or-later
检查的版本
3.8.13 ·
源代码
gitlab.com/gnutls/gnutls
文档
www.gnutls.org/manual/

兼容性概览

ECDSAEdDSA

ECDSA 曲线
P-224P-256P-384P-521
EdDSA
Ed25519Ed448

GnuTLS 为许多 GNU 和 Linux 组件提供 TLS、X.509 和 PKCS 处理,并附带 certtool 和 gnutls-cli。它的抽象接口 gnutls_privkey_* 和 gnutls_pubkey_* 对软件密钥、PKCS#11 令牌和 TPM 的用法完全相同。本页的事实基于 Alpine 3.24.2 的软件包检查得出,即 GnuTLS 3.8.13,它用 Nettle 完成底层运算。从 GnuTLS 3.8.10 起,后量子算法只能由可选的 leancrypto 库提供,而这个软件包没有链接它。

gnutls_privkey_sign_data 使用随机 nonce,以 DER 形式返回 ECDSA 签名;标志 GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE 会切换到 RFC 6979,启用后精确复现了 RFC 中的 P-256 测试向量。s 没有经过规范化,high-S 签名也能验证通过。gnutls_privkey_sign_hash2 对你自行计算的摘要签名。

验证失败时返回负的错误码,而且各种失败看起来都一样:原始 r‖s 签名、3 字节的数据块,以及针对其他数据的有效签名,都返回 GNUTLS_E_PK_SIG_VERIFY_FAILED(-89)。可以用 DER ⇄ 原始格式转换器排除编码问题。

ECDSA 仅限于 P-224 到 P-521 的 NIST 曲线——gnutls_ecc_curve_get_id("SECP256K1") 返回 0,即未知——而 Ed25519 和 Ed448 的签名与验证均成功。头文件声明了 GNUTLS_PK_MLKEM768 和 GNUTLS_PK_MLDSA65,但这个构建两者都没有实现:它们不在 gnutls_pk_list() 中,生成 ML-DSA-65 密钥会报错 GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM,而 gnutls-cli --list 列出的 TLS 组包含 X25519 和 X448,却没有 ML-KEM 混合组。针对这些常量能编译通过的代码,在运行时仍可能失败,因此要检查运行时列表。可以用证书解码器查看 certtool 生成的证书。

事实及其依据的检查

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

  • ECDSA 签名编码

    der

    gnutls_privkey_sign_data 返回 ASN.1 DER。

    检查记录
    fact ecdsa-signature-encoding = der
  • ECDSA 的 nonce

    rfc6979-opt-in

    默认随机;GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE 生成了完全相同的签名,并复现了 RFC 6979 附录 A.2.5。

    检查记录
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: default repeats: no; GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE repeats: yes and reproduces the RFC 6979 A.2.5 r: yes
  • 签名时的 low-S

    either

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

    检查记录
    fact ecdsa-low-s-sign = either
  • 验证时的 high-S

    accepts-high-s

    把 s 替换为 n − s 的 DER 签名验证通过。

    检查记录
    fact ecdsa-low-s-verify = accepts-high-s
  • 签名调用对哪些内容做哈希

    hashes-message-or-digest

    gnutls_privkey_sign_data 会先计算哈希;gnutls_privkey_sign_hash2 对摘要签名,该签名针对原消息验证通过。

    检查记录
    fact ecdsa-prehash = hashes-message-or-digest
  • 格式错误的 ECDSA 签名

    returns-false

    原始 r‖s 签名、3 字节的数据块以及针对其他数据的有效签名,都返回 GNUTLS_E_PK_SIG_VERIFY_FAILED(-89)。

    检查记录
    fact ecdsa-malformed-signature = returns-false
    evidence ecdsa-malformed-signature: gnutls_pubkey_verify_data2: raw r||s -89 (GNUTLS_E_PK_SIG_VERIFY_FAILED), 3 bytes -89 (GNUTLS_E_PK_SIG_VERIFY_FAILED), wrong message -89 (GNUTLS_E_PK_SIG_VERIFY_FAILED)
  • ECDSA 曲线

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

    P-224、P-256、P-384 和 P-521;SECP256K1 不是已知曲线。

    检查记录
    fact ecdsa-curves = P-224, P-256, P-384, P-521
    evidence ecdsa-curves: gnutls_ecc_curve_get_id("SECP256K1") = 0 (0 = unknown)
  • EdDSA

    Ed25519Ed448

    Ed25519 和 Ed448 密钥的生成、签名与验证均成功。

    检查记录
    fact eddsa = Ed25519, Ed448
    evidence eddsa: Ed25519: keygen GNUTLS_E_SUCCESS, sign ok, verify ok
    evidence eddsa: Ed448: keygen GNUTLS_E_SUCCESS, sign ok, verify ok
  • 后量子算法

    none

    ML-DSA 和 ML-KEM 在头文件中有声明,但不在 gnutls_pk_list() 中;生成 ML-DSA-65 密钥失败,X25519-MLKEM768 是未知的组。

    检查记录
    fact pqc = none
    evidence pqc: the headers declare GNUTLS_PK_MLKEM768 = 14 and GNUTLS_PK_MLDSA65 = 16; gnutls_pk_list(): RSA, RSA-PSS, RSA-OAEP, RSA, DSA, GOST R 34.10-2012-512, GOST R 34.10-2012-256, GOST R 34.10-2001, EC/ECDSA, EdDSA (Ed25519), EdDSA (Ed448), DH, ECDH (X25519), ECDH (X448)
    evidence pqc: ML-DSA-65: in gnutls_pk_list: no; key generation GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM, sign and verify failed
    evidence pqc: ML-KEM-768: in gnutls_pk_list: no; gnutls_group_get_id("X25519-MLKEM768") = 0 (0 = unknown)
    evidence pqc: gnutls-cli --list Groups: GROUP-SECP192R1, GROUP-SECP224R1, GROUP-SECP256R1, GROUP-SECP384R1, GROUP-SECP521R1, GROUP-X25519, GROUP-GC256B, GROUP-GC512A, GROUP-X448, GROUP-FFDHE2048, GROUP-FFDHE3072, GROUP-FFDHE4096, GROUP-FFDHE6144, GROUP-FFDHE8192
    evidence pqc: shared libraries the gnutls package depends on: libbrotlidec.so.1 libbrotlienc.so.1 libc.musl-aarch64.so.1 libgmp.so.10 libhogweed.so.6 libidn2.so.0 libnettle.so.8 libp11-kit.so.0 libtasn1.so.6 libunistring.so.5 libz.so.1 libzstd.so.1 (GnuTLS 3.8.10 NEWS: PQC is only provided through leancrypto)

用法

生成 P-256 密钥,用可复现标志签名两次并验证,确认两个签名完全相同。
#include <gnutls/abstract.h>
#include <gnutls/gnutls.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    gnutls_global_init();
    gnutls_privkey_t priv;
    gnutls_pubkey_t pub;
    gnutls_privkey_init(&priv);
    gnutls_privkey_generate(priv, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0);
    gnutls_pubkey_init(&pub);
    gnutls_pubkey_import_privkey(pub, priv, 0, 0);

    gnutls_datum_t msg = {(unsigned char *)"order #1042 approved", 20}, sig1, sig2;
    /* REPRODUCIBLE switches ECDSA to RFC 6979 nonces; the default is random. */
    gnutls_privkey_sign_data(priv, GNUTLS_DIG_SHA256, GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE, &msg, &sig1);
    gnutls_privkey_sign_data(priv, GNUTLS_DIG_SHA256, GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE, &msg, &sig2);

    /* Negative return codes mean failure; any malformed input is GNUTLS_E_PK_SIG_VERIFY_FAILED. */
    int rc = gnutls_pubkey_verify_data2(pub, GNUTLS_SIGN_ECDSA_SHA256, 0, &msg, &sig1);
    printf("verified: %d, deterministic: %d\n", rc >= 0, sig1.size == sig2.size && memcmp(sig1.data, sig2.data, sig1.size) == 0);
    gnutls_global_deinit();
    return 0;
}

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

环境准备
apk add --no-cache -q build-base gnutls-dev=3.8.13-r0 >/dev/null
命令
cc -o usage usage.c -lgnutls && ./usage
输出必须匹配(正则表达式)
verified: 1, deterministic: 1

库