返回
创建于
状态公开

下面给你一张「够用且靠谱」的对比速查表,涵盖常见 JWT 签名算法在性能、体积、部署难度上的差异。你用 JWT_ALGO 选型时,基本按这个来就行。

性能&特性对比(概览)

算法签名速度验证速度典型签名长度(Base64URL后)密钥/部署兼容性适用场景
HS256 (HMAC-SHA256)🟢 最快🟢 最快43 字符(32B)只需共享 secret(对称)→ 密钥分发要小心广泛内网/同域服务高 QPS;简单、成本低
RS256 (RSA-2048 + SHA256)🟡 慢(相对)🟢 较快342 字符(256B)私钥签名、公钥验证,易做密钥分发与轮换最通用/标准第三方/多方验证;网关/开放平台
RS512 (RSA-2048 + SHA512)🟡≈RS256,略慢🟢≈RS256,略慢342 字符同 RS256略差(部分老实现只测 RS256)很少有必要(哈希改 256→512 对安全提升不明显)
ES256 (ECDSA-P256 + SHA256)🟢 较快🟡 较慢(相对 RSA)~96 字符(~64–72B DER 编码)非对称;密钥小,便于移动端/硬件好(少数旧库兼容坑)追求小体积(带宽/存储敏感)、移动端/IoT

常见性能排序(CPU 无专门硬件加速,Go/OpenSSL 等主流实现下) 吞吐/延迟:HS256 ≫ ES256(sign 更快 / verify 更慢) ≈ RS256(sign 慢 / verify 快)

  • RSA:签名 、验证 (e=65537)
  • ECDSA:签名 、验证 (相对 RSA)
  • HMAC:对称 MAC,签名/验证都非常快

选型建议(结合你的场景)

  • 内网微服务 / 极高 QPS / 简单可靠:优先 HS256。 缺点是需要在签发方与验证方安全地共享同一个 secret(可用 KMS/密钥注入+短周期轮换缓解)。
  • 对外/多方验证(开放平台、前后端/第三方都要验):优先 RS256。 私钥仅在签发端;下发公钥或 JWKS,便于轮换权限隔离。生态与文档最佳。
  • 带宽/存储敏感(例如 cookie、Header、移动/IoT)、或更小密钥:可选 ES256。 签名更小、签名速度快,但验证偏慢、历史兼容偶有坑;确保你的库/网关稳定支持。
  • RS512:通常不需要。相较 RS256 提升有限,反而增加计算与兼容成本。提高 RSA 密钥位数更实际(如 3072/4096),但也会显著变慢和变大。

大致延迟区间(仅供估算,单核、常见 x86,无硬件加速)

  • HS256:约 < 0.05 ms/op(签名/验证都极快)
  • RS256(2048位)签名 ~0.5–1.5 ms验证 ~0.1–0.4 ms
  • ES256(P-256)签名 ~0.2–0.6 ms验证 ~0.4–1.0 ms

实际取决于语言/库版本、是否开启 AVX/硬件加速、密钥长度、是否启用 JIT/汇编优化、以及你的运行环境(容器/虚拟化/能耗模式)。

体积影响(JWT 总长度)

  • 头/载荷一样时,主要差在签名段

    • HS256:~43 字符
    • ES256:~96 字符(DER 可变)
    • RS256/RS512(2048位):~342 字符
  • 这直接影响 HTTP Header 体积、Cookie 大小与传输/存储成本

Go 实战基准(可直接跑)

