
jwt签名验证时,因base64编码末尾填充特性(如=被省略或替换为相似字符),不同字符串可能解码为相同字节序列,导致看似不同的签名被误判为有效——这并非漏洞,而是编码规范的自然结果。
jwt签名验证时,因base64编码末尾填充特性(如=被省略或替换为相似字符),不同字符串可能解码为相同字节序列,导致看似不同的签名被误判为有效——这并非漏洞,而是编码规范的自然结果。
JSON Web Token(JWT)的签名部分采用Base64Url编码(RFC 7515),该编码是标准Base64的变种:它使用A-Z、a-z、0-9、-和_字符集,并省略末尾填充符=。关键在于:Base64解码器在处理不完整填充时具有容错性——当输入长度不是4的倍数时,多数实现(包括jwt-go及jwt.io前端)会自动补足缺失的=,并忽略末尾无效字符(如将V、W、X等映射到同一字节的低有效位区域)。
以问题中的两个签名为例:
SxshVL42DUH9e7jXUblbB_bTwKxhe4jo70DrvbQMlaU SxshVL42DUH9e7jXUblbB_bTwKxhe4jo70DrvbQMlaV
它们经Base64Url解码后均得到完全相同的32字节哈希值(SHA-256输出):
# 使用命令行验证(需先替换 '-' → '+','_' → '/',再补全填充) echo "SxshVL42DUH9e7jXUblbB_bTwKxhe4jo70DrvbQMlaU" | \ sed 's/-/+/g; s/_/\//g; s/.$/==/' | base64 -d | xxd -p # 输出:a31b2154be360d41fd7bb8d751b95b07f6d3c0ac617b88e8ef40ebbdb40c95a4 echo "SxshVL42DUH9e7jXUblbB_bTwKxhe4jo70DrvbQMlaV" | \ sed 's/-/+/g; s/_/\//g; s/.$/==/' | base64 -d | xxd -p # 输出:a31b2154be360d41fd7bb8d751b95b07f6d3c0ac617b88e8ef40ebbdb40c95a4
这是因为Base64每4字符编码3字节,最后1–2个字符仅影响1–2个最低有效位;而U/V/W/X在Base64字母表中对应二进制后缀高度重叠(例如U=20→010100,V=21→010101),在缺失填充且解码器截断时,高位差异被忽略,最终还原出一致的原始签名字节。
⚠️ 注意事项:
- 此现象不构成安全风险——只要服务端使用标准库(如github.com/golang-jwt/jwt/v5,已取代旧版dgrijalva/jwt-go)进行严格校验,其底层调用base64.RawURLEncoding.DecodeString()会拒绝非法字符或长度异常的签名,避免此类混淆;
- 开发者应始终使用权威库而非手动拼接/解码JWT;
- jwt.io等调试工具为提升用户体验启用宽松解码,不可作为生产环境验证依据;
- 若需自定义验证逻辑,务必使用base64.RawURLEncoding(无填充)并校验输入长度是否为4的倍数。
✅ 总结:所谓“多个签名有效”,本质是Base64Url编码的数学与工程妥协所致。理解其原理有助于规避调试误解,但真正的安全保障仍依赖于遵循RFC标准、使用维护良好的JWT库,并坚持服务端严格验证。










