3. JWT签名与加密算法全景

在JWT的工程实践中,算法选择往往是最容易被忽视、却对安全边界影响最大的决策。很多团队在项目初期随手写下 HS256,等到需要引入第三方验证方、需要密钥轮换、需要审计签名来源时,才发现整个密钥治理体系已经被这个选择锁死。本节从数学基础出发,把五种主流签名算法、JWS与JWE的结构差异,以及算法选择如何反向决定密钥治理策略,串成一条完整的判断链。

一、JWS与JWE:先分清“签名”和“加密”

JWT本身只是一个声明容器,真正决定其安全形态的是承载它的结构。JWS(JSON Web Signature)解决的是完整性与来源认证:载荷是Base64URL编码的明文,任何人都能读取,但任何篡改都会导致签名校验失败。JWE(JSON Web Encryption)解决的是机密性:载荷被加密,未持有解密密钥的一方无法读取内容。

两者的结构差异直接体现在序列化形式上。JWS的紧凑序列化是三段式 header.payload.signature;JWE的紧凑序列化是五段式 header.encrypted_key.iv.ciphertext.tag。这意味着一个JWT到底是JWS还是JWE,从段数就能初步判断。更关键的是,JWE的头部必须同时声明两个算法:alg用于保护内容加密密钥(CEK),enc用于实际加密载荷,例如 {"alg":"RSA-OAEP","enc":"A256GCM"}。而JWS只有一个alg。

实践中最常见的误区是把敏感信息放进JWS载荷,误以为“签名了就安全”。签名只防篡改,不防读取。凡是包含用户身份标识、内部权限位、会话上下文的令牌,如果会经过不可信通道或前端存储,就应当评估是否改用JWE,或者干脆只放不可推导的引用ID。

JWS / JWE 结构对比与签名算法密钥治理映射 JWS · 签名(完整性 + 来源认证) header payload(明文) signature 三段式:header.payload.signature 头部仅声明 alg;载荷可被任意方读取 ⚠ 签名 ≠ 加密,敏感数据不可放入载荷 JWE · 加密(机密性 + 完整性) header enc_key iv ciphertext tag 五段式:header.enc_key.iv.ciphertext.tag 头部同时声明 alg(保护CEK)与 enc(加密载荷) ✓ 载荷不可读,适合携带敏感声明 五种签名算法:数学基础 · 密钥类型 · 治理影响 算法 数学基础 密钥类型 适用场景 治理影响 HS256 HMAC-SHA256,对称哈希 共享密钥(同一把) 单体服务、内部短令牌 密钥分发即泄露风险 RS256 RSA大整数分解,PKCS#1 v1.5 RSA公私钥对(≥2048位) 多方验证、OIDC标准场景 公钥可公开分发 ES256 椭圆曲线离散对数,P-256 EC公私钥对(256位) 移动端、IoT、带宽敏感 密钥短、签名短 PS256 RSA-PSS,随机化填充 RSA公私钥对(≥2048位) 高安全合规、新系统 抗选择消息攻击更强 EdDSA Edwards曲线,Ed25519 Ed25519公私钥对 现代系统、高性能验证 确定性签名、无随机数依赖

二、五种签名算法的数学基础与工程差异

HS256基于HMAC-SHA256,属于对称算法。签名与验证使用同一把密钥,数学上依赖SHA-256的压缩函数与密钥异或运算。它的优势是计算极快、实现简单,缺点是密钥必须被所有验证方共享。一旦某个验证方被攻破,攻击者就能伪造任意令牌。因此HS256只适合“签发方与验证方是同一信任域”的场景,例如单体应用内部的短期访问令牌。

RS256基于RSA,安全性建立在“大整数分解困难”之上,采用PKCS#1 v1.5填充。签名方用私钥运算,验证方用公钥运算,公钥可以安全地公开分发。这是OIDC和多数企业SSO的事实标准。但PKCS#1 v1.5填充在理论上存在选择消息攻击的隐患,且RSA密钥长度通常需要2048位以上,签名体积较大。

