bigywater.tech
Java · 259

HMAC 与数字签名:共享密钥和私钥的边界

共享密钥的 HMAC 与私钥签名都能防篡改,边界在哪里?谁能伪造?签名多大、确定不确定?验证失败时返回 false 还是抛异常?这篇用 JDK 逐项真实运行,并量出各自的耗时。

谁都能签
持有 HMAC 共享密钥的验证方能为任意消息算出合法标签
64 / 69–72 字节
Ed25519 / ECDSA P-256(DER)的签名大小
1.8 μs vs 428.3 μs
HMAC-SHA256 与 Ed25519 签名的单次耗时

HMAC:共享密钥

实测 · 本机执行输出
  HMAC-SHA256:标签 32 字节(2f72b69f9fb1…);同一个密钥同一条消息两次标签相同 = true(确定性)
  改 1 个 bit 的消息,标签相同 = false
  改 1 个 bit 的密钥,标签相同 = false
  持有同一个密钥的「验证方」给自己编造的消息 {amount: 99999} 算出的标签,用同一个密钥验证 → true(验证方与签发方无法区分:HMAC 不提供不可否认性)
  HmacSHA256  标签长度 32 字节
  HmacSHA512  标签长度 64 字节
  HmacSHA1    标签长度 20 字节
  空密钥初始化 HmacSHA256 → IllegalArgumentException: Empty key
  密钥长于分组长度(64 字节)时,HMAC 先把密钥哈希:用 200 字节密钥的标签 = 用其 SHA-256 摘要当密钥的标签 → true

数字签名:大小与确定性

实测 · 本机执行输出
  Ed25519                    公钥  44 字节;签名 64–64 字节;同一条消息签两次相同 = true ;验证 true,消息被改 1 bit 验证 false
  ECDSA P-256                公钥  91 字节;签名 69–72 字节;同一条消息签两次相同 = false;验证 true,消息被改 1 bit 验证 false
  ECDSA P-256(P1363 格式)      公钥  91 字节;签名 64–64 字节;同一条消息签两次相同 = false;验证 true,消息被改 1 bit 验证 false
  RSA-2048 PKCS#1 v1.5       公钥 294 字节;签名 256–256 字节;同一条消息签两次相同 = true ;验证 true,消息被改 1 bit 验证 false
  RSA-2048 PSS               公钥 294 字节;签名 256–256 字节;同一条消息签两次相同 = false;验证 true,消息被改 1 bit 验证 false

验证失败的形式

实测 · 本机执行输出
  签名被截短(63 字节)→ SignatureException: signature length invalid
  签名改 1 bit → verify = false(返回 false,不抛异常)
  换一个公钥 → verify = false
  ECDSA 签名被截短 8 字节 → SignatureException: Invalid encoding for signature
  ECDSA 签名(DER)被当作 P1363 格式去验证:SHA256withECDSAinP1363Format
    DER(70 字节)交给 P1363 验证器 → verify = false
  ECDSA 验证全零的 DER 签名 → verify = false
  没有 initSign/initVerify 直接 update → SignatureException: object not initialized for signature or verification
  验证模式下调用 sign() → SignatureException: object not initialized for signing

耗时对比

实测 · 本机执行输出
  HMAC-SHA256    对 29 字节消息:1.8 μs/次(签发与验证相同,一次计算)
  Ed25519        签名   428.3 μs/次;验证   420.7 μs/次(验证是签名的 0.98 倍时间)
  ECDSA P-256    签名   124.6 μs/次;验证   408.1 μs/次(验证是签名的 3.27 倍时间)
  RSA-2048       签名   934.0 μs/次;验证    34.1 μs/次(验证是签名的 0.04 倍时间)

分步演示

HMAC 与数字签名
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;所有密钥都是测试程序自己生成的
    • 没有验证:恒定时间比较(见下一篇)、ECDSA 的 nonce 重用后果、HMAC 的长度扩展攻击(HMAC 构造本身不受影响,没有演示)、JWT 等具体格式里的算法混淆、签名的批量验证、硬件密钥(HSM)里的签名;耗时是单机少量测量
    京ICP备2026067502号-1京公网安备11010502063487号