Web Crypto API实战:AES-CBC加密逆向分析与Node.js复现

发布时间:2026/7/1 4:44:28
Web Crypto API实战:AES-CBC加密逆向分析与Node.js复现
1. 项目概述一次教科书式的AES逆向分析最近在分析一些教育类应用的登录流程时遇到了“升学e网通”这个案例。它的密码加密方式让我眼前一亮——这竟然是一个相当标准的AES加密实现。在如今各种魔改、混淆、自定义算法横行的逆向环境中能遇到一个规规矩矩遵循标准协议的加密简直像在沙漠里找到了一片绿洲。对于逆向新手来说这无疑是一个绝佳的练手材料因为它剥离了那些令人头疼的“黑盒”操作让你能清晰地看到数据从明文到密文的完整、标准的转换过程。这个案例的核心就是定位到其前端JavaScript代码中调用Web Crypto API进行AES-CBC加密的逻辑并完整复现其加密过程最终实现密码的模拟加密。整个过程涉及前端代码调试、加密参数提取和算法复现三个关键环节。2. 逆向环境与目标分析2.1 目标网站与工具准备我们的目标是“升学e网通”的登录页面。在开始之前需要准备好一套顺手的逆向工具链。浏览器自然是核心战场Chrome或Edge的开发者工具是首选其Sources面板和Network面板将是我们主要的侦察阵地。为了更高效地调试JavaScript特别是处理经过压缩或混淆的代码一个格式化工具Pretty Print是必不可少的。此外因为涉及加密操作我们需要一个能够执行JavaScript代码片段的环境浏览器的Console面板就很好用但对于更复杂的复现也可以准备一个Node.js环境。最后为了验证我们的复现结果一个能够发送HTTP请求的工具如Postman或Hoppscotch也会很有帮助。2.2 登录流程初探与加密定位打开登录页面输入账号密码请务必使用测试账号点击登录前先打开开发者工具的Network面板并勾选“Preserve log”。点击登录后你会看到浏览器发起了一个登录请求。重点关注这个请求的Request Payload你会发现密码字段通常叫password已经不再是明文而是一长串看似随机的字符这证实了密码在客户端被加密了。我们的任务就是找到生成这串字符的JavaScript代码。通常加密函数会在点击登录按钮时被触发。我们可以在Sources面板中对可能的按钮点击事件监听器或者表单提交事件打上断点然后再次点击登录让代码执行暂停在加密发生之前。另一种更直接的方法是在Network面板中找到那个登录请求右键点击它选择“Copy - Copy as cURL”然后粘贴到一个文本编辑器中搜索password这个关键词看看它的值是什么样子这能给我们一个关于密文格式比如是否是Base64编码的初步印象。3. 核心加密逻辑的定位与解析3.1 搜索与追踪加密入口点在庞大的前端代码库中寻找加密函数需要一些技巧。最直接的方法是在Sources面板中对所有已加载的JavaScript文件进行全局搜索。搜索关键词可以尝试“encrypt”、“AES”、“CBC”、“password”、“crypto”等。在“升学e网通”的案例中通过搜索“encrypt”或“AES”我们很快就能定位到关键代码段。这些代码通常不会深度混淆因为Web Crypto API的使用本身比较标准。找到疑似函数后在其第一行打上断点然后再次触发登录操作。代码执行会在此处暂停这时我们就能进入这个函数内部一步步观察它如何工作。3.2 标准AES-CBC参数剖析当断点命中后我们进入函数内部单步执行F11。关键的发现就在这里代码中明确使用了crypto.subtle.encrypt这个API。这是现代浏览器提供的用于执行底层加密操作的Web Cryptography API接口。我们需要在调试器中仔细观察传递给这个函数的参数。一个标准的AES-CBC加密调用通常如下所示const encrypted await crypto.subtle.encrypt( { name: AES-CBC, // 算法模式 iv: ivArrayBuffer, // 初始化向量 }, keyObject, // 加密密钥 plaintextArrayBuffer // 明文数据 );从这里我们必须提取出三个最关键的参数密钥 (Key)这是加密解密的根本。它可能是一个固定的字符串硬编码也可能是从某个接口动态获取的或者是通过某种算法如PBKDF2从用户密码派生而来。在调试器中找到keyObject变量的来源至关重要。初始化向量 (IV, Initialization Vector)CBC模式必需的参数一个随机或固定的字节序列用于确保相同的明文加密每次产生不同的密文。我们需要找到ivArrayBuffer的值。明文数据 (Plaintext)即用户输入的原始密码。我们需要确认在加密前密码是否经过了其他处理比如添加了盐值salt或者进行了特定的编码转换。在“升学e网通”的实例中经过调试发现其密钥是固定值通常是一个字符串如“某固定密钥”并通过TextEncoder转换为ArrayBuffer。IV也是一个固定值。这正是它“标准”和“简单”的地方——所有加密要素都是静态可知的没有动态协商或复杂变换。注意在调试时对于ArrayBuffer类型的数据直接console.log打印出来是看不到具体内容的。你需要将其转换为十六进制字符串或Base64字符串来查看。例如在Console中执行btoa(String.fromCharCode(...new Uint8Array(ivArrayBuffer)))可以将ArrayBuffer转为Base64查看。3.3 数据编码与传输格式确认crypto.subtle.encrypt返回的结果也是一个ArrayBuffer。前端需要将这个二进制密文转换为字符串才能通过网络传输。常见的转换方式是Base64编码。我们需要在代码中紧接着加密函数之后寻找类似btoa、Buffer.from().toString(base64)或者引入的第三方Base64库的调用。在“升学e网通”中加密后的ArrayBuffer被直接转换为Base64字符串然后作为password字段的值发送。这一步的确认保证了我们复现算法后输出格式与目标完全一致。4. 加密算法的完整复现4.1 复现环境选择Node.js既然我们已经在前端浏览器环境中搞清了所有参数和步骤现在就需要在一个独立的环境比如后端或脚本中复现这个加密过程。Node.js是一个完美选择因为它内置了crypto模块功能强大且与Web Crypto API有一定相似性。我们将使用Node.js的crypto模块来重写加密函数。4.2 关键参数提取与验证首先将我们在浏览器调试中抓取到的关键参数记录下来密钥明文假设我们从代码中看到密钥字符串是“this_is_a_secret_key”。IV明文假设我们看到IV是“initial_vector_iv”。算法细节AES-CBC密钥长度可能是128、192或256位。这通常由密钥字符串的长度决定。一个16字节128位的密钥很常见。这里有一个非常重要的步骤验证这些参数的正确性。我们可以写一个简单的Node.js测试脚本用这些参数加密一个已知的密码比如“123456”然后将输出的Base64密文与我们在浏览器Network里捕获到的、用相同密码登录时产生的密文进行比对。如果一致恭喜你参数正确如果不一致就需要回头检查密钥或IV的提取是否正确或者密码在加密前是否被做了其他处理例如是否在密码前后拼接了其他字符串。4.3 Node.js 复现代码详解以下是一个完整的Node.js复现代码示例包含了详细的注释const crypto require(crypto); function encryptPassword(password) { // 1. 定义固定的密钥和IV此处为示例需替换为实际抓取的值 const KEY_STRING this_is_a_secret_key; // 替换为实际密钥 const IV_STRING initial_vector_iv; // 替换为实际IV // 2. 处理密钥AES-128-CBC要求密钥长度为16字节。 // 如果密钥字符串不是16字节需要进行处理。常见方法是取MD5哈希值的前16位或者直接使用字符串的字节表示如果刚好16字节。 // 假设我们的密钥字符串就是16字节则直接使用。 const key Buffer.from(KEY_STRING, utf-8); // 将字符串转为Buffer // 如果密钥长度不足16字节可以填充如果超过可以截取。这里假设KEY_STRING设计时就是16字节。 if (key.length ! 16) { console.warn(密钥长度(${key.length}字节)非标准16字节可能需特殊处理。); // 一种常见做法使用密钥字符串的MD5值作为密钥16字节 // const key crypto.createHash(md5).update(KEY_STRING).digest(); } // 3. 处理IVCBC模式要求IV为16字节。 const iv Buffer.from(IV_STRING, utf-8); if (iv.length ! 16) { console.warn(IV长度(${iv.length}字节)非标准16字节可能需特殊处理。); // 同样可以截取或填充至16字节。例如用0填充const iv Buffer.alloc(16, 0); iv.write(IV_STRING); } // 4. 创建Cipher对象指定算法为aes-128-cbc使用上文的key和iv。 const cipher crypto.createCipheriv(aes-128-cbc, key, iv); // 5. 执行加密更新传入明文并最终化获取全部密文。 let encrypted cipher.update(password, utf8, base64); // 输入编码utf8输出编码base64 encrypted cipher.final(base64); // 完成加密追加剩余输出 // 6. 返回Base64格式的密文。 return encrypted; } // 测试 const testPassword my_password_123; const encryptedPassword encryptPassword(testPassword); console.log(明文密码:, testPassword); console.log(加密后(Base64):, encryptedPassword);代码关键点解析crypto.createCipheriv这是核心函数用于创建加密器。aes-128-cbc指定了算法和模式。如果密钥是24字节或32字节则对应aes-192-cbc或aes-256-cbc。cipher.update和cipher.final这是流式加密的典型用法。update可以分块处理数据final输出最后一块并清理资源。对于密码这种短数据一次update接final即可。编码一致性update方法的第二个参数是输入编码‘utf8’第三个参数是输出编码‘base64’。这必须与前端加密时的处理方式一致。前端如果明文是字符串通常也是UTF-8编码输出如果是Base64这里就选Base64。4.4 复现结果验证与抓包比对运行上述Node.js脚本得到一个Base64加密字符串。然后打开浏览器在“升学e网通”登录页面输入你在Node.js脚本中使用的相同测试密码my_password_123抓取登录请求。对比抓包数据中的password字段值和你脚本输出的值。如果两者完全一致那么你的逆向复现就成功了。这意味着你完全掌握了它的加密逻辑可以在任何需要模拟登录的地方使用这个加密函数了。5. 逆向过程中的常见问题与深度思考5.1 典型问题排查清单即使面对如此标准的加密在逆向过程中也可能遇到一些坑。下面是一个快速排查表问题现象可能原因排查思路与解决方案Node.js加密结果与浏览器抓包结果不一致1. 密钥/IV提取错误或编码不一致。2. 密码明文在加密前被预处理如加盐、拼接。3. AES密钥长度或模式不匹配如误用ECB模式。4. 输出编码不一致如浏览器是Base64 URL Safe。1.核对参数在浏览器调试态将密钥、IV的ArrayBuffer转为Hex或Base64与Node.js脚本中使用的Buffer进行严格比对。2.追踪明文在加密函数入口打断点查看即将被加密的明文数据到底是什么确认是否就是原始密码字符串。3.检查算法确认浏览器crypto.subtle.encrypt的name参数确实是”AES-CBC”并确认密钥字节数对应的AES变体128/192/256。4.检查编码对比抓包密文和Node.js输出看是否存在/-和//_的替换这是Base64 URL Safe的特征。无法在代码中搜索到“encrypt”、“AES”等关键词1. 代码被重度混淆函数名变量名被替换。2. 加密逻辑被封装在Web Worker或异步加载的模块中。3. 使用了非标准的加密库如CryptoJS。1.格式化与搜索使用开发者工具的格式化功能然后搜索更底层的API名如”subtle.encrypt”、”CBC”。2.网络包搜索在Network面板搜索所有加载的JS文件内容Chrome支持在Network面板下直接搜索文件内容。3.事件监听在登录按钮上查看事件监听器找到对应的处理函数逐步跟进。密钥或IV看起来是动态的密钥或IV可能从服务器接口获取或由前端代码动态生成。1.Network搜索在登录前的网络请求中寻找可能返回密钥或IV的接口。2.代码生成逻辑如果动态生成需逆向生成算法如从某个固定字符串派生。此时标准AES的“简单”部分可能就不复存在了。5.2 关于“标准”加密的思考与安全启示“升学e网通”采用标准AES-CBC加密从逆向学习角度是福音但从安全角度却暴露了问题。固定密钥和固定IV是安全大忌。这意味着所有用户的密码密文在已知明文攻击下是脆弱的。一旦攻击者通过某种方式知道了密钥所有历史通信的密码都可能被解密。更佳的做法应该是使用HTTPS这是基础确保传输过程安全。非对称加密前端使用公钥加密密码后端用私钥解密。这样前端无需存储密钥。动态密钥协商如使用ECDH等密钥交换协议。至少使用随机IV即使使用对称加密每次加密使用随机IV能保证相同明文加密结果不同。作为开发者在设计系统时应避免将对称加密的密钥硬编码在前端代码中。作为安全研究人员或逆向学习者这个案例则清晰地展示了“标准”不等于“安全”。逆向工程不仅能帮助我们理解逻辑、实现自动化更能让我们从攻击者的视角审视系统脆弱点这对于构建更健壮的系统至关重要。5.3 从该案例延伸的逆向学习路径掌握了这个标准AES案例后你可以尝试挑战更复杂的场景魔改AES遇到密钥不是直接使用而是经过一个自定义函数变换或者AES的S盒被替换。RSA加密定位前端公钥学习如何用Node.js的crypto模块进行RSA加密。哈希加盐密码不是加密而是进行MD5(password salt)或SHA256哈希需要找到salt的生成逻辑。代码混淆对抗当核心函数名被混淆成a0b1c2时如何通过调用栈、参数类型和网络行为来定位关键函数。每一次逆向都是一次解谜。从简单的、标准的算法入手建立信心和方法论再逐步攻克更复杂的堡垒这才是技术能力提升的扎实路径。这个“升学e网通”的案例无疑是一个近乎完美的起点。