3. JWT 签名算法选型:HMAC、RSA、ECDSA 与强度对比

JWT 的核心安全边界由签名算法决定。选错算法或配置不当,会导致令牌伪造、密钥泄露或性能瓶颈。本节从算法原理、适用场景、安全强度三个维度,对比 HMAC、RSA、ECDSA 三大类签名算法,并给出选型决策依据。

3.1 算法分类与核心差异

JWT 标准(RFC 7519)定义的签名算法分为两类:对称签名(HMAC)和非对称签名(RSA / ECDSA)。对称签名使用同一个密钥进行签名和验签;非对称签名使用私钥签名、公钥验签。下表列出主流算法的关键参数:

算法族 JWT 头 alg 值 密钥类型 签名长度 计算速度 典型场景
HMAC-SHA256 HS256 对称(共享密钥) 32 字节 极快 单体服务、内部微服务间信任
HMAC-SHA384 HS384 对称(共享密钥) 48 字节 快 对安全要求更高的内部系统
HMAC-SHA512 HS512 对称(共享密钥) 64 字节 中等 极高安全内部链路
RSA-SHA256 RS256 非对称(RSA 2048+) 256 字节 慢(签名) 跨服务、跨组织鉴权
RSA-SHA384 RS384 非对称(RSA 2048+) 384 字节 慢 金融、政务等高安全场景
RSA-SHA512 RS512 非对称(RSA 2048+) 512 字节 慢 合规要求极高的场景
ECDSA-P256 (SHA256) ES256 非对称(ECC P-256) 64 字节 快 移动端、IoT、高并发微服务
ECDSA-P384 (SHA384) ES384 非对称(ECC P-384) 96 字节 中等 高安全、低带宽场景
ECDSA-P521 (SHA512) ES512 非对称(ECC P-521) 132 字节 中等 顶级安全合规场景
选型第一原则:只要涉及跨信任域(不同团队、不同组织、公网环境),必须使用非对称签名(RS256 / ES256)。对称签名 HMAC 仅限同一信任域内的内部通信。

3.2 HMAC 对称签名:简单但边界脆弱

HMAC(Hash-based Message Authentication Code)结合哈希函数与共享密钥,计算速度快、实现简单。但对称密钥必须通过安全通道分发,一旦泄露,攻击者可伪造任意令牌。

  • 优点:计算开销极低,适合高吞吐内部调用;密钥长度灵活(推荐 ≥ 256 位)。
  • 缺点:密钥分发困难;无法实现非对称的“私钥签名、公钥验签”模型;验签方也能签名,无法做抗抵赖。
  • 典型陷阱:将 HMAC 密钥硬编码在代码仓库中;使用弱密钥(如短于 256 位)。
// 正确生成 HMAC 密钥(Node.js crypto 示例)
const crypto = require('crypto');
const secret = crypto.randomBytes(32).toString('hex');
// 输出:a3f2b8c1...(64 个十六进制字符,256 位)
console.log(secret);

3.3 RSA 非对称签名:成熟但沉重

RSA 是目前最广泛使用的非对称签名算法。私钥签名、公钥验签的模型天然适合分布式系统:认证服务持有私钥,下游服务只需公钥即可验签,无需共享敏感密钥。

  • 优点:公钥可公开分发;支持数字证书(X.509)体系;生态成熟,几乎所有语言都有标准库支持。
  • 缺点:签名体积大(RS256 签名 256 字节);签名速度慢(尤其私钥操作);密钥对生成耗时。
  • 推荐密钥长度:至少 2048 位;生产环境建议 4096 位。
安全警告:禁止使用 RS256 配合 1024 位以下 RSA 密钥。2024 年后,1024 位 RSA 已被证明可在合理成本内破解。务必使用 2048 位或更高。

3.4 ECDSA 椭圆曲线签名:现代高性能选择

ECDSA(Elliptic Curve Digital Signature Algorithm)基于椭圆曲线密码学,在相同安全强度下,密钥更短、签名更小、计算更快。P-256(secp256r1)曲线提供与 RSA 2048 等价的安全强度,但签名长度仅 64 字节。

  • 优点:签名体积小(ES256 仅 64 字节);签名和验签速度均优于 RSA;带宽和存储开销低。
  • 缺点:曲线选择复杂(P-256 / P-384 / P-521 / Curve25519 等);部分老旧库支持不完整;随机数生成质量要求高。
  • 推荐曲线:通用场景使用 P-256(ES256);高安全场景使用 P-384(ES384)。
