前端AES加密逆向实战:从混淆JS中提取Key与Iv的完整指南 📅 2026/8/3 4:31:54 1. 项目缘起一次意料之外的“黑盒”挑战前几天团队里一个负责数据对接的同事跑来找我眉头紧锁。他手里拿着一个第三方供应商提供的Web页面功能是正常的但核心的业务数据在传输前被一段混淆过的JavaScript代码用AES加密了。供应商只给了调用方式关于加密的核心参数——密钥Key和初始化向量Iv——却以“安全考虑”为由拒绝提供。没有这两个东西我们的后端服务就无法解密数据整个数据自动化流程就卡住了。这其实是一个在爬虫、逆向分析和安全测试领域非常经典的场景面对一个前端加密的黑盒如何从纷繁复杂的JavaScript代码中定位到关键的加密逻辑并提取出决定性的Key和Iv。这不仅仅是找到两个字符串那么简单它涉及到对现代前端代码尤其是经过混淆和压缩的代码的静态分析与动态调试能力。这次经历我就把它完整地记录下来一方面做个总结另一方面也给遇到类似问题的朋友提供一个清晰的排查思路。整个过程就像是在玩一个数字世界的“寻宝游戏”线索都藏在代码的执行逻辑里。2. 逆向分析的核心思路与工具选型面对一个经过混淆的JS文件直接阅读几乎是不可行的。变量名可能是单个字母函数结构被压扁逻辑支离破碎。因此我们的核心思路必须从“静态硬读”转向“动态追踪”和“关键点拦截”。2.1 逆向分析的三步走策略我的策略通常分为三步这三步环环相扣缺一不可环境定位与初步侦查首先要确定加密发生的位置。是在表单提交时还是在某个特定的API请求发起前使用浏览器开发者工具的“网络”Network面板找到那个携带加密数据的请求查看其调用栈Initiator可以快速定位到触发加密的JavaScript函数入口。动态调试与逻辑追踪在定位到的函数入口处设置断点。当加密触发时代码执行会在此暂停。此时利用调试器的“单步步入”Step into、“单步步过”Step over功能结合“调用栈”Call Stack和“作用域”Scope面板一步步跟踪数据的流向。我们的目标是找到最终调用加密库函数如CryptoJS的CryptoJS.AES.encrypt或Web Crypto API的crypto.subtle.encrypt的那一行代码。参数提取与验证在加密函数被调用的一瞬间其传入的参数明文、Key、Iv、模式、填充方式等会暴露在调试器的监控之下。此时我们可以直接查看或记录这些参数的值。提取后必须用独立的加解密工具如Python的cryptography库、在线AES工具进行验证确保提取的参数能正确解密出已知的测试数据。2.2 工具链的选择与考量工欲善其事必先利其器。以下是本次分析中用到的核心工具及其选择理由浏览器开发者工具Chrome DevTools这是主战场。它免费、强大且与执行环境完美集成。Sources面板用于断点调试Network面板用于抓包和查看调用栈Console面板可以实时执行代码片段来测试猜测。代码美化工具Pretty Print在Sources面板中面对被压缩成一行或几行的混淆代码点击左下角的{}按钮可以格式化代码使其恢复一定的可读性如添加换行和缩进。这是静态分析的起点。全局搜索CtrlShiftF在格式化后的代码中搜索关键词如“AES”、“encrypt”、“decrypt”、“CryptoJS”、“mode”、“padding”、“iv”、“key”等可以快速缩小可疑代码的范围。“重写”函数进行Hook这是一种高级技巧。在Console中或通过浏览器插件如Tampermonkey可以重写关键的加密函数。例如拦截CryptoJS.AES.encrypt在其执行前打印出所有参数。这相当于在加密逻辑的必经之路上安装了一个“监控探头”。// 示例Hook CryptoJS.AES.encrypt var originalEncrypt CryptoJS.AES.encrypt; CryptoJS.AES.encrypt function(plaintext, key, cfg) { console.log([HOOK] AES Encrypt Called!); console.log(Plaintext:, plaintext); console.log(Key:, key); console.log(Config:, cfg); // 继续执行原函数 return originalEncrypt.apply(this, arguments); };注意混淆代码可能会将函数名、变量名动态化直接搜索字符串可能失效。此时动态调试和Hook是更可靠的方法。3. 实战拆解从混淆代码到Key/Iv的完整过程下面我以一次模拟的实战为例详细拆解每一步的操作和思考过程。假设我们遇到一个使用CryptoJS进行AES-CBC-Pkcs7加密的页面。3.1 第一步网络抓包与入口定位打开目标网页开启开发者工具的Network面板并勾选Preserve log保留日志。触发数据加密操作比如点击查询按钮。在Network面板中找到携带加密数据的请求通常Request Payload或Form Data里是一长串看似随机的Base64或Hex字符串。点击这个请求查看Headers和Payload。关键一步点击该请求的Initiator标签页这里会显示导致这个请求发生的JavaScript调用栈。调用栈的最顶端通常是XMLHttpRequest.send或fetch往下找找到属于你自己网站域名下的那个脚本文件和行号这很可能就是加密函数被调用后紧接着发起请求的地方。点击这个链接会自动跳转到Sources面板的对应位置。3.2 第二步静态分析与动态断点跳转到Sources面板后你看到的很可能是一坨压缩的代码。首先点击{}进行美化。 在美化后的代码区域在你刚才跳转到的行附近仔细阅读。寻找可能包含加密逻辑的函数。同时使用全局搜索CtrlShiftF搜索“encrypt”。 假设我们找到了一个可疑函数function e(t) { ... }它接收参数t可能是明文然后进行了一系列操作最后返回了一个值这个值被用在了请求参数里。在function e(t)的内部我们可能会看到类似CryptoJS.AES.encrypt(...)的调用或者是一些关于CryptoJS、mode、padding的变量。在包含这行调用的语句前一行打上断点点击行号左侧。3.3 第三步动态调试提取关键参数重新触发加密操作如再次点击按钮。代码执行会在你的断点处暂停。此时右侧的Scope面板会显示当前作用域的所有变量。展开Local或Closure仔细查找。将鼠标悬停在加密函数调用的参数上调试器会显示其当前值。例如悬停在CryptoJS.AES.encrypt(plainText, key, {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7})的key和iv上。关键操作在Console面板中你可以直接输入变量名来查看其具体值。比如输入key和iv。你可能会发现key和iv本身可能是CryptoJS.lib.WordArray对象。这时需要调用其toString()方法查看具体字符串通常是Hex或Base64格式。// 在Console中执行 console.log(Key Hex:, key.toString(CryptoJS.enc.Hex)); console.log(Key Base64:, key.toString(CryptoJS.enc.Base64)); console.log(Iv Hex:, iv.toString(CryptoJS.enc.Hex));记录下这些值。同时注意记录加密模式mode和填充方式padding本例中是CBC和Pkcs7。3.4 第四步参数验证与算法确认仅仅提取出参数还不够必须验证其正确性。构造验证环境在加密触发前想办法让页面加密一个你自己知道的明文比如test123。可以通过在Console中调用那个加密函数e(test123)或者在断点时修改传入的明文参数。记录输出得到对应的密文假设是Base64格式。独立解密使用一个可信的第三方工具进行解密验证。这里以Python为例from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend import base64 # 替换为你提取的Key和Iv (Hex格式) key_hex 你提取的Key的Hex字符串 iv_hex 你提取的Iv的Hex字符串 # 替换为加密得到的密文Base64 ciphertext_b64 加密test123得到的Base64字符串 key bytes.fromhex(key_hex) iv bytes.fromhex(iv_hex) ciphertext base64.b64decode(ciphertext_b64) cipher Cipher(algorithms.AES(key), modes.CBC(iv), backenddefault_backend()) decryptor cipher.decryptor() plaintext_padded decryptor.update(ciphertext) decryptor.finalize() # 移除PKCS7填充 padding_len plaintext_padded[-1] plaintext plaintext_padded[:-padding_len] print(解密结果:, plaintext.decode(utf-8)) # 应该输出 test123如果解密成功恭喜你Key和Iv完全正确。同时你也确认了加密算法是AES-CBC-PKCS7Padding。实操心得有时候Key和Iv并不是硬编码在JS里的而是通过更复杂的方式生成比如由服务器下发的某个令牌Token经过哈希MD5、SHA256后截取固定长度而来。在动态调试时如果找不到直接的Key字符串就要关注其生成过程在生成Key/Iv的函数调用处下断点一步步追踪源头。4. 深度解析AES加密在JS中的常见形态与对抗混淆在实际逆向中情况往往比上面的例子复杂。开发者会采用各种手段增加分析难度。4.1 常见加密库与调用方式CryptoJS这是最常用的前端加密库通常以单独的crypto-js.js文件引入或被打包进主JS。其调用方式比较直观如上例所示。混淆可能会重命名CryptoJS这个全局对象但库内部的核心函数名如AES、encrypt通常不会被改变因为它们是作为对象属性被调用的。搜索AES和encrypt依然有效。Web Crypto API现代浏览器原生支持的加密API标准、性能好。调用方式异步使用crypto.subtle.encrypt。逆向时需要在Promise回调里找key和iv。由于是原生API无法直接Hook其函数名但可以Hookcrypto.subtle或是在生成密钥importKey和加密encrypt的地方下断点。// Web Crypto API 示例片段 const key await crypto.subtle.importKey(...); const iv crypto.getRandomValues(new Uint8Array(16)); const ciphertext await crypto.subtle.encrypt({name: AES-CBC, iv: iv}, key, plaintextData);自定义实现或小众库有些开发者为了“安全”会自己实现AES或者使用冷门库。这增加了识别难度。此时动态调试变得至关重要。你需要关注那些对数据块进行固定长度16字节处理、涉及S盒替换、行移位、列混合等典型AES运算的函数群。4.2 对抗代码混淆与加密的策略变量名混淆将key,iv变成_0x1a2b3c,_0x4d5e6f。应对策略不依赖变量名依赖执行逻辑和值。在调试器中变量的值是不会骗人的。关注传递给加密函数的第二个和第三个参数的实际值。字符串加密将AES、CBC这样的关键字符串也加密存储使用时解密。应对策略在代码执行到使用这些字符串的地方下断点此时内存中已经是解密后的明文可以在调试器中直接看到。代码控制流平坦化将线性的代码逻辑打乱用巨大的switch-case或跳转表来分散执行流程使静态分析几乎失效。应对策略动态调试是唯一出路。通过断点让程序自己“走”出正确的执行路径我们只关心最终汇聚到加密函数调用的那个点。环境检测与反调试代码会检测是否打开了开发者工具如果检测到可能会改变执行逻辑、进入死循环甚至直接崩溃。应对策略可以使用一些浏览器插件来禁用反调试或者更硬核一点直接修改本地保存的JS文件将反调试代码片段删除或注释掉然后刷新页面加载修改后的文件。5. 问题排查与实战技巧实录在实际操作中你一定会遇到各种意想不到的问题。下面是我总结的一些常见坑点和解决技巧。5.1 常见问题速查表问题现象可能原因排查思路与解决方案断点无法触发或瞬间跳过1. 代码被动态生成或eval执行。2. 存在反调试检测到断点后修改了代码或线程。3. 断点位置不对加密逻辑在另一个分支或异步回调中。1. 在(anonymous)或eval代码段设置断点。2. 尝试在开发者工具设置中禁用“Async call stack”或使用“Never pause here”排除无关脚本。3. 在Network请求的Initiator调用栈的每一个环节都尝试下断点。找到的Key/Iv解密失败1. Key/Iv的编码格式不对如提取的是对象未转字符串。2. 加密模式或填充方式判断错误。3. Key/Iv在传输前经过了二次处理如Hex转Base64。4. 存在额外的加密层如先RSA再AES。1. 确认在Console中提取的是字符串值并记录其编码Hex/Base64。2. 仔细查看加密函数配置对象确认mode和padding。3. 跟踪Key/Iv从生成到被使用的全过程看中间是否有转换。4. 分析网络请求看是否有多阶段加密的迹象。搜索不到“AES”、“encrypt”等关键词1. 代码高度混淆关键词被加密或拆分。2. 使用了非常规的加密库或自定义实现。3. 加密逻辑在Web Worker或iframe中。1. 转向动态分析从网络请求的调用栈入手。2. 搜索特征常量如AES的S盒数据0x63, 0x7c...或CBC模式常见的初始化向量长度16字节。3. 检查是否存在new Worker()或iframe并切换到对应上下文进行调试。加密函数被多次调用不知哪个是目标一个页面可能有多处加密如登录密码、查询参数。1. 在断点处查看加密的明文内容是否与你的目标数据相关。2. 对比加密输出的结果是否与网络请求中的密文一致。3. 在加密函数入口处用Hook方法打印调用栈和明文便于区分。5.2 独家避坑技巧“监听”所有加密操作在Console初始化阶段就注入一个通用的Hook脚本拦截所有可能的加密入口。例如Hookwindow.CryptoJS如果存在、crypto.subtle.encrypt甚至XMLHttpRequest.prototype.send和fetch在发送前检查请求体。这样无论加密逻辑藏得多深最终都要通过网络发送在这里一定能抓到“现行”。善用“条件断点”如果加密函数会被频繁调用比如在循环里可以设置条件断点只在满足特定条件时暂停。例如当明文字符串包含某个特定关键词如你的测试数据时才触发断点能极大提高调试效率。保存与修改代码片段在Sources面板可以直接修改当前的JS文件修改仅在当前页面生效然后按CtrlS保存。你可以尝试将混淆的变量名改为有意义的名称或者注释掉可疑的反调试代码。这能让你在后续的调试中获得更清晰的视野。从结果反推如果动态调试非常困难可以尝试“黑盒测试”。收集多组不同的明文和对应的密文可以通过构造不同的请求参数获得。然后使用已知的AES工具进行暴力猜测尝试常见的模式ECB CBC和填充PKCS7 ZeroPadding结合可能的Key生成规律如固定字符串、时间戳哈希等进行碰撞。虽然效率低但在某些简单场景下可能有效。6. 总结与安全思考完成一次完整的JS加密逆向就像完成了一次精细的外科手术。它考验的不仅是技术更是耐心和逻辑推理能力。从网络抓包定位入口到美化代码静态侦查再到动态调试步步追踪最后验证参数完成解密每一步都需要严谨和细致。回过头来看前端加密的本质是一种“防君子不防小人”的防护。它增加了自动化脚本直接调用接口的难度保护了数据传输过程中的隐私防止在浏览器控制台被轻易窥探但对于有心的分析者只要加密逻辑在客户端执行Key和Iv就必然存在暴露的可能。因此真正敏感的业务逻辑和核心密钥永远应该放在后端服务器进行。前端的加密更应被视为一种增加破解成本、提升数据安全基线的手段而非绝对的安全屏障。对于开发者而言了解这些逆向手段也能更好地设计自己的安全策略比如采用动态密钥由后端临时生成、加入请求签名验签、使用非对称加密保护对称密钥等方式来构建更立体的防御体系。安全是一个持续对抗的过程知己知彼方能百战不殆。