3. JWT 签名算法:HS256 对称签名原理、RS256 非对称签名原理、算法选择策略
JWT 的签名算法是保证令牌完整性与可信性的核心。签名算法决定了 Token 在传输过程中是否被篡改,以及接收方能否验证签发方的身份。本章深入剖析最常用的两种算法:HS256(对称签名)与 RS256(非对称签名),并给出生产环境下的算法选择策略。
3.1 HS256 对称签名原理
HS256(HMAC with SHA-256)使用同一个密钥进行签名和验签。签发方和接收方必须事先共享该密钥,且密钥不得泄露。
签名与验签流程
- 准备输入:将 JWT 的 Header 和 Payload 分别进行 Base64URL 编码,用点号连接。
- 计算签名:使用 HMAC-SHA256 算法,以共享密钥对上述字符串计算摘要,得到签名。
- 组装 Token:将签名进行 Base64URL 编码,附加到前两部分之后。
- 验签:接收方使用相同的共享密钥,对 Header.Payload 重新计算 HMAC,比对结果是否一致。
核心特征:签名 = HMAC-SHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
安全提示:共享密钥必须具有足够的熵(至少 256 位随机数),且通过安全的带外渠道(如密钥管理服务、配置文件加密)分发。切勿将密钥硬编码在代码仓库中。
3.2 RS256 非对称签名原理
RS256(RSA Signature with SHA-256)使用一对密钥:私钥(Private Key)用于签名,公钥(Public Key)用于验签。私钥由签发方保管,公钥可以公开分发给所有接收方。
签名与验签流程
- 签发方:使用私钥对 Header.Payload 进行 RSA-SHA256 签名。
- 接收方:使用公钥验证签名,无需持有私钥。
- 密钥分发:公钥可通过 JWKS(JSON Web Key Set)端点、配置文件或证书方式发布。
核心特征:签名 = RSA-SHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), privateKey )
验签 = RSA-SHA256_verify( signature, publicKey )
验签 = RSA-SHA256_verify( signature, publicKey )
性能注意:RS256 签名速度比 HS256 慢约 10-100 倍,但验签速度相对较快。在需要频繁签发的场景(如高并发登录)需评估性能影响。
3.3 算法选择策略
选择 HS256 还是 RS256 取决于系统架构、安全要求和运维能力。下表从多个维度进行对比:
| 对比维度 | HS256(对称) | RS256(非对称) |
|---|---|---|
| 密钥管理 | 单一共享密钥,分发困难 | 私钥仅签发方持有,公钥可公开 |
| 安全性 | 密钥泄露则整个系统失效 | 私钥泄露风险低,公钥泄露无影响 |
| 签名性能 | 快(纯哈希运算) | 慢(RSA 模幂运算) |
| 验签性能 | 快(与签名相同) | 较快(公钥指数小) |
| 密钥轮换 | 需要同步更新所有服务 | 仅需更新公钥,支持 JWKS 动态轮换 |
| 适用场景 | 单体应用、内部微服务、信任域内 | 开放 API、第三方集成、跨信任域 |
生产环境推荐策略
- 内部微服务通信:优先使用 HS256,性能高且密钥管理简单(如通过 Kubernetes Secret 共享)。
- 开放 API / 第三方集成:必须使用 RS256,公钥可通过 JWKS 端点动态获取,避免密钥分发风险。
- 多服务 / 多团队:推荐 RS256 + JWKS,每个服务独立管理密钥,支持无感轮换。
- 移动端 / 前端:只能使用 RS256,因为客户端无法安全存储共享密钥。
最佳实践:不要使用 HS256 作为跨信任域的认证方案。一旦共享密钥泄露,攻击者可以伪造任意用户的 Token。RS256 配合 JWKS 是业界标准的微服务安全架构。
代码示例:HS256 与 RS256 签名对比(Node.js + jsonwebtoken)
const jwt = require('jsonwebtoken');
// ===== HS256 对称签名 =====
const secret = 'your-256-bit-secret';
const tokenHS = jwt.sign(
{ userId: 123, role: 'admin' },
secret,
{ algorithm: 'HS256', expiresIn: '1h' }
);
// 验签
const decodedHS = jwt.verify(tokenHS, secret);
console.log('HS256 验签通过:', decodedHS);
// ===== RS256 非对称签名 =====
const fs = require('fs');
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');
const tokenRS = jwt.sign(
{ userId: 456, role: 'user' },
privateKey,
{ algorithm: 'RS256', expiresIn: '2h' }
);
// 验签
const decodedRS = jwt.verify(tokenRS, publicKey);
console.log('RS256 验签通过:', decodedRS);
安全警告:生产环境中,HS256 的密钥长度至少 256 位;RS256 的私钥推荐使用 2048 位或更高。切勿使用弱密钥或过短的 RSA 密钥(如 1024 位)。