欢迎光临
我们一直在努力

JWT签名算法全解析:HS256 vs RS256 vs ES256 怎么选

JWT 的 Header 里有个 alg 字段,填 HS256 还是 RS256?很多人直接用库的默认值,从没想过这个问题。选错了,轻则性能浪费,重则安全漏洞。本文把三类签名算法的原理、适用场景和选型决策讲清楚。

JWT 签名算法分类

JWT 规范定义了三类签名算法:

类别算法密钥模式典型场景
HMAC HS256 / HS384 / HS512 对称密钥 单体应用、内部服务
RSA RS256 / RS384 / RS512 非对称密钥(公钥+私钥) 微服务、第三方授权
ECDSA ES256 / ES384 / ES512 非对称密钥(公钥+私钥) 移动端、物联网
None none 无密钥 ❌ 不应该使用

还有 PS256(RSASSA-PSS)系列,是 RSA 的概率性签名变体,安全性更高但库支持较少,实际项目中不太常见。

HMAC 对称签名(HS256)

HS256 是最常见的 JWT 算法。签发和验证用同一个密钥——这就是"对称"的含义。

工作原理:

签名 = HMAC-SHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)

用 Node.js 的 crypto 模块手动实现:

const crypto = require('crypto')

function signHS256(header, payload, secret) {
const data = Buffer.from(
base64UrlEncode(JSON.stringify(header)) + '.' + base64UrlEncode(JSON.stringify(payload))
)
return crypto.createHmac('sha256', secret).update(data).digest('base64url')
}

function verifyHS256(token, secret) {
const parts = token.split('.')
const data = parts[0] + '.' + parts[1]
const expected = crypto.createHmac('sha256', secret).update(data).digest('base64url')
// 用 timingSafeEqual 防止时序攻击
const a = Buffer.from(expected)
const b = Buffer.from(parts[2])
return a.length === b.length && crypto.timingSafeEqual(a, b)
}

用 jsonwebtoken 库更简单:

const jwt = require('jsonwebtoken')

const token = jwt.sign(
{ sub: 'user-123', role: 'admin' },
'my-256-bit-secret-key-aaaaaa',
{ algorithm: 'HS256', expiresIn: '1h' }
)

const decoded = jwt.verify(token, 'my-256-bit-secret-key-aaaaaa')

优点:计算快,实现简单,一个密钥搞定。

缺点:签发方和验证方共享密钥。任何持有密钥的服务都能签发 Token,无法区分"谁签的"。

适用场景:单体应用(自己签自己验)、内部微服务间通信(可信环境)。

密钥要求:至少 256 位(32 字节)。用 crypto.randomBytes(32).toString('hex') 生成。

RSA 非对称签名(RS256)

RS256 用私钥签名、公钥验证。签发方持有私钥,验证方只需要公钥。

工作原理:

签名 = RSA-SHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
privateKey
)

验证 = RSA-SHA256-Verify(data, signature, publicKey)

生成 RSA 密钥对:

# 生成私钥
openssl genrsa -out private.pem 2048

# 从私钥导出公钥
openssl rsa -in private.pem -pubout -out public.pem

Node.js 代码:

const crypto = require('crypto')
const privateKey = require('fs').readFileSync('private.pem')
const publicKey = require('fs').readFileSync('public.pem')

// 签发(用私钥)
const token = jwt.sign({ sub: 'user-123' }, privateKey, {
algorithm: 'RS256',
expiresIn: '1h'
})

// 验证(用公钥)
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] })

优点:公钥可以公开分发,任何服务都能验证 Token,但只有签发方能签发。解决了"谁签的"问题。

缺点:签名和验证速度比 HMAC 慢一个数量级。RSA-2048 密钥体积大(PEM 文件约 1.7KB)。

适用场景:微服务架构(认证中心签发,多个业务服务验证)、OAuth2/OpenID Connect、第三方授权。

ECDSA 椭圆曲线签名(ES256)

ES256 用椭圆曲线密码学(ECC),同样是私钥签名、公钥验证,但密钥更短、速度更快。

RSA vs ECDSA 密钥长度对照:

安全等级RSA 密钥长度ECDSA 密钥长度JWT 算法
128 位 2048 256 RS256 / ES256
192 位 3072 384 RS384 / ES384
256 位 15360 521 RS512 / ES512

