JWT 的”签名算法”看似只是 header 里一个 alg 字段,但选错会直接影响安全性、性能、密钥管理复杂度。HS256、RS256、ES256、PS256——每个都对应着不同的密钥体系、性能曲线和威胁模型。这篇按”决策树”的方式讲清各算法的取舍,让你能在 5 分钟内为新项目锁定算法。
一张算法对照表
| 算法 | 类型 | 密钥体系 | 签名长度 | 速度 | 典型场景 |
|---|---|---|---|---|---|
| HS256 | HMAC-SHA256 | 对称(共享 secret) | 32 字节 | 极快 | 单体应用、Webhook |
| HS384 | HMAC-SHA384 | 对称 | 48 字节 | 极快 | 同上,需更高安全 |
| HS512 | HMAC-SHA512 | 对称 | 64 字节 | 快 | 同上,最高强度 |
| RS256 | RSA-PKCS1-SHA256 | 非对称 RSA | 256 字节 | 中 | 微服务、OAuth |
| RS384/RS512 | 同上 | 非对称 RSA | 384/512 字节 | 中 | 高合规场景 |
| ES256 | ECDSA-P256-SHA256 | 非对称 EC | 64 字节 | 快 | 现代 OAuth/OIDC |
| ES384 | ECDSA-P384-SHA384 | 非对称 EC | 96 字节 | 中快 | 政府/军工 |
| PS256 | RSA-PSS-SHA256 | 非对称 RSA | 256 字节 | 中 | FIPS 合规 |
| EdDSA | Ed25519 | 非对称 EC | 64 字节 | 最快 | 最新规范 |
光看这张表难判断——下面按场景拆解。
单方签发自验:HS256
特征:发 token 和验 token 都是同一个服务(或同一组对等服务),不需要把验证权下放给第三方。
典型场景:
- 单体 Web 应用:Express/Django/Rails 直接发 token、验 token
- Webhook 验签:GitHub、Stripe、Shopify 给你一个 secret,发 webhook 时签名,你用同一 secret 验
- 内部 API:网关签发,业务服务用 secret 验
- IoT 设备 token:设备和服务器预共享 key
为什么是 HS256:
- 密钥就一个 secret,无需 PKI 基建
- 签发和验证都是 HMAC,单核每秒数十万次,几乎零成本
- 部署只是把 secret 塞进环境变量
注意事项:
- secret 至少 32 字节,从
crypto.randomBytes(32)来 - 任何持有 secret 的服务都能伪造任意 token——所以 secret 不要广撒
- 密钥泄露 = 全员被盗,必须立即轮换
微服务、跨团队:RS256 / ES256
特征:签发服务和验证服务不是同一个,需要”签发权与验证权分离”。
典型场景:
- 鉴权中心签发 token,订单服务、支付服务、物流服务各自验证
- B2B 平台:你签发 token,第三方业务方持你的公钥验证
- 单点登录 SSO:IdP(Identity Provider)签,多个 SP(Service Provider)验
为什么不能用 HS256:HS256 的 secret 给到验证服务后,那个服务就也能签——任何下游都能伪造 token,安全模型崩塌。
RS256 vs ES256:
| 维度 | RS256 | ES256 |
|---|---|---|
| 公钥大小 | ≈270 字节 | ≈64 字节 |
| 私钥大小 | ≈1700 字节 | ≈120 字节 |
| 签名长度 | 256 字节 | 64 字节 |
| token 总长 | 长 | 短 30-40% |
| 签名性能 | 慢 | 中等 |
| 验证性能 | 快 | 中等 |
| 算法成熟度 | 极高 | 高 |
| 移动端友好 | 一般 | 好 |
实务:
- 新项目优先 ES256:token 短、JWKS 体积小、移动端流量友好
- 已有 RS256 不必紧急切:RSA-2048 仍然安全,切换成本不低
- OAuth/OIDC 标准化场景:双方都接受时优先 ES256
高合规场景:PS256
特征:受 FIPS 140-3、PCI DSS 4.0、政府/军工等监管约束。
为什么是 PS256:PS256 用 RSA-PSS,是 RSA 签名的现代化版本,引入随机 salt,有可证明安全的密码学证明。RS256 用的 PKCS#1 v1.5 在工程上没有已知漏洞,但密码学界认为 PSS”更现代”。
实际取舍:
- 没有合规要求时,PS256 性能略低于 RS256,没有显著优势
- 库支持比 RS256 略弱——部分老库(早期 Java、PHP 库)不支持
- 切换成本和 RS256 差不多,但收益主要是”满足审计”而非真实安全提升
结论:除非合规审计要求,否则跳过 PS256,直接用 RS256 或 ES256。
最新规范:EdDSA(Ed25519)
特征:RFC 8037 定义的 JWT 新算法,基于 Ed25519 椭圆曲线。
优势:
- 签名速度比 ECDSA 快 2-3 倍
- 公钥/签名都只 32/64 字节
- 不需要 RFC 6979 那样的确定性 nonce 处理——曲线设计本身确定性
- 抗侧信道攻击设计
劣势:
- JWT 库支持不齐——node-jsonwebtoken 直到 v9 才完整支持
- 部分网关、CDN(Cloudflare、AWS API Gateway)不识别
- 监管合规未必接受
实务:技术选型激进的团队可以用 EdDSA;保守团队用 ES256 即可,差距不大。
密钥长度指引
| 算法 | 推荐密钥长度 | 最低 |
|---|---|---|
| HS256 | 32 字节(256 位) | 32 字节 |
| HS384 | 48 字节 | 48 字节 |
| HS512 | 64 字节 | 64 字节 |
| RS256/PS256 | RSA-2048 | RSA-2048 |
| RS384/PS384 | RSA-3072 | RSA-2048 |
| ES256 | EC P-256 | EC P-256 |
| ES384 | EC P-384 | EC P-384 |
| EdDSA | Ed25519 | Ed25519 |
HS 密钥用 openssl rand -base64 32 或 crypto.randomBytes(32).toString('base64') 生成,不要自己拍字符串。
RSA 密钥:
openssl genrsa -out priv.pem 2048
openssl rsa -in priv.pem -pubout -out pub.pem
EC 密钥(P-256):
openssl ecparam -genkey -name prime256v1 -noout -out priv.pem
openssl ec -in priv.pem -pubout -out pub.pem
时效字段:iat / exp / nbf 怎么设
签发时至少带这三个字段:
{
"iat": 1714200000,
"exp": 1714201800,
"nbf": 1714200000,
"sub": "user_12345",
"role": "user"
}
| 字段 | 含义 | 推荐值 |
|---|---|---|
iat | 签发时间(Unix 秒) | 当前时间 |
exp | 过期时间 | iat + 15 分钟(access)/ + 7 天(refresh) |
nbf | 生效时间 | 等于 iat 或省略 |
jti | token 唯一 ID | 用于黑名单/吊销 |
iss | 签发方 | 你的服务标识 |
aud | 预期接收方 | 验证端要校验 |
sub | 主体(通常 user_id) | 必填 |
短 access + 长 refresh:access 15 分钟到期,refresh 7-30 天,业务服务只验 access,refresh 只在 /refresh 端点用。这样 access 被盗的时间窗口短,refresh 可以在服务端立即吊销。
验签时的硬规则:算法白名单
无论用什么算法,验签代码必须写死白名单:
// ❌ 错:信 header 里的 alg
jwt.verify(token, key);
// ✅ 对:白名单
jwt.verify(token, key, { algorithms: ['RS256'] });
不写死会被 alg confusion 攻击:攻击者把 RS256 token 的 header 改成 alg: HS256,用你公开的 RSA 公钥当 HMAC secret 重签——服务端按 HS256 验证,公钥正好等于 secret,验证通过。
更狠的 alg: none 攻击:攻击者把 alg 改成 "none"、签名段留空、payload 随便改——老库直接放行。
白名单是 JWT 验签的第一防线,比选哪个算法更重要。
选型决策树
1. 签发方和验证方是同一个吗?
├─ 是 → HS256(密钥 ≥ 32 字节)
└─ 否 → 继续
2. 有 FIPS / 政府合规要求吗?
├─ 是 → PS256 / ES384
└─ 否 → 继续
3. 移动端 / 流量敏感?
├─ 是 → ES256
└─ 否 → RS256(成熟)/ ES256(更现代)
绝大多数场景,HS256 + RS256 + ES256 三个就够覆盖。再往外的算法属于”特定合规需求”或”前沿尝鲜”,按需引入即可。