// 生成 ES256 密钥对(Node.js crypto 示例)
const { generateKeyPairSync } = require('crypto');
const { publicKey, privateKey } = generateKeyPairSync('ec', {
  namedCurve: 'P-256',  // 等价于 secp256r1
  publicKeyEncoding: { type: 'spki', format: 'pem' },
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
console.log(privateKey);  // 私钥 PEM 格式
console.log(publicKey);   // 公钥 PEM 格式

3.5 算法强度对比表

下表从安全强度、性能、体积三个维度,对主流算法进行横向对比。安全强度以 NIST 推荐的“比特安全强度”为基准。

算法 安全强度(比特) 等效 RSA 密钥长度 签名体积(字节) 签名速度(相对) 验签速度(相对)
HS256 128 RSA 3072 32 极快 极快
HS384 192 RSA 7680 48 快 快
HS512 256 RSA 15360 64 中等 中等
RS256 (2048) 112 RSA 2048 256 慢 中等
RS256 (4096) 128 RSA 4096 512 很慢 慢
ES256 (P-256) 128 RSA 3072 64 快 快
ES384 (P-384) 192 RSA 7680 96 中等 中等
ES512 (P-521) 256 RSA 15360 132 中等 中等
关键结论:ES256(P-256)在安全强度 128 比特下,签名体积仅为 RS256 的 1/4,签名速度快 5-10 倍。对于新系统,优先选择 ES256 而非 RS256。

3.6 签名算法选型决策流程

下图展示从业务需求出发的签名算法选型决策树:

JWT 签名算法选型决策树 是否跨信任域? 内部:HMAC 可行 跨域:必须非对称 HS256(推荐) HS384 / HS512(高安全) 性能优先? 是 → ES256 否 → RS256 决策说明: • 内部系统(同一团队/同一集群):HMAC 足够,推荐 HS256,密钥长度 ≥ 256 位。 • 跨服务/跨组织:必须非对称签名,推荐 ES256(P-256),签名小、速度快。 • 合规要求(如金融、政务):使用 RS256(4096 位)或 ES384(P-384)。 • 移动端/IoT:优先 ES256,减少带宽和电量消耗。

3.7 常见选型陷阱与规避

  • 陷阱一:在跨域场景使用 HMAC。后果:密钥泄露后所有下游服务均可伪造令牌。规避:跨域必须使用非对称签名。
  • 陷阱二:使用 RS256 但密钥长度不足 2048 位。后果:安全强度低于 112 比特,存在被破解风险。规避:强制要求 RSA 密钥 ≥ 2048 位。
  • 陷阱三:ECDSA 随机数重用。后果:私钥可被直接计算出来。规避:使用安全的随机数生成器,或使用 EdDSA(Ed25519)等确定性签名算法。
  • 陷阱四:算法混淆攻击(alg:none)。攻击者将 JWT 头中的 alg 改为 "none" 绕过签名验证。规避:服务端必须白名单校验 alg 字段,拒绝 "none" 和未知算法。
生产强制规则:JWT 验签时,必须显式指定允许的算法列表,禁止从 JWT 头部动态读取 alg 并直接使用。示例:jwt.verify(token, publicKey, { algorithms: ['ES256'] })。

3.8 算法迁移路径

如果现有系统使用 RS256,希望迁移到 ES256 以获得更优性能,建议采用双算法过渡策略:

  1. 签发端:同时支持 RS256 和 ES256 两种签名算法,新令牌优先使用 ES256。
  2. 验签端:配置两个公钥(RSA 公钥 + ECC 公钥),根据 JWT 头中的 alg 字段选择对应公钥验签。
  3. 灰度期:监控 ES256 令牌的验签成功率,确认无误后逐步下线 RS256 签发。
  4. 清理:所有令牌过期后,移除 RSA 密钥对和相关代码。

这种平滑迁移方式可避免服务中断,同时逐步降低签名体积和验证延迟。