同样的安全等级,ECDSA 的密钥短一个数量级。

生成 ECDSA 密钥对:

# 生成 ECDSA 私钥(prime256v1 = ES256 曲线)
openssl ecparam -genkey -name prime256v1 -noout -out ec-private.pem

# 导出公钥
openssl ec -in ec-private.pem -pubout -out ec-public.pem

Node.js 代码:

const privateKey = require('fs').readFileSync('ec-private.pem')
const publicKey = require('fs').readFileSync('ec-public.pem')

// 签发
const token = jwt.sign({ sub: 'user-123' }, privateKey, {
algorithm: 'ES256',
expiresIn: '1h'
})

// 验证
const decoded = jwt.verify(token, publicKey, { algorithms: ['ES256'] })

优点:密钥短、签名短、签名速度比 RSA 快 3-5 倍。适合带宽受限场景。

缺点:实现比 RSA 复杂,库的兼容性不如 RSA。如果 ECDSA 的随机数源有问题(如重复随机数),会导致私钥泄露。

适用场景:移动端认证、物联网设备、对 Token 体积敏感的 API。

alg: none —— 绝对不要用

JWT 规范允许 alg: none,表示不签名。Token 只有 Header 和 Payload 两段,没有 Signature。

// 签发无签名 Token(危险!)
const token = jwt.sign({ sub: 'user-123' }, '', { algorithm: 'none' })
// eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ1c2VyLTEyMyJ9.

这个算法存在于规范中是为了开发调试方便,但生产环境绝对不能用。很多 JWT 安全漏洞都和 alg: none 有关——攻击者把 alg 改成 none,服务端不校验算法就直接信任 Payload。

防御方法:验证 Token 时显式指定允许的算法:

// 错误:不指定算法,可能被 alg: none 绕过
const decoded = jwt.verify(token, publicKey)

// 正确:白名单指定算法
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256', 'ES256'] // 不包含 none
})

选型决策树

是否需要多个服务验证 Token?
├── 否 → HS256(对称密钥,简单快速)
└── 是 → 是否对 Token 体积/性能敏感?
├── 否 → RS256(RSA 非对称,生态最成熟)
└── 是 → ES256(ECDSA 非对称,密钥短速度快)

实际项目中 90% 的团队选 HS256,因为单体应用或内部服务间不区分签发方和验证方。一旦要做第三方授权或开放 API,就应该切 RS256。ES256 在国内用得少,但在 Google、Cloudflare 等公司的体系中很常见。

算法速查表

算法类型密钥签名速度验证速度安全性
HS256 HMAC 共享密钥 推荐
HS384 HMAC 共享密钥 推荐
HS512 HMAC 共享密钥 推荐
RS256 RSA 公钥+私钥 推荐
RS384 RSA 公钥+私钥 推荐
RS512 RSA 公钥+私钥 推荐
ES256 ECDSA 公钥+私钥 推荐
ES384 ECDSA 公钥+私钥 推荐
ES512 ECDSA 公钥+私钥 推荐
none None 不安全

在线验证

理解算法最好的方式是看真实 Token 的 Header 差异。可以在 盘子工具站 JWT 解析工具 上做这种对比——粘贴不同算法签发的 Token,观察 Header 卡片中 alg 字段的变化。用 HS256 签发的 Token,Header 显示 "alg": "HS256";用 RS256 签发的,显示 "alg": "RS256"。 在这里插入图片描述 工具的算法参考表直接列出了 10 种 JWT 算法的类型、说明和安全性标注。如果 Token 用的是 alg: none,工具会显示醒目的橙色"无签名算法(alg=none)"警告——你可以构造一个 alg: none 的 Token 粘贴进去看看效果,理解为什么生产环境不能用。

工具的所有解码在浏览器本地完成,不上传服务器。在选型阶段,可以把不同算法签发的 Token 都粘贴进去,对比各段的长度差异——RSA 签名的 Token 比 HMAC 的长不少,ECDSA 居中。这对 Token 在 URL 或 Header 中传输的体积评估很有参考价值。

赞(0)
未经允许不得转载:171主机测评 » JWT签名算法全解析:HS256 vs RS256 vs ES256 怎么选
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址