bigywater.tech
Java · 408

XDH:基于椭圆曲线的密钥交换

X25519 是现代 TLS 的默认密钥交换。这篇在 JDK 21 上用 RFC 向量、字节序、clamp、小阶点和 OpenSSL 互通几个角度验证它的行为,最后与 X448 和 ECDH P-256 比较协商成本。

逐字节相同
Java 的 X25519 公钥与共享密钥和 RFC 7748 §6.1 测试向量的比较结果
5 位
256 位私钥里被 clamp 后不起作用的位数(低 3 位 + 最高位 + 次高位)
97 vs 552 µs
X25519 与 ECDH P-256 的单次密钥协商耗时

RFC 7748 向量

实测 · 本机执行输出
  RFC 7748 §6.1:Alice 的公钥(私钥 × 基点 u=9)与 RFC 相同 = true,Bob 的公钥与 RFC 相同 = true
  Alice 与 Bob 算出的共享密钥相同 = true,与 RFC 的 4a5d9d5b… 相同 = true,长度 32 字节
  随机生成的三把密钥:k1×k2 与 k2×k1 相同 = true;k1×k3 与 k1×k2 不同 = true
  generateSecret() 之后没有重新 init 就再次调用:IllegalStateException
  doPhase(…, false):IllegalStateException:Only two party agreement supported, lastPhase must be true

编码与字节序

实测 · 本机执行输出
  X25519 公钥的 X.509 编码 44 字节,前 12 字节(算法标识)302a300506032b656e032100;最后 32 字节是裸的 u 坐标(小端)
  XECPublicKey.getU() = 1760262bf2144ee1…(BigInteger,大端);把 getU() 转成 32 字节小端后与编码里的最后 32 字节相同 = true
  私钥 PKCS#8 编码 48 字节,XECPrivateKey.getScalar() 是 32 字节;公钥由私钥推出的 u(k × 9)与 getU() 相同 = true
  X448:公钥编码 68 字节,私钥编码 72 字节,共享密钥 56 字节
  对照 ECDH P-256:公钥编码 91 字节,共享密钥 32 字节
  KeyPairGenerator("XDH") + NamedParameterSpec.X25519 可用

clamp

实测 · 本机执行输出
  私钥第 0 字节的低 3 位(会被 clamp 清零)翻转后,公钥是否不变 = true
  私钥第 31 字节的最高位(会被 clamp 清零)翻转后,公钥是否不变 = true
  私钥第 31 字节的次高位(会被 clamp 置 1)翻转后,公钥是否不变 = true
  私钥第 0 字节的第 4 位(不被 clamp 影响)翻转后,公钥是否不变 = false
  1000 对随机私钥(每对只在低 3 位上不同):公钥相同的有 1000 对;1000 把随机私钥的公钥互不相同 = true

小阶点与非法输入

实测 · 本机执行输出
  u = 0(阶 1 的点) → InvalidKeyException:Point has small order
  u = 1(阶 4 的点) → InvalidKeyException:Point has small order
  u = 325606250916…(阶 8 的点) → InvalidKeyException:Point has small order
  u = 393823572354…(阶 8 的点) → InvalidKeyException:Point has small order
  u = p - 1(2^255 - 20,阶 2 的点) → InvalidKeyException:Point has small order
  u = 9(基点) → 共享密钥 8520f0098930a754…
  u = p + 9(与基点 9 同余,超出域):共享密钥与 u = 9 相同 = true
  u = -1:成功
  u = 2^256:成功构造公钥
  X25519 私钥 + X448 公钥:InvalidKeyException:Public key parameters are not compatible with private key.

OpenSSL 与性能

实测 · 本机执行输出
  openssl pkeyutl -derive(B 的私钥 × A 的公钥):退出码 0,输出 32 字节;与 Java(A 的私钥 × B 的公钥)算出的共享密钥相同 = True
  X25519         生成密钥对 99 µs/次,密钥协商(init + doPhase + generateSecret)97 µs/次(约 10314 次/秒)
  X448           生成密钥对 414 µs/次,密钥协商(init + doPhase + generateSecret)412 µs/次(约 2430 次/秒)
  ECDH P-256     生成密钥对 95 µs/次,密钥协商(init + doPhase + generateSecret)552 µs/次(约 1810 次/秒)

分步演示

XDH
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(SunEC);OpenSSL 4.0.3;性能是自写的 nanoTime 循环、单线程、5 轮中位数,不是 JMH
    • 没有验证:协商后的密钥派生(HKDF,JDK 21 没有 KDF API)、JDK 17 / 27 的差异、DH 2048 与 FFDHE 的性能对比(这次没有测)、TLS 握手里 X25519 的实际使用、X448 的互通、侧信道常数时间性质
    京ICP备2026067502号-1京公网安备11010502063487号