PS256同样基于RSA,但使用PSS(Probabilistic Signature Scheme)随机化填充。它在数学上具有可证明的安全性归约,对选择消息攻击的抵抗更强。代价是签名过程需要高质量随机数,且部分老旧JWT库对PS256支持不完整。新系统在需要RSA兼容性时,应优先选择PS256而非RS256。

ES256基于P-256椭圆曲线上的离散对数问题。256位EC密钥的安全强度大致相当于3072位RSA密钥,但签名只有64字节左右。它的数学运算基于椭圆曲线点乘,计算量远小于RSA。ES256非常适合移动端、IoT设备和带宽受限的令牌传递场景。需要注意的是,ECDSA签名依赖随机数k,若k重复或可预测,私钥会被直接推导出来——这是历史上多次真实攻击的根源。

EdDSA(通常指Ed25519)基于扭曲爱德华兹曲线,采用确定性签名方案。它不依赖每次签名时的随机数,从根本上规避了ECDSA的随机数风险。Ed25519的验证速度极快,密钥和签名都很短,且实现复杂度低、侧信道抵抗性好。对于新建系统,如果没有必须兼容RSA的约束,EdDSA通常是综合最优的选择。

三、算法选择如何反向决定密钥治理

算法选择不是孤立的密码学决策,它直接决定了密钥的生命周期管理方式。可以从三个维度来理解这种反向约束。

第一,密钥分发边界。HS256的密钥必须同时存在于签发方和所有验证方,密钥分发范围等于信任域范围。这意味着密钥轮换需要协调所有验证方同步更新,任何一方滞后都会导致验证失败。而RS256、ES256、PS256、EdDSA都是非对称算法,私钥只存在于签发方,公钥通过JWKS端点公开。轮换时只需签发方生成新密钥对并发布新公钥,验证方通过kid自动匹配,治理复杂度大幅降低。

第二,密钥轮换与多密钥并存。非对称算法天然支持“多公钥并存”:JWKS中可以同时发布多个公钥,每个公钥带唯一kid。签发方用新私钥签发新令牌,旧令牌仍可用旧公钥验证,直到过期。这实现了平滑轮换。HS256要实现同样的效果,必须在所有验证方同时维护多把共享密钥,协调成本极高。

第三,算法混淆攻击的防御。历史上出现过攻击者将头部alg从RS256改为HS256,然后用公开的RSA公钥作为HMAC密钥来伪造签名的攻击。防御的核心原则是:验证方必须显式指定允许的算法白名单,绝不能信任令牌头部声明的alg。在密钥治理层面,这意味着JWKS中应当明确标注每个密钥的alg和use字段,验证逻辑必须做算法与密钥类型的交叉校验。

关键提醒:无论选择哪种算法,都必须显式配置算法白名单。例如在Node.js的jsonwebtoken库中,验证时应写jwt.verify(token, publicKey, { algorithms: ['RS256'] }),而不是省略algorithms参数。省略参数会让库接受头部声明的任意算法,直接打开算法混淆攻击的大门。

四、一个可参考的算法选择决策表

下表给出的是工程决策参考,不是绝对规则。实际选择还需结合现有技术栈、合规要求和验证方能力。

场景特征推荐算法密钥治理要点
单体应用内部令牌,签发与验证同域HS256密钥长度≥256位,定期轮换,禁止跨域共享
OIDC / 企业SSO,多方验证RS256 或 PS256JWKS公开公钥,kid匹配,私钥HSM保护
移动端 / IoT,带宽与算力受限ES256 或 EdDSA确保随机数质量(ES256)或直接选EdDSA
新建系统,无RSA兼容包袱EdDSA确定性签名,密钥短,轮换简单
需要携带敏感声明JWE + 上述任一签名算法CEK与签名密钥分离管理,enc选A256GCM

最后需要强调的是,算法选择没有“一劳永逸”的答案。随着系统从单体走向微服务、从内部走向开放平台,最初选择的HS256可能成为密钥治理的瓶颈。因此在架构初期就应当把“验证方数量”和“信任域边界”作为算法选择的第一判断依据,而不是等到密钥分发失控时再被迫迁移。