4. JWT 签名算法对比:HS256、RS256、ES256

签名算法是 JWT 安全性的核心。它决定了 Token 如何被签发、如何被验证,以及整个系统的信任模型。本章深入对比三种主流算法:HMAC 对称签名(HS256)、RSA 非对称签名(RS256)和ECDSA 椭圆曲线签名(ES256),帮助你在架构选型时做出正确决策。

4.1 算法分类与核心原理

JWT 签名算法分为对称和非对称两大类。对称签名使用同一个密钥进行签名和验签;非对称签名使用私钥签名、公钥验签。ECDSA 虽然也属于非对称签名,但其数学基础基于椭圆曲线,密钥更短、性能更高。

特性 HS256(HMAC-SHA256) RS256(RSA-SHA256) ES256(ECDSA-P256)
算法类型 对称签名 非对称签名 非对称签名(椭圆曲线)
密钥数量 1 个(共享密钥) 2 个(私钥 + 公钥) 2 个(私钥 + 公钥)
签名速度 快 慢 中等
验签速度 快 慢 快
密钥长度 任意(建议 ≥ 256 bit) 2048 / 4096 bit 256 bit
签名长度 32 字节 256 字节(2048 bit) 64 字节
安全性基础 哈希碰撞 + 密钥保密 大整数分解 椭圆曲线离散对数
典型场景 单体应用、内部微服务 开放 API、跨组织认证 移动端、IoT、高并发
选型提示:HS256 实现简单,但密钥泄露风险高;RS256 适合需要分发公钥的场景;ES256 在安全性和性能之间取得最佳平衡,是当前 JWT 签名的推荐选择。

4.2 HS256:对称签名

HS256 使用 HMAC-SHA256 算法,签名和验签使用同一个密钥。服务端必须确保密钥不被泄露,任何持有密钥的客户端都可以伪造 Token。

4.2.1 签名与验签流程

// 签名(服务端)
String token = Jwts.builder()
    .setSubject("user123")
    .signWith(SignatureAlgorithm.HS256, secretKey.getBytes())
    .compact();

// 验签(同一服务端或信任方)
Jws<Claims> claims = Jwts.parser()
    .setSigningKey(secretKey.getBytes())
    .parseClaimsJws(token);

4.2.2 安全风险

  • 密钥分发困难:每个需要验签的服务都必须持有相同的密钥,密钥在传输和存储过程中容易泄露。
  • 无法实现非对称信任:第三方服务无法独立验证 Token 来源,必须信任密钥持有方。
  • 密钥轮换复杂:更换密钥需要所有依赖方同步更新,容易导致服务中断。
安全警告:在生产环境中,切勿将 HS256 密钥硬编码在代码中或提交到版本控制系统。建议使用密钥管理服务(KMS)或环境变量注入。

4.3 RS256:RSA 非对称签名

RS256 基于 RSA 算法,使用私钥签名、公钥验签。私钥仅由签发服务持有,公钥可以安全地分发给任何需要验证 Token 的服务。

4.3.1 密钥对生成

// 生成 2048 位 RSA 密钥对
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair keyPair = generator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();

4.3.2 签名与验签

// 签名(使用私钥)
String token = Jwts.builder()
    .setSubject("user456")
    .signWith(SignatureAlgorithm.RS256, privateKey)
    .compact();

// 验签(使用公钥)
Jws<Claims> claims = Jwts.parser()
    .setSigningKey(publicKey)
    .parseClaimsJws(token);

4.3.3 性能考量

RS256 的签名操作比 HS256 慢 10-100 倍,验签操作也相对较慢。在 Token 签发频率高或验签请求量大的场景下,RSA 可能成为性能瓶颈。

4.4 ES256:ECDSA 椭圆曲线签名

ES256 使用 NIST P-256 椭圆曲线(secp256r1),提供与 RSA 2048 相当的安全性,但密钥长度更短、签名更小、计算速度更快。

4.4.1 密钥对生成

