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