go
1// go test -bench=. -benchmem
2package jwtbench
3
4import (
5	"crypto/ecdsa"
6	"crypto/elliptic"
7	"crypto/rand"
8	"crypto/rsa"
9	"crypto/sha256"
10	"math/big"
11	"testing"
12
13	"github.com/golang-jwt/jwt/v5"
14)
15
16var payload = jwt.MapClaims{"sub":"123","role":"admin","exp": 1924992000}
17
18func signHS256() (string, error) {
19	token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
20	return token.SignedString([]byte("super-secret-32bytes-minimum-recommended"))
21}
22
23func genRSA() *rsa.PrivateKey {
24	k, _ := rsa.GenerateKey(rand.Reader, 2048)
25	return k
26}
27func signRS256(priv *rsa.PrivateKey) (string, error) {
28	token := jwt.NewWithClaims(jwt.SigningMethodRS256, payload)
29	return token.SignedString(priv)
30}
31
32func genEC() *ecdsa.PrivateKey {
33	k, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
34	return k
35}
36func signES256(priv *ecdsa.PrivateKey) (string, error) {
37	token := jwt.NewWithClaims(jwt.SigningMethodES256, payload)
38	return token.SignedString(priv)
39}
40
41func BenchmarkHS256_Sign(b *testing.B) {
42	for i := 0; i < b.N; i++ {
43		if _, err := signHS256(); err != nil { b.Fatal(err) }
44	}
45}
46
47func BenchmarkRS256_Sign(b *testing.B) {
48	priv := genRSA()
49	b.ResetTimer()
50	for i := 0; i < b.N; i++ {
51		if _, err := signRS256(priv); err != nil { b.Fatal(err) }
52	}
53}
54
55func BenchmarkES256_Sign(b *testing.B) {
56	priv := genEC()
57	b.ResetTimer()
58	for i := 0; i < b.N; i++ {
59		if _, err := signES256(priv); err != nil { b.Fatal(err) }
60	}
61}
62
63// 验证基准:预先签一个 token,循环验证
64func BenchmarkHS256_Verify(b *testing.B) {
65	secret := []byte("super-secret-32bytes-minimum-recommended")
66	tok, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, payload).SignedString(secret)
67	b.ResetTimer()
68	for i := 0; i < b.N; i++ {
69		_, err := jwt.Parse(tok, func(*jwt.Token) (interface{}, error) { return secret, nil })
70		if err != nil { b.Fatal(err) }
71	}
72}
73
74func BenchmarkRS256_Verify(b *testing.B) {
75	priv := genRSA(); pub := &priv.PublicKey
76	tok, _ := signRS256(priv)
77	b.ResetTimer()
78	for i := 0; i < b.N; i++ {
79		_, err := jwt.Parse(tok, func(*jwt.Token) (interface{}, error) { return pub, nil })
80		if err != nil { b.Fatal(err) }
81	}
82}
83
84func BenchmarkES256_Verify(b *testing.B) {
85	priv := genEC(); pub := &priv.PublicKey
86	tok, _ := signES256(priv)
87	b.ResetTimer()
88	for i := 0; i < b.N; i++ {
89		_, err := jwt.Parse(tok, func(*jwt.Token) (interface{}, error) { return pub, nil })
90		if err != nil { b.Fatal(err) }
91	}
92}
93
94// 额外:演示“ES 验证更慢”的直觉对比(非基准) 
95func TestECDSAIntuition(t *testing.T) {
96	priv := genEC()
97	r, s, _ := ecdsa.Sign(rand.Reader, priv, sha256.Sum256([]byte("x"))[:])
98	ok := ecdsa.Verify(&priv.PublicKey, sha256.Sum256([]byte("x"))[:], r, s)
99	if !ok { t.Fatal("verify failed") }
100}

小结(怎么选)

  • 追求极致性能/QPSHS256
  • 需要非对称、跨服务/第三方RS256(首选,生态最佳)
  • 需要更小签名/移动端友好ES256(注意验证成本与兼容性)
  • 避免RS512(除非有合规或明确要求)

如果你把 JWT_ALGO 做成可切换(例如 HS256 / RS256 / ES256),建议同时支持:

  • kid + JWKS(便于密钥轮换
  • 短有效期 + 刷新机制(降低密钥泄漏风险)
  • 针对 ES256 提前做兼容性验收(网关/SDK/第三方)