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。
二、五种签名算法的数学基础与工程差异
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 或 PS256 | JWKS公开公钥,kid匹配,私钥HSM保护 |
| 移动端 / IoT,带宽与算力受限 | ES256 或 EdDSA | 确保随机数质量(ES256)或直接选EdDSA |
| 新建系统,无RSA兼容包袱 | EdDSA | 确定性签名,密钥短,轮换简单 |
| 需要携带敏感声明 | JWE + 上述任一签名算法 | CEK与签名密钥分离管理,enc选A256GCM |
最后需要强调的是,算法选择没有“一劳永逸”的答案。随着系统从单体走向微服务、从内部走向开放平台,最初选择的HS256可能成为密钥治理的瓶颈。因此在架构初期就应当把“验证方数量”和“信任域边界”作为算法选择的第一判断依据,而不是等到密钥分发失控时再被迫迁移。