// 生成 EC P-256 密钥对
KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
generator.initialize(ecSpec);
KeyPair keyPair = generator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();

4.4.2 签名与验签

// 签名(使用私钥)
String token = Jwts.builder()
    .setSubject("user789")
    .signWith(SignatureAlgorithm.ES256, privateKey)
    .compact();

// 验签(使用公钥)
Jws<Claims> claims = Jwts.parser()
    .setSigningKey(publicKey)
    .parseClaimsJws(token);

4.4.3 优势总结

  • 签名长度短:仅 64 字节,适合 HTTP Header 传输。
  • 验签速度快:比 RS256 快 5-10 倍,适合高并发验签场景。
  • 密钥管理简单:256 位私钥易于安全存储,公钥体积小便于分发。

4.5 签名算法对比 SVG 图

下图从密钥结构、签名过程和验签过程三个维度对比三种算法的工作机制。

JWT 签名算法对比:HS256 vs RS256 vs ES256 HS256 对称签名 共享密钥(Secret) 签名(HMAC-SHA256) 验签(HMAC-SHA256) 同一密钥签名 & 验签 密钥必须保密 适合内部微服务 RS256 非对称签名 私钥(Private) 公钥(Public) 签名(RSA-SHA256) 验签(RSA-SHA256) 私钥签名,公钥验签 公钥可安全分发 适合开放 API ES256 椭圆曲线签名 私钥(256 bit) 公钥(256 bit) 签名(ECDSA-P256) 验签(ECDSA-P256) 密钥短,签名小(64B) 验签速度极快 适合移动端/IoT 选型建议 HS256 适合内部信任环境;RS256 适合需要公钥分发的开放系统;ES256 在安全性和性能上最优,推荐作为新项目首选

4.6 算法选型决策树

根据实际场景选择签名算法,可以参考以下决策流程:

  1. 是否所有服务都在同一个信任域内?
    • 是 → 考虑 HS256(简单快速,但需做好密钥管理)
    • 否 → 进入下一步
  2. 是否需要向第三方分发公钥?
    • 是 → 使用非对称签名(RS256 或 ES256)
    • 否 → 考虑 HS256 或内部非对称方案
  3. Token 签发和验签的并发量是否很高?
    • 是 → 优先选择 ES256(验签速度快,签名短)
    • 否 → RS256 也可接受,但建议评估性能
  4. 客户端是否受限(移动端、IoT)?
    • 是 → 必须选择 ES256(密钥短、签名小、省电)
    • 否 → RS256 或 ES256 均可

4.7 签名长度对网络传输的影响

JWT 通常放在 HTTP Header 中传输,签名长度直接影响请求大小。在微服务架构中,Token 可能被多次传递,累积的带宽开销不容忽视。

算法 签名长度 典型 Token 总大小 1000 次请求带宽
HS256 32 字节 ~200 字节 ~200 KB
RS256(2048 bit) 256 字节 ~450 字节 ~450 KB
ES256 64 字节 ~250 字节 ~250 KB
实践要点:在网关或 API 网关层面,建议统一使用 ES256 签名,既能保证安全性,又能减少网络开销。如果遗留系统使用 RS256,可以考虑在网关处做签名转换,但会增加延迟。

4.8 安全性对比总结

从安全架构角度,三种算法的安全性对比如下:

  • HS256:安全性完全依赖密钥保密。一旦密钥泄露,攻击者可以伪造任意 Token。适合内部网络隔离良好的场景。
  • RS256:私钥泄露风险可控(仅签发服务持有),公钥泄露不影响安全。但 RSA 算法本身存在量子计算威胁。
  • ES256:提供与 RSA 2048 同等级别的安全性,密钥更短,抗量子计算能力略强于 RSA。当前被认为是 JWT 签名的最佳实践。
安全提醒:无论选择哪种算法,都必须定期轮换密钥。建议设置密钥有效期(如 90 天),并使用自动化工具(如 Vault、AWS KMS)管理密钥生命周期。