JWT 签名算法 #
算法概述 #
JWT 支持多种签名算法,主要分为三大类:
text
┌─────────────────────────────────────────────────────────────┐
│ JWT 签名算法分类 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 对称算法(HMAC) │ │
│ │ - HS256, HS384, HS512 │ │
│ │ - 使用相同密钥签名和验证 │ │
│ │ - 速度快,适合单服务 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 非对称算法(RSA) │ │
│ │ - RS256, RS384, RS512, PS256, PS384, PS512 │ │
│ │ - 私钥签名,公钥验证 │ │
│ │ - 适合多服务,公钥可公开 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 椭圆曲线算法(ECDSA) │ │
│ │ - ES256, ES384, ES512 │ │
│ │ - 私钥签名,公钥验证 │ │
│ │ - 更短密钥,同等安全 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
对称算法(HMAC) #
什么是 HMAC? #
HMAC(Hash-based Message Authentication Code)是一种基于哈希的消息认证码,使用相同的密钥进行签名和验证。
text
┌─────────────────────────────────────────────────────────────┐
│ HMAC 工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 签名过程: │
│ │
│ 消息 + 密钥 ────> HMAC 函数 ────> 签名 │
│ │
│ 验证过程: │
│ │
│ 消息 + 密钥 ────> HMAC 函数 ────> 签名' │
│ │ │
│ ▼ │
│ 签名' === 签名 ? 通过 : 失败 │
│ │
└─────────────────────────────────────────────────────────────┘
HMAC 算法列表 #
| 算法 | 哈希函数 | 输出长度 | 签名长度 |
|---|---|---|---|
| HS256 | SHA-256 | 256 位 | 32 字节 |
| HS384 | SHA-384 | 384 位 | 48 字节 |
| HS512 | SHA-512 | 512 位 | 64 字节 |
HS256 示例 #
javascript
const jwt = require('jsonwebtoken');
const payload = {
sub: 'user-123',
name: 'John Doe'
};
const secret = 'your-256-bit-secret';
const token = jwt.sign(payload, secret, { algorithm: 'HS256' });
console.log(token);
验证 JWT #
javascript
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
console.log(decoded);
密钥要求 #
text
┌─────────────────────────────────────────────────────────────┐
│ HMAC 密钥要求 │
├─────────────────────────────────────────────────────────────┤
│ │
│ HS256: │
│ - 最小长度:32 字节(256 位) │
│ - 推荐长度:32 字节或更长 │
│ │
│ HS384: │
│ - 最小长度:48 字节(384 位) │
│ - 推荐长度:48 字节或更长 │
│ │
│ HS512: │
│ - 最小长度:64 字节(512 位) │
│ - 推荐长度:64 字节或更长 │
│ │
│ 生成安全密钥: │
│ const crypto = require('crypto'); │
│ const secret = crypto.randomBytes(32).toString('hex'); │
│ │
└─────────────────────────────────────────────────────────────┘
HMAC 优缺点 #
text
┌─────────────────────────────────────────────────────────────┐
│ HMAC 优缺点分析 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 优点: │
│ ✅ 计算速度快 │
│ ✅ 实现简单 │
│ ✅ 签名长度短 │
│ ✅ 适合单服务场景 │
│ │
│ 缺点: │
│ ❌ 密钥必须安全分发 │
│ ❌ 所有验证方都需要密钥 │
│ ❌ 密钥泄露影响所有令牌 │
│ ❌ 不适合多方验证场景 │
│ │
│ 适用场景: │
│ - 单一授权服务器 │
│ - 内部服务间通信 │
│ - 简单应用架构 │
│ │
└─────────────────────────────────────────────────────────────┘
非对称算法(RSA) #
什么是 RSA? #
RSA 是一种非对称加密算法,使用一对密钥:私钥用于签名,公钥用于验证。
text
┌─────────────────────────────────────────────────────────────┐
│ RSA 工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 密钥对: │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 私钥 │ │ 公钥 │ │
│ │ (保密) │ │ (公开) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ │ 签名 │ 验证 │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 授权服务器 │ │ 资源服务器 │ │
│ │ (签发 Token) │ │ (验证 Token) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 优势: │
│ - 公钥可以公开分发 │
│ - 私钥只在授权服务器 │
│ - 多个资源服务器可独立验证 │
│ │
└─────────────────────────────────────────────────────────────┘
RSA 算法列表 #
| 算法 | 描述 | 签名长度 |
|---|---|---|
| RS256 | RSASSA-PKCS1-v1_5 SHA-256 | 256 字节 |
| RS384 | RSASSA-PKCS1-v1_5 SHA-384 | 256 字节 |
| RS512 | RSASSA-PKCS1-v1_5 SHA-512 | 256 字节 |
| PS256 | RSASSA-PSS SHA-256 | 256 字节 |
| PS384 | RSASSA-PSS SHA-384 | 256 字节 |
| PS512 | RSASSA-PSS SHA-512 | 256 字节 |
RS256 vs PS256 #
text
┌─────────────────────────────────────────────────────────────┐
│ RS256 vs PS256 │
├─────────────────────────────────────────────────────────────┤
│ │
│ RS256(RSASSA-PKCS1-v1_5): │
│ - 传统 RSA 签名方案 │
│ - 广泛支持 │
│ - 存在潜在安全问题 │
│ │
│ PS256(RSASSA-PSS): │
│ - 更安全的签名方案 │
│ - 添加随机盐值 │
│ - 推荐使用 │
│ │
│ 选择建议: │
│ - 新项目推荐使用 PS256 │
│ - 兼容性要求高用 RS256 │
│ │
└─────────────────────────────────────────────────────────────┘
生成 RSA 密钥对 #
bash
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
RS256 签名示例 #
javascript
const jwt = require('jsonwebtoken');
const fs = require('fs');
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');
const payload = {
sub: 'user-123',
name: 'John Doe'
};
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
console.log(token);
RS256 验证示例 #
javascript
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
console.log(decoded);
RSA 优缺点 #
text
┌─────────────────────────────────────────────────────────────┐
│ RSA 优缺点分析 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 优点: │
│ ✅ 公钥可公开分发 │
│ ✅ 私钥安全在授权服务器 │
│ ✅ 多个资源服务器可独立验证 │
│ ✅ 适合分布式系统 │
│ │
│ 缺点: │
│ ❌ 计算速度较慢 │
│ ❌ 签名长度较长(256字节) │
│ ❌ 密钥尺寸大 │
│ │
│ 适用场景: │
│ - 多个资源服务器 │
│ - 公开的 API │
│ - OpenID Connect │
│ - 微服务架构 │
│ │
└─────────────────────────────────────────────────────────────┘
椭圆曲线算法(ECDSA) #
什么是 ECDSA? #
ECDSA(Elliptic Curve Digital Signature Algorithm)是一种基于椭圆曲线的数字签名算法,提供与 RSA 相同的安全级别,但使用更短的密钥。
text
┌─────────────────────────────────────────────────────────────┐
│ ECDSA 工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 与 RSA 类似: │
│ - 私钥签名,公钥验证 │
│ - 公钥可以公开分发 │
│ │
│ 椭圆曲线优势: │
│ - 更短的密钥长度 │
│ - 更快的计算速度 │
│ - 更小的签名尺寸 │
│ │
│ 安全性对比: │
│ ┌───────────────┬───────────────┐ │
│ │ RSA 密钥长度 │ ECDSA 密钥长度│ │
│ ├───────────────┼───────────────┤ │
│ │ 3072 位 │ 256 位 │ │
│ │ 7680 位 │ 384 位 │
│ │ 15360 位 │ 512 位 │ │
│ └───────────────┴───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
ECDSA 算法列表 #
| 算法 | 曲线 | 哈希函数 | 签名长度 |
|---|---|---|---|
| ES256 | P-256 | SHA-256 | 64 字节 |
| ES384 | P-384 | SHA-384 | 96 字节 |
| ES512 | P-521 | SHA-512 | 132 字节 |
生成 ECDSA 密钥对 #
bash
openssl ecparam -genkey -name prime256v1 -noout -out private.pem
openssl ec -in private.pem -pubout -out public.pem
ES256 签名示例 #
javascript
const jwt = require('jsonwebtoken');
const fs = require('fs');
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');
const payload = {
sub: 'user-123',
name: 'John Doe'
};
const token = jwt.sign(payload, privateKey, { algorithm: 'ES256' });
console.log(token);
ES256 验证示例 #
javascript
const decoded = jwt.verify(token, publicKey, { algorithms: ['ES256'] });
console.log(decoded);
ECDSA 优缺点 #
text
┌─────────────────────────────────────────────────────────────┐
│ ECDSA 优缺点分析 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 优点: │
│ ✅ 密钥长度短 │
│ ✅ 签名长度短 │
│ ✅ 计算速度快 │
│ ✅ 适合移动端和 IoT │
│ │
│ 缺点: │
│ ❌ 实现复杂度较高 │
│ ❌ 部分旧系统不支持 │
│ ❌ 随机数质量影响安全 │
│ │
│ 适用场景: │
│ - 需要高性能的场景 │
│ - 移动应用 │
│ - IoT 设备 │
│ - 带宽受限环境 │
│ │
└─────────────────────────────────────────────────────────────┘
算法对比 #
安全性对比 #
text
┌─────────────────────────────────────────────────────────────┐
│ 算法安全性对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 安全级别 │ HMAC │ RSA │ ECDSA │
│ ───────────────┼──────────────┼──────────────┼────────────│
│ 128 位安全 │ HS256 │ RSA-3072 │ ES256 │
│ 192 位安全 │ HS384 │ RSA-7680 │ ES384 │
│ 256 位安全 │ HS512 │ RSA-15360 │ ES512 │
│ │
│ 推荐: │
│ - 128 位安全对于大多数应用足够 │
│ - 高安全需求使用 192 位或更高 │
│ │
└─────────────────────────────────────────────────────────────┘
性能对比 #
text
┌─────────────────────────────────────────────────────────────┐
│ 算法性能对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 签名速度(快 → 慢): │
│ HMAC > ECDSA > RSA │
│ │
│ 验证速度(快 → 慢): │
│ HMAC > RSA > ECDSA │
│ │
│ 签名长度(短 → 长): │
│ HMAC (32-64字节) < ECDSA (64-132字节) < RSA (256字节) │
│ │
│ 密钥长度(短 → 长): │
│ ECDSA (32-66字节) < HMAC (32-64字节) < RSA (2048+字节) │
│ │
└─────────────────────────────────────────────────────────────┘
场景选择 #
text
┌─────────────────────────────────────────────────────────────┐
│ 算法选择指南 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 单服务架构: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 推荐:HS256 │ │
│ │ 原因:简单、快速、签名短 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 多服务/微服务架构: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 推荐:RS256 或 ES256 │ │
│ │ 原因:公钥可公开,资源服务器独立验证 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 公开 API: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 推荐:RS256 │ │
│ │ 原因:兼容性好,公钥可公开分发 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 移动端/IoT: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 推荐:ES256 │ │
│ │ 原因:签名短、计算快、带宽占用小 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 高安全需求: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 推荐:PS256 或 ES384 │ │
│ │ 原因:更安全的签名方案 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
none 算法 #
什么是 none 算法? #
text
┌─────────────────────────────────────────────────────────────┐
│ none 算法警告 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ⚠️ 危险:none 算法表示不签名 │
│ │
│ 特点: │
│ - 没有 Signature 部分 │
│ - 任何人都可以修改 Payload │
│ - 极度不安全 │
│ │
│ 示例: │
│ { "alg": "none", "typ": "JWT" } │
│ { "sub": "admin", "role": "superuser" } │
│ (无签名) │
│ │
│ 安全建议: │
│ ❌ 永远不要使用 none 算法 │
│ ✅ 验证时明确拒绝 none 算法 │
│ ✅ 始终验证 alg 字段 │
│ │
└─────────────────────────────────────────────────────────────┘
防护措施 #
javascript
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256', 'ES256']
});
算法混淆攻击 #
攻击原理 #
text
┌─────────────────────────────────────────────────────────────┐
│ 算法混淆攻击 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 攻击场景: │
│ 1. 服务端使用 RS256(RSA 公钥验证) │
│ 2. 攻击者将 alg 改为 HS256 │
│ 3. 攻击者用公钥作为 HMAC 密钥签名 │
│ 4. 服务端可能误用公钥验证 HMAC │
│ │
│ 攻击流程: │
│ 原始 Header: { "alg": "RS256" } │
│ 篡改 Header: { "alg": "HS256" } │
│ 使用公钥作为 HMAC 密钥签名 │
│ │
│ 防护措施: │
│ ✅ 明确指定允许的算法列表 │
│ ✅ 不要根据 JWT 的 alg 字段选择算法 │
│ ✅ 使用类型安全的 JWT 库 │
│ │
└─────────────────────────────────────────────────────────────┘
防护代码 #
javascript
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256']
});
密钥管理 #
密钥生成 #
javascript
const crypto = require('crypto');
const hmacSecret = crypto.randomBytes(32).toString('hex');
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
namedCurve: 'P-256',
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
密钥存储 #
text
┌─────────────────────────────────────────────────────────────┐
│ 密钥存储最佳实践 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 环境变量 │
│ JWT_SECRET=your-secret-key │
│ 适合:开发环境、简单应用 │
│ │
│ 2. 密钥管理服务(KMS) │
│ AWS KMS, Google Cloud KMS, Azure Key Vault │
│ 适合:生产环境、企业应用 │
│ │
│ 3. 密钥文件 │
│ 存储在安全目录,设置权限 │
│ 适合:服务器部署 │
│ │
│ 4. 硬件安全模块(HSM) │
│ 物理设备存储密钥 │
│ 适合:高安全需求 │
│ │
│ 安全建议: │
│ ❌ 不要将密钥提交到代码仓库 │
│ ❌ 不要在日志中打印密钥 │
│ ✅ 定期轮换密钥 │
│ ✅ 不同环境使用不同密钥 │
│ │
└─────────────────────────────────────────────────────────────┘
下一步 #
现在你已经了解了 JWT 的签名算法,接下来学习 JWT 基本使用,开始在实际项目中使用 JWT!
最后更新:2026-03-28