
1. 项目概述从“弱密码”到“密钥破解”的攻防演练最近在排查一个内部系统的日志时发现了几条可疑的JWT令牌记录格式看起来没问题但签发方却有点对不上。这让我立刻警觉起来因为JWTJSON Web Token作为现代Web应用身份验证的“身份证”一旦它的“防伪印章”——也就是签名密钥——出了问题整个系统的安全防线就可能形同虚设。很多人包括一些经验丰富的开发者都容易犯一个错误为了图方便或者测试使用了诸如secret、password123、甚至直接用空字符串作为JWT的签名密钥。这无异于把自家大门的钥匙挂在门把手上。这个项目我们就来一次“以攻促防”的实战演练。我不会教你去做坏事而是通过手把手演示如何利用强大的密码恢复工具Hashcat去“破解”一个使用弱密钥签名的JWT让你亲眼看到弱密钥是多么不堪一击。只有当你站在攻击者的角度理解了攻击是如何发生的你才能真正重视并实施有效的防御。整个过程就像一次安全审计目标是为了让你回去检查自己的系统加固你的身份验证机制。我们将从理解JWT的结构和签名原理开始到准备攻击环境、构造攻击载荷最后使用Hashcat进行高效的离线破解。你会发现破解一个弱密钥的JWT可能比你想像的要快得多。2. JWT结构解析与弱密钥风险根源要理解如何破解首先得彻底弄明白JWT是什么以及它的安全基石在哪里。JWT本质上是一个经过编码和签名的JSON对象用于在各方之间安全地传输信息。它通常由三部分组成用点号.分隔Header.Payload.Signature。Header头部通常包含两部分令牌类型typ固定为JWT和所使用的签名算法alg比如HS256HMAC SHA-256、RS256RSA SHA-256等。这部分是Base64Url编码的。Payload负载包含了所要传递的声明Claims。声明是关于实体通常是用户和其他数据的陈述。常见的声明有iss签发者、exp过期时间、sub主题等。你也可以添加自定义声明。这部分同样也是Base64Url编码的。Signature签名这是整个JWT安全性的核心。签名的生成方式是对编码后的Header、编码后的Payload、以及一个密钥Secret进行特定算法的运算。例如对于HS256算法签名是这样生成的HMACSHA256(base64UrlEncode(header) “.” base64UrlEncode(payload), secret)服务器在签发JWT时用密钥生成这个签名。当客户端后续携带此JWT访问时服务器会用同样的密钥对收到的Header和Payload部分重新计算签名并与JWT中的Signature部分进行比对。如果一致说明令牌未被篡改且是由持有正确密钥的服务器签发的。那么弱密钥的风险就显而易见了。如果攻击者拿到了一个有效的JWT这很容易JWT通常存储在客户端的LocalStorage或Cookie中他就可以得到Header和Payload的明文Base64解码即可以及最终的Signature。对于HMAC类算法如HS256签名和验证使用同一个密钥。攻击者的目标就是找到一个密钥使得HMACSHA256(已知的HeaderPayload, 猜测的密钥) 已知的Signature。这个过程是一个典型的离线暴力破解或字典攻击。如果密钥强度很低是一个常见的单词、短字符串或简单变体那么在强大的算力和精心准备的字典面前被破解只是时间问题而且这个时间可能短得惊人——几分钟甚至几秒钟。注意这里讨论的破解场景特指使用对称密钥如HS256的JWT。对于非对称算法如RS256破解的是私钥难度极大通常不通过暴力破解。但开发错误如配置为none算法或密钥泄露是另一回事。3. 攻击环境搭建与工具准备在进行实际操作前我们需要一个可控的环境。我强烈建议在虚拟机或独立的测试机器上进行避免对生产或日常使用的系统造成任何影响。我的实验环境是一台安装了Kali Linux的虚拟机这为我们提供了大部分所需的工具。当然你也可以在Windows或macOS上通过包管理器安装。核心工具HashcatHashcat被誉为世界上最快的密码恢复工具支持多种哈希算法和攻击模式字典、组合、暴力破解等。它能够充分利用CPU和GPU尤其是GPU的计算能力将破解速度提升数个量级。我们将使用它来尝试破解JWT的HMAC签名。安装HashcatKali Linux / Debian/Ubuntu:sudo apt update sudo apt install hashcatmacOS (使用Homebrew):brew install hashcatWindows: 从Hashcat官网下载预编译的可执行文件。安装后在终端输入hashcat --version验证是否成功。准备字典文件 字典攻击的成功率极大程度上依赖于字典的质量。我们可以从一些知名的开源密码字典开始比如rockyou.txtKali Linux中位于/usr/share/wordlists/rockyou.txt.gz需要解压。此外还可以根据目标系统可能使用的命名规则自定义字典例如公司名、项目名、常用单词的变体等。# 在Kali中解压rockyou字典 sudo gunzip /usr/share/wordlists/rockyou.txt.gz你也可以创建自己的简易字典文件my_wordlist.txt内容如secret password 123456 mysecretkey supersecret test dev prod获取一个用于破解的JWT样本 为了演示我们需要一个由弱密钥签名的JWT。你可以使用在线的JWT调试工具如jwt.io来生成。假设我们使用密钥secret对以下内容进行HS256签名Header:{alg:HS256,typ:JWT}Payload:{sub:1234567890,name:John Doe,iat:1516239022}生成的JWT为eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c我们将这个令牌保存下来用于后续的破解。在真实场景中这个JWT是从应用的前端请求如浏览器开发者工具的Network标签中获取的。4. 破解实战使用Hashcat爆破JWT密钥有了环境和目标我们现在进入核心的破解环节。Hashcat本身不直接支持“JWT破解”但我们可以将问题转化为Hashcat支持的哈希格式。我们需要从JWT中提取出用于验证哈希计算的数据。步骤一数据提取与格式转换一个JWT的签名是使用密钥对base64UrlEncode(header) “.” base64UrlEncode(payload)这部分数据进行HMAC-SHA256运算的结果。在Hashcat中我们需要构造一个特定的格式来代表这个计算挑战。JWT的Header和Payload部分是已知的就是点号分隔的前两部分。我们需要计算的是对于候选密钥其HMAC-SHA256结果是否等于JWT的第三部分Signature的Base64Url解码值。Hashcat社区已经为我们准备好了对应的格式。对于HS256JWT其对应的Hashcat模式代号是16500。我们需要将JWT转换成Hashcat能识别的哈希字符串。格式为签名部分Base64Url解码后的十六进制:消息部分Header.Payload手动转换比较麻烦我们可以利用一个简单的Python脚本来完成import base64 import hashlib import hmac import sys def jwt_to_hashcat(jwt_token): try: # 分割JWT parts jwt_token.split(.) if len(parts) ! 3: raise ValueError(Invalid JWT format) header_payload parts[0] . parts[1] signature_b64url parts[2] # 将Base64Url编码的签名转换为标准的Base64然后解码为字节 signature_b64 signature_b64url.replace(-, ).replace(_, /) # 添加可能缺少的填充 padding len(signature_b64) % 4 if padding: signature_b64 * (4 - padding) signature_bytes base64.b64decode(signature_b64) # 转换为十六进制字符串 signature_hex signature_bytes.hex() # 组合成Hashcat格式 (mode 16500) hashcat_format f{signature_hex}:{header_payload} return hashcat_format except Exception as e: print(f转换失败: {e}) return None if __name__ __main__: if len(sys.argv) 1: jwt sys.argv[1] else: # 使用我们示例的JWT jwt “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c” result jwt_to_hashcat(jwt) if result: print(Hashcat 格式哈希:) print(result) # 可以保存到文件 with open(jwt_hash.txt, w) as f: f.write(result)运行这个脚本你会得到一个字符串类似于5f25c5b044948c78a285d904f87f0a4c7897f7e8f3a4eb2255fda750b2cc0977:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ将这段哈希保存到文件jwt_hash.txt中。步骤二执行Hashcat破解现在我们可以使用Hashcat发起攻击了。我们首先尝试字典攻击这是最快的方法。# 基本字典攻击命令 hashcat -m 16500 jwt_hash.txt /usr/share/wordlists/rockyou.txt-m 16500指定哈希类型为JWT (HS256)。jwt_hash.txt包含我们刚生成的哈希值的文件。/usr/share/wordlists/rockyou.txt使用的字典文件。如果你的系统有性能较强的GPUHashcat会自动调用。你可以通过-w选项调整工作负载强度1-4数字越大性能越强但系统响应可能变慢。对于简单的测试默认即可。运行命令后Hashcat会开始工作。由于我们的密钥是极其简单的secret而rockyou.txt字典非常庞大且包含这个单词破解过程几乎是瞬间完成的。终端会显示状态、速度以及最终破解出的密钥。步骤三解读结果破解成功后Hashcat会在屏幕上显示恢复出的密码即JWT密钥。同时它会在当前目录下生成一个名为hashcat.potfile的文件里面存储了所有已破解的哈希及其对应的明文。你可以用cat hashcat.potfile查看。对于我们的示例你会在结果中清晰地看到破解出的密钥secret。实操心得第一次看到自己生成的、看似复杂的令牌被瞬间破解那种震撼感是任何理论说教都无法替代的。这直观地证明了在自动化工具面前人类直觉认为“够用”的简单密码脆弱得就像一张纸。5. 高级攻击模式与性能调优如果字典攻击没有成功你可能需要尝试更强大的攻击模式。Hashcat提供了多种攻击模式应对不同的场景。组合攻击Combinator Attack 如果你怀疑密钥可能是两个单词的组合如mysecret可以使用组合模式。你需要准备两个字典文件。# 假设有 dict1.txt 和 dict2.txt hashcat -m 16500 -a 1 jwt_hash.txt dict1.txt dict2.txt-a 1代表组合攻击模式。掩码攻击Mask Attack 当你对密钥的格式有一定了解时例如知道是8位数字或是以“key”开头后面跟4位数字掩码攻击效率极高。它系统地遍历指定字符集的所有可能组合。# 假设猜测密钥是6位纯数字 hashcat -m 16500 -a 3 jwt_hash.txt ?d?d?d?d?d?d # 假设猜测密钥是“key”3位数字 hashcat -m 16500 -a 3 jwt_hash.txt key?d?d?d-a 3代表掩码攻击。?d代表数字?l代表小写字母?u代表大写字母?s代表特殊字符。?a代表所有可打印字符。混合攻击Hybrid Attack 结合字典和掩码例如在字典每个单词后面添加数字。# 字典2位数字后缀 hashcat -m 16500 -a 6 jwt_hash.txt dictionary.txt ?d?d # 2位数字前缀字典 hashcat -m 16500 -a 7 jwt_hash.txt ?d?d dictionary.txt-a 6是字典掩码-a 7是掩码字典。性能调优建议使用GPU确保Hashcat识别并使用了你的GPUNVIDIA或AMD。NVIDIA用户可能需要安装CUDA工具包AMD用户需要安装ROCm或OpenCL驱动。使用hashcat -I查看可用的OpenCL设备。优化工作负载-w 3或-w 4可以显著提升破解速度但会让电脑几乎无法进行其他操作。在专用破解机器上可以使用。规则化字典对字典中的单词应用规则如大小写变换、添加后缀、字符替换等可以极大扩展攻击覆盖面。Hashcat自带强大的规则引擎使用-r参数指定规则文件如dive.rule,best64.rule。hashcat -m 16500 jwt_hash.txt dictionary.txt -r /usr/share/hashcat/rules/best64.rule6. 从攻击视角看防御如何加固你的JWT身份验证通过上面的实战我们已经深刻体会到弱密钥的危害。现在让我们把视角转回防御方讨论如何构建一个健壮的JWT身份验证系统。6.1 密钥管理是重中之重使用强密钥绝对禁止使用字典单词、常见短语、短字符串或与项目相关的简单词汇。密钥应该是一个高熵值的随机字符串。建议长度至少为32字节256位对于HS256算法这正好是哈希函数的块大小。可以使用安全的随机数生成器来生成。# 在Linux/macOS下生成一个32字节的随机十六进制密钥 openssl rand -hex 32 # 输出类似4f3b2a1c5e6d7f809a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5密钥分离不同环境开发、测试、生产、不同服务应使用不同的密钥。避免一个密钥泄露导致全线崩溃。定期轮换制定密钥轮换策略。但这需要谨慎处理因为正在使用的JWT在过期前仍需能被验证。一种方案是使用密钥IDKey ID,kid头声明并维护一个有效的密钥列表新签发的令牌使用新密钥旧令牌在过期前仍可用旧密钥验证。6.2 算法与令牌配置避免使用none算法绝对不要在生产环境中使用alg: none。服务器端必须严格校验alg头并只接受预期的强算法如HS256, RS256。设置合理的过期时间expJWT应有较短的过期时间如15分钟到几小时减少令牌泄露后的风险窗口。通过刷新令牌Refresh Token机制来获取新的访问令牌。使用iss签发者和aud受众声明在验证令牌时检查iss是否是你信任的授权服务器aud是否是你的服务。这可以防止令牌被滥用。6.3 安全的存储与传输避免LocalStorage在浏览器中将JWT存储在LocalStorage容易受到XSS攻击。更推荐使用HttpOnly、Secure、SameSite的Cookie但这需要妥善处理CSRF防护。强制HTTPSJWT必须在TLS/HTTPS连接中传输防止中间人窃听。6.4 实施额外的验证与监控令牌吊销列表对于需要立即失效令牌的场景如用户登出、密码修改可以维护一个短期的令牌吊销列表黑名单但这会引入状态部分违背JWT无状态的初衷。需要权衡。监控与告警监控异常的JWT使用模式如大量使用同一个令牌、来自异常地理位置的请求、令牌在过期后仍被尝试使用等。对失败的签名验证尝试进行日志记录和告警。7. 常见问题与排查技巧实录在实际操作Hashcat或加固JWT系统时你可能会遇到一些问题。这里记录了一些典型场景和解决方法。7.1 Hashcat破解相关问题Hashcat运行后没有任何进度或者速度极慢。排查首先用hashcat -I检查是否识别到了GPU。如果只使用CPU速度会慢很多。对于大型字典或复杂掩码耐心等待或考虑优化攻击策略。技巧可以先用一个很小的字典或简单的掩码如?d?d?d?d测试命令是否正常运行排除哈希格式错误的可能。问题提示“Token length exception”或“Signature unmatched”。排查这几乎总是因为提供的JWT哈希格式不正确。仔细检查转换脚本确保从JWT中提取的Header和Payload部分点号前两部分完全正确且签名部分被正确解码为十六进制。一个常见的错误是JWT字符串本身包含了引号或换行符。问题字典攻击没找到密钥接下来怎么办思路扩大字典合并多个字典使用更全面的字典如rockyou.txtkaonashi.txt。应用规则对字典使用规则文件这是性价比极高的方法。分析目标思考开发人员可能设置什么样的密钥项目名、公司名缩写、常用测试密码的变体基于此构建针对性的字典或掩码。转向掩码攻击如果你对密钥长度和字符集有大致估计例如感觉像8-12位的字母数字组合可以尝试掩码攻击。从较短的掩码开始尝试。7.2 JWT验证与加固相关问题如何在我的后端代码中安全地验证JWT建议使用成熟、经过审计的库而不是自己手写解析和验证逻辑。例如在Node.js中使用jsonwebtoken在Python中使用PyJWT或Authlib在Java中使用jjwt。这些库通常会强制你指定算法避免none算法漏洞。示例Python PyJWTimport jwt try: # 必须指定算法列表这里只允许HS256 payload jwt.decode(encoded_jwt, ‘你的强密钥’, algorithms[‘HS256’]) # 还可以验证iss, aud等 # payload jwt.decode(…, options{‘verify_iss’: True}, issuer‘your-issuer’) except jwt.exceptions.InvalidSignatureError: # 签名无效拒绝请求 pass except jwt.ExpiredSignatureError: # 令牌过期 pass问题密钥轮换时如何保证旧令牌不失效方案引入密钥IDkid。在JWT的Header中携带一个kid字段。服务器端维护一个密钥环Key Ring包含当前和历史的kid到密钥的映射。验证时根据kid查找对应的密钥进行验证。签发新令牌时使用新的密钥和新的kid。问题我怀疑系统存在弱密钥如何自查方法你可以像我们本次演练一样收集生产环境中正在使用的JWT确保有合法授权例如对自己的测试账户在隔离环境中使用强大的字典和规则进行离线测试。如果密钥能被快速破解说明存在严重风险。这应作为安全审计的一部分。这次从攻击到防御的完整推演核心目的只有一个消除侥幸心理。在安全领域“看不见”不等于“不存在”。一个弱JWT密钥就像一颗埋在深处的雷平时风平浪静一旦被攻击者扫描发现并利用造成的破坏可能是毁灭性的。加固身份验证并非难事从生成一个足够随机的强密钥开始遵循安全库的最佳实践并建立持续的监控意识就能构筑起坚实的第一道防线。记住真正的安全不在于完全杜绝攻击而在于让攻击的成本高到不可接受。使用强密钥正是提高攻击成本最简单、最有效的一步。