bigywater.tech
Java · 258

KeyAgreement:两端如何协商出共享密钥

两端怎么在公开信道上协商出共享密钥?拿到的共享秘密能直接当密钥吗?对方给的公钥要检查什么?没有认证会怎样?这篇用 JDK 的 KeyAgreement 逐项真实运行,并实现 HKDF 验证标准向量。

120 vs 590 μs
X25519 / P-256 计算一次共享秘密的耗时
Point has small order
X25519 拒绝小阶点公钥时的异常信息
RFC 5869
自己实现的 HKDF 通过的标准测试向量

基本用法、大小与速度

实测 · 本机执行输出
  X25519    公钥(X.509 编码) 44 字节,私钥(PKCS#8) 48 字节;共享秘密 32 字节;双方算出的相同 = true;与第三方 C 算出的相同 = false
  EC P-256  公钥(X.509 编码) 91 字节,私钥(PKCS#8) 67 字节;共享秘密 32 字节;双方算出的相同 = true;与第三方 C 算出的相同 = false
  EC P-384  公钥(X.509 编码)120 字节,私钥(PKCS#8) 80 字节;共享秘密 48 字节;双方算出的相同 = true;与第三方 C 算出的相同 = false
  共享秘密(前 8 字节)0891a1c2d495f94c…,看起来是随机的,但它是 DH 的原始输出(椭圆曲线上的点坐标),不是可以直接拿来当 AES 密钥的均匀随机字节串;应该先经过 KDF
  同一个 KeyAgreement 对象再次 generateSecret() → IllegalStateException: Not initialized correctly
  X25519           计算共享秘密 120 μs/次;生成密钥对 130 μs/次
  EC P-256         计算共享秘密 590 μs/次;生成密钥对 117 μs/次
  EC P-384         计算共享秘密 1299 μs/次;生成密钥对 681 μs/次
  DH 2048(有限域)     计算共享秘密 354 μs/次;生成密钥对 347 μs/次

对方公钥的校验

实测 · 本机执行输出
  点坐标改了 1 个 bit(不在曲线上):KeyFactory.generatePublic 成功,但 ECDH → InvalidKeyException: Point is not on curve
  无穷远点作为公钥 → IllegalArgumentException: w is ECPoint.POINT_INFINITY
  坐标 (1, 2) 不在曲线上 → InvalidKeyException: Point is not on curve
  P-256 私钥配 P-384 公钥(曲线不匹配)→ InvalidKeyException: Invalid coordinate
  X25519 的 u = 0(小阶点)作为对方公钥 → ECDH 抛 InvalidKeyException: Point has small order
  X25519 的 u = 1 → InvalidKeyException: Point has small order

用 HKDF 派生密钥

实测 · 本机执行输出
  RFC 5869 附录 A.1 的测试向量:自己用 HmacSHA256 实现的 HKDF 输出 3cb25f25faacd57a90434f64… 与标准值一致 = true
  同一个共享秘密、不同的 info:encrypt 密钥 53265870decc…,mac 密钥 ff8797afab84…,两个密钥相同 = false;双方各自派生的 encrypt 密钥相同 = true
  换一个 salt 派生出的 encrypt 密钥相同 = false
  一次派生 64 字节:前 32 字节 95e638cf…,后 32 字节 8d1418c5…(HKDF-Expand 的输出前缀与短输出一致:true)
  用 Alice 派生的密钥加密、Bob 派生的密钥解密:hello

无认证 DH 与中间人

实测 · 本机执行输出
  无认证的 DH,中间人 Mallory 把双方的公钥都换成自己的:Alice 与 Mallory 的秘密相同 = true;Bob 与 Mallory 的秘密相同 = true;Alice 与 Bob 的秘密相同 = false(两边都以为在和对方通信,其实各自与 Mallory 建立了密钥)
  Bob 用长期签名密钥(Ed25519)签名自己的 DH 公钥,Alice 事先知道 Bob 的验证密钥:验证真实公钥 → true
  Mallory 把 Bob 的 DH 公钥换成自己的,沿用 Bob 的签名 → 验证 false(中间人被发现;前提是 Alice 事先持有可信的 Bob 验证密钥)
  握手记录(transcript)绑定进密钥派生:KDF 的 info 里放入双方公钥,即使对方被替换,两边派生的密钥也不一致:true

分步演示

密钥协商
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;所有密钥都是测试程序自己生成的
    • 没有验证:密钥协商的前向保密在完整协议里的意义(见 TLS 篇目)、MLKEM 等后量子密钥封装(JDK 21 里没有)、JDK 24 引入的 KDF API、更多的无效公钥种类(如非规范编码)、侧信道、ECDH 在 PKCS#11 或硬件里的行为;中间人演示是本进程内的模拟,只用于说明认证的必要性
    京ICP备2026067502号-1京公网安备11010502063487号