1. 项目概述为什么JS逆向绕不开RSA如果你正在学习或者已经接触过Web安全、爬虫或者前端安全审计那么“JS逆向”这个词对你来说一定不陌生。而在这个领域里RSA加密算法就像一座绕不开的大山。无论是登录密码的加密、关键API请求参数的签名还是某些核心业务数据的传输前端JavaScript代码中大量使用RSA来对抗自动化脚本和逆向分析。我见过太多新手朋友面对一个经过RSA加密的请求参数抓耳挠腮不知从何下手。他们能定位到加密函数但面对那一串串看似随机的长字符串密文和复杂的数学运算往往感到无从下手。简单来说JS逆向中的RSA核心目标不是去破解RSA算法本身这在现有计算能力下几乎不可能而是逆向出前端JavaScript是如何调用RSA的从而在本地复现整个加密流程。这包括了找到加密所用的公钥通常是模数N和指数e、理解数据拼接和填充方式如PKCS1_v1_5或OAEP、以及最关键的一步——模拟JavaScript的加密逻辑用Python、Java等后端语言重新实现。这个过程更像是一场“侦探游戏”你需要仔细阅读混淆或压缩过的JS代码找到关键的密钥片段和函数调用链。对于爬虫工程师掌握RSA逆向意味着能突破更高级别的反爬机制对于安全研究员这是分析前端安全漏洞和加密实现弱点的基本功对于前端开发者理解这个过程能帮助你写出更安全的代码。接下来我将以一个典型的实战场景为例带你一步步拆解RSA逆向的全过程分享我踩过的坑和总结出的有效技巧。2. 核心思路拆解定位、分析与复现面对一个使用了RSA加密的网站或应用我们的逆向工作可以系统性地分为三个核心阶段定位加密点、分析加密逻辑、本地化复现。这三个阶段环环相扣缺一不可。2.1 第一阶段精准定位加密发生地一切始于一个被加密的参数。通常你在浏览器的开发者工具F12的网络Network选项卡中会发现某个重要的POST请求其请求体Payload或请求头Headers里包含一长串看似无规律的Base64字符串或十六进制字符串。这就是我们的“猎物”。第一步使用“搜索大法”定位关键代码。全局搜索在开发者工具的源代码Sources面板直接使用CtrlShiftFWindows或CmdOptionFMac进行全局搜索。搜索的内容可以是加密参数名比如你发现了一个叫encryptedData的参数就直接搜它。加密字符串的片段复制密文开头和结尾的几个字符避免因编码问题搜不到进行搜索。关键函数名如encrypt、RSA、public、setPublicKey、JSEncrypt一个常用库等。XHR/断点辅助如果全局搜索效果不佳可以在网络面板中找到那个发送加密请求的XHR/Fetch请求右键选择“Reveal in Sources panel”或类似选项这能直接带你到发起这个网络请求的JavaScript代码附近。然后你可以在该行代码上打上“XHR/Fetch断点”重新触发请求代码执行就会在此处暂停方便你一步步跟踪。实操心得很多网站的加密操作可能在Webpack打包的模块里。这时找到的代码可能是一个数字标识符如(0, i.encrypt)(t)。不要慌继续在附近搜索encrypt或查看该模块的导出定义往往就能找到真正的函数实现位置。2.2 第二阶段深入分析加密逻辑与密钥找到加密函数后真正的挑战才开始。眼前的代码很可能是经过混淆的变量名可能是a,b,c函数名可能是_0x123abc。1. 密钥公钥的寻找RSA加密需要公钥公钥的核心是模数N和指数e。它们通常以以下几种形式存在明文硬编码最理想的情况在JS代码中直接能找到类似setPublicKey(MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...)的调用引号里的长字符串就是Base64编码的公钥通常是PKCS#1或PKCS#8格式。你可以直接复制出来。分段变量拼接N和e可能被拆分成多个字符串或十六进制数散落在代码不同地方最后通过拼接、反转等操作组合起来。你需要仔细跟踪代码执行流找到最终拼接成的字符串。网络请求获取公钥可能由前端在初始化时通过另一个API请求从服务器获取。你需要检查更早的网络请求找到一个返回包含publicKey或modulus、exponent字段的响应。2. 加密库的识别前端常用的RSA库有JSEncrypt、node-rsa浏览器版、forge以及各大厂自研的加密模块。识别出使用的库有助于你理解其默认的填充模式和数据格式。new JSEncrypt()是JSEncrypt库的典型用法。setPublicKey方法也很常见。观察加密函数的输入输出输入是否是原始字符串输出是否是Base64这关系到填充方式。3. 数据预处理分析在调用RSA加密前原始数据如密码、时间戳等往往需要经过一系列处理拼接username | timestamp哈希先对数据进行MD5或SHA256哈希。编码转换将字符串转为ArrayBuffer或特定的字节序列。填充PaddingRSA加密本身要求输入数据长度必须小于密钥长度。因此需要对数据进行填充。常见的填充方案有PKCS#1 v1.5和OAEP。填充方案是复现时最容易出错的地方必须通过代码逻辑或库的默认行为确定。2.3 第三阶段本地化复现加密流程分析清楚后目标是在Python以pycryptodome或cryptography库为例或你使用的其他语言中完全复现前端的加密过程。复现步骤还原公钥将找到的模数N和指数e或者整个公钥PEM字符串在Python中正确构造出公钥对象。还原数据预处理严格按照JS代码的逻辑进行相同的拼接、哈希、编码等操作得到待加密的原始字节数据。应用相同的填充方案进行加密使用与前端相同的填充模式如PKCS1_v1_5进行加密。输出格式转换将加密后的二进制密文按照前端的方式通常是Base64进行编码得到最终的加密字符串。验证成功与否的唯一标准是你用本地代码生成的加密结果与浏览器发送的加密参数完全一致。3. 实战拆解一个模拟登录场景的RSA逆向假设我们遇到一个登录页面其密码字段被加密表单数据如下{ username: testUser, password: oX4fL9k...很长的一段Base64 }我们的目标是逆向这个password的生成过程。3.1 定位与初步分析通过搜索password或加密后的片段我们定位到一个主要的JavaScript文件login.xxxxxx.js。在其中搜索encrypt找到如下关键代码段经过一定美化function encryptPassword(pwd) { var rsa new JSEncrypt(); rsa.setPublicKey(-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN\nFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2XblLPMyNahMBQYz5T4\nI/1n1zF8V6dN1kC4gLThX4QJzVcM6eR5pV5t8vJc7P0Z2ZQIDAQAB\n-----END PUBLIC KEY-----); var encrypted rsa.encrypt(pwd); return encrypted; } // 调用处 var inputPwd document.getElementById(pwd).value; var timestamp Date.now(); var dataToEncrypt inputPwd | timestamp; var finalEncryptedPwd encryptPassword(dataToEncrypt);分析结果使用的库JSEncrypt。公钥直接硬编码在代码中是标准的PEM格式公钥。数据预处理密码明文与当前时间戳用|连接形成待加密字符串。加密与输出调用rsa.encrypt()该库默认使用PKCS#1 v1.5填充并输出Base64编码的字符串。3.2 使用Python进行复现现在我们在Python中复现这一过程。我们将使用pycryptodome库。from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 import time # 1. 准备公钥 (直接从JS代码中复制) public_key_pem -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN FOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2XblLPMyNahMBQYz5T4 I/1n1zF8V6dN1kC4gLThX4QJzVcM6eR5pV5t8vJc7P0Z2ZQIDAQAB -----END PUBLIC KEY----- # 2. 加载公钥 key RSA.import_key(public_key_pem) cipher PKCS1_v1_5.new(key) # 使用与JSEncrypt默认相同的PKCS#1 v1.5填充 # 3. 模拟前端的数据预处理 password_plain MySecretPassword123 timestamp int(time.time() * 1000) # JS的Date.now()是毫秒时间戳 data_to_encrypt f{password_plain}|{timestamp}.encode(utf-8) # 转为bytes # 4. 加密 # 注意PKCS1_v1_5加密的输入数据长度不能超过 (密钥长度/8 - 11)字节。 # 本例密钥1024位128字节所以最大数据长度为 128 - 11 117字节。 encrypted_bytes cipher.encrypt(data_to_encrypt) # 5. 输出Base64 final_encrypted_password base64.b64encode(encrypted_bytes).decode(utf-8) print(f加密后的密码: {final_encrypted_password})运行这段代码将password_plain替换为你想测试的密码生成的final_encrypted_password应该与浏览器发送的加密密码在相同时间戳下完全一致。3.3 处理更复杂的情况分段密钥与自定义填充并非所有网站都如此友好。更常见的情况是这样的var n a5c7d8...很长十六进制; var e 10001; // 十六进制对应十进制65537这是最常用的公钥指数 function customEncrypt(t) { // ... 一系列复杂的位运算和拼接最终调用了某个内部RSA函数 return rsaFunc(t, n, e); }应对策略构造公钥你需要将十六进制的模数n和指数e转换成Python的整数然后构造RSA公钥。from Crypto.PublicKey import RSA import base64 n_hex a5c7d8... e_hex 10001 n_int int(n_hex, 16) e_int int(e_hex, 16) # 构造RSA key对象 key RSA.construct((n_int, e_int)) # 后续加密步骤同上确定填充如果代码中没有明确显示填充方式你需要查阅库文档如果识别出是特定库如forge查看其默认填充。逆向填充函数跟踪rsaFunc内部或之前的代码看是否有对输入数据添加特定前缀如\x00\x02...的步骤这是PKCS#1 v1.5填充的特征。经验猜测与测试最常用的填充是PKCS#1 v1.5。可以先尝试用它复现如果结果对不上再尝试其他填充或无填充极少数情况。4. 核心工具链与调试技巧工欲善其事必先利其器。一套高效的JS逆向工具链能极大提升效率。4.1 浏览器开发者工具进阶用法条件断点Conditional Breakpoint当加密函数被频繁调用如按键事件你可以在该行代码的断点上右键添加条件例如t.indexOf(password) -1这样只有当加密的数据包含“password”时才会暂停避免无效中断。调用栈Call Stack在断点暂停时查看调用栈面板可以清晰地看到函数是如何被一层层调用的帮助你理解加密触发的完整路径。作用域Scope在断点暂停时查看局部作用域和闭包作用域可以直接看到当前函数内所有变量的值这是获取密钥、中间计算结果的黄金时刻。Overrides本地替换在Sources面板的Overrides标签下你可以选择一个本地文件夹然后将线上JS文件保存并映射到本地。之后你可以在本地修改这个JS文件例如添加一些console.log来打印关键变量刷新页面后浏览器会加载你修改过的本地版本。这是动态分析和修改代码逻辑的神器。4.2 辅助工具推荐CryptoJS识别与模拟很多网站使用CryptoJS库进行哈希MD5, SHA256和AES加密。如果你在代码中看到CryptoJS.MD5(...)或CryptoJS.AES.encrypt(...)就需要在Python中对应使用hashlib或pycryptodome库来模拟。注意CryptoJS的AES加密默认输出可能是一个包含盐、密文等信息的特殊对象需要解析其toString()或ciphertext属性。Pythonexecjs库当JS加密逻辑极其复杂涉及大量浏览器环境特有的对象或难以翻译的位操作时可以考虑使用execjs库直接在Python中执行JavaScript代码片段。这相当于一个“降维打击”但会牺牲一些性能和可移植性。import execjs with open(encrypt.js, r, encodingutf-8) as f: js_code f.read() ctx execjs.compile(js_code) result ctx.call(encryptPassword, myPassword, 1234567890) print(result)AST抽象语法树解析工具对于高度混淆的代码可以借助esprima、babel等工具将JS代码解析成AST然后编写脚本进行反混淆比如还原变量名、控制流平坦化等。这是高阶逆向技能入门阶段可以先了解。5. 常见问题排查与避坑指南在实际操作中你一定会遇到各种问题导致本地加密结果与前端不一致。下面是一个常见问题排查清单问题现象可能原因排查思路与解决方案加密结果长度不一致1. 编码方式不同。2. 填充模式不同。3. 公钥不一致。1. 确认JS和Python端对明文、密文的编码UTF-8, ASCII, Hex, Base64每一步都一致。2. 核对填充方案。JSEncrypt默认PKCS#1 v1.5Python的PKCS1_v1_5需对应。3. 检查公钥字符串或(N, e)值是否完全一致注意换行符、头尾标记。加密结果完全不同1. 待加密数据源不同。2. 使用了随机盐或IV多见于混合加密。3. 密钥是动态的。1. 在JS加密函数入口打断点精确捕获传入的原始字符串与Python准备的字符串进行逐字符比对包括不可见字符。2. 检查加密前是否有生成随机数并参与运算。如果是需要将随机数也一并捕获并在Python中固定使用。3. 确认公钥是否是每次页面加载或请求时动态从服务器获取的。Python报错ValueError: Plaintext is too long.待加密数据长度超过了所选填充模式下的最大允许长度。对于1024位密钥的PKCS#1 v1.5最大明文长度为117字节。检查你的明文数据转为bytes后长度。如果超长前端可能采用了分段加密或先哈希再加密的策略。能加密但服务器不认可1. 时间戳等动态参数未同步。2. 请求头如User-Agent, Referer缺失或不对触发了风控。3. 加密逻辑有细微差别如字节序。1. 确保时间戳等动态值与浏览器请求时完全一致。可以尝试硬编码一个成功请求的时间戳来测试。2. 在Python请求中尽量完整模拟浏览器的请求头。3. 使用最笨但最有效的方法在JS加密函数的每一步都打印出中间变量的值字符串、Hex、Base64然后在Python中完全复现每一步进行比对。遇到ReferenceError: window/ document is not defined(在execjs中)JS代码运行在Node.js环境execjs但代码中使用了浏览器特有的BOM对象。1. 在execjs执行前在JS代码全局注入模拟对象如window {}; document {};。2. 更彻底的方法是分析代码到底用这些对象做了什么可能是获取cookie或userAgent然后在Python中直接提供这些值。核心避坑技巧“二分法”调试。不要试图一次性复现整个复杂流程。将过程拆解先确保从相同输入如固定字符串“test”到相同输出。如果不对就在JS代码中在加密函数调用前一刻把输入数据打印出来在Python中用完全相同的数据加密。还不对就打印出JS中加载的公钥的N和e与Python中加载的进行比对。还不对就单独测试填充函数。通过这种逐步缩小范围的方法总能定位到差异点。6. 从RSA到更广阔的前端加密逆向掌握了RSA的逆向你就拿到了打开前端加密世界大门的钥匙。许多更复杂的加密方案都是基于类似原理的叠加对称加密AES/DES关键在于找到密钥Key和初始向量IV。它们可能硬编码、由RSA加密传输、或通过特定算法动态生成。逆向思路同样是定位密钥生成或获取逻辑。哈希与加盐MD5, SHA, HMAC重点是找到盐Salt值和哈希的轮次。盐可能固定、与用户相关或来自服务器。混合加密最常见的是“RSA加密AES密钥AES加密业务数据”。你需要先逆向RSA部分拿到AES密钥再用该密钥解密数据。代码混淆与反调试网站会使用obfuscator等工具混淆代码增加阅读难度还会设置反调试如检测开发者工具打开、无限debugger循环。对付混淆需要耐心和AST工具对付反调试可以禁用断点、使用override功能替换检测代码。逆向的本质是理解。理解开发者的保护思路理解加密库的运作方式理解数据在网络中的生命轨迹。这个过程没有一成不变的公式每一个网站都可能是一个新的谜题。但只要你掌握了本文所述的核心方法论——定位、分析、复现并辅以耐心和细致的调试绝大多数前端加密的壁垒都将被你逐一攻克。记住每一次成功的逆向不仅是获得了一段可用的代码更是对你逻辑思维和解决问题能力的一次锤炼。
JS逆向实战:RSA加密定位、分析与Python复现全解析
1. 项目概述为什么JS逆向绕不开RSA如果你正在学习或者已经接触过Web安全、爬虫或者前端安全审计那么“JS逆向”这个词对你来说一定不陌生。而在这个领域里RSA加密算法就像一座绕不开的大山。无论是登录密码的加密、关键API请求参数的签名还是某些核心业务数据的传输前端JavaScript代码中大量使用RSA来对抗自动化脚本和逆向分析。我见过太多新手朋友面对一个经过RSA加密的请求参数抓耳挠腮不知从何下手。他们能定位到加密函数但面对那一串串看似随机的长字符串密文和复杂的数学运算往往感到无从下手。简单来说JS逆向中的RSA核心目标不是去破解RSA算法本身这在现有计算能力下几乎不可能而是逆向出前端JavaScript是如何调用RSA的从而在本地复现整个加密流程。这包括了找到加密所用的公钥通常是模数N和指数e、理解数据拼接和填充方式如PKCS1_v1_5或OAEP、以及最关键的一步——模拟JavaScript的加密逻辑用Python、Java等后端语言重新实现。这个过程更像是一场“侦探游戏”你需要仔细阅读混淆或压缩过的JS代码找到关键的密钥片段和函数调用链。对于爬虫工程师掌握RSA逆向意味着能突破更高级别的反爬机制对于安全研究员这是分析前端安全漏洞和加密实现弱点的基本功对于前端开发者理解这个过程能帮助你写出更安全的代码。接下来我将以一个典型的实战场景为例带你一步步拆解RSA逆向的全过程分享我踩过的坑和总结出的有效技巧。2. 核心思路拆解定位、分析与复现面对一个使用了RSA加密的网站或应用我们的逆向工作可以系统性地分为三个核心阶段定位加密点、分析加密逻辑、本地化复现。这三个阶段环环相扣缺一不可。2.1 第一阶段精准定位加密发生地一切始于一个被加密的参数。通常你在浏览器的开发者工具F12的网络Network选项卡中会发现某个重要的POST请求其请求体Payload或请求头Headers里包含一长串看似无规律的Base64字符串或十六进制字符串。这就是我们的“猎物”。第一步使用“搜索大法”定位关键代码。全局搜索在开发者工具的源代码Sources面板直接使用CtrlShiftFWindows或CmdOptionFMac进行全局搜索。搜索的内容可以是加密参数名比如你发现了一个叫encryptedData的参数就直接搜它。加密字符串的片段复制密文开头和结尾的几个字符避免因编码问题搜不到进行搜索。关键函数名如encrypt、RSA、public、setPublicKey、JSEncrypt一个常用库等。XHR/断点辅助如果全局搜索效果不佳可以在网络面板中找到那个发送加密请求的XHR/Fetch请求右键选择“Reveal in Sources panel”或类似选项这能直接带你到发起这个网络请求的JavaScript代码附近。然后你可以在该行代码上打上“XHR/Fetch断点”重新触发请求代码执行就会在此处暂停方便你一步步跟踪。实操心得很多网站的加密操作可能在Webpack打包的模块里。这时找到的代码可能是一个数字标识符如(0, i.encrypt)(t)。不要慌继续在附近搜索encrypt或查看该模块的导出定义往往就能找到真正的函数实现位置。2.2 第二阶段深入分析加密逻辑与密钥找到加密函数后真正的挑战才开始。眼前的代码很可能是经过混淆的变量名可能是a,b,c函数名可能是_0x123abc。1. 密钥公钥的寻找RSA加密需要公钥公钥的核心是模数N和指数e。它们通常以以下几种形式存在明文硬编码最理想的情况在JS代码中直接能找到类似setPublicKey(MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...)的调用引号里的长字符串就是Base64编码的公钥通常是PKCS#1或PKCS#8格式。你可以直接复制出来。分段变量拼接N和e可能被拆分成多个字符串或十六进制数散落在代码不同地方最后通过拼接、反转等操作组合起来。你需要仔细跟踪代码执行流找到最终拼接成的字符串。网络请求获取公钥可能由前端在初始化时通过另一个API请求从服务器获取。你需要检查更早的网络请求找到一个返回包含publicKey或modulus、exponent字段的响应。2. 加密库的识别前端常用的RSA库有JSEncrypt、node-rsa浏览器版、forge以及各大厂自研的加密模块。识别出使用的库有助于你理解其默认的填充模式和数据格式。new JSEncrypt()是JSEncrypt库的典型用法。setPublicKey方法也很常见。观察加密函数的输入输出输入是否是原始字符串输出是否是Base64这关系到填充方式。3. 数据预处理分析在调用RSA加密前原始数据如密码、时间戳等往往需要经过一系列处理拼接username | timestamp哈希先对数据进行MD5或SHA256哈希。编码转换将字符串转为ArrayBuffer或特定的字节序列。填充PaddingRSA加密本身要求输入数据长度必须小于密钥长度。因此需要对数据进行填充。常见的填充方案有PKCS#1 v1.5和OAEP。填充方案是复现时最容易出错的地方必须通过代码逻辑或库的默认行为确定。2.3 第三阶段本地化复现加密流程分析清楚后目标是在Python以pycryptodome或cryptography库为例或你使用的其他语言中完全复现前端的加密过程。复现步骤还原公钥将找到的模数N和指数e或者整个公钥PEM字符串在Python中正确构造出公钥对象。还原数据预处理严格按照JS代码的逻辑进行相同的拼接、哈希、编码等操作得到待加密的原始字节数据。应用相同的填充方案进行加密使用与前端相同的填充模式如PKCS1_v1_5进行加密。输出格式转换将加密后的二进制密文按照前端的方式通常是Base64进行编码得到最终的加密字符串。验证成功与否的唯一标准是你用本地代码生成的加密结果与浏览器发送的加密参数完全一致。3. 实战拆解一个模拟登录场景的RSA逆向假设我们遇到一个登录页面其密码字段被加密表单数据如下{ username: testUser, password: oX4fL9k...很长的一段Base64 }我们的目标是逆向这个password的生成过程。3.1 定位与初步分析通过搜索password或加密后的片段我们定位到一个主要的JavaScript文件login.xxxxxx.js。在其中搜索encrypt找到如下关键代码段经过一定美化function encryptPassword(pwd) { var rsa new JSEncrypt(); rsa.setPublicKey(-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN\nFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2XblLPMyNahMBQYz5T4\nI/1n1zF8V6dN1kC4gLThX4QJzVcM6eR5pV5t8vJc7P0Z2ZQIDAQAB\n-----END PUBLIC KEY-----); var encrypted rsa.encrypt(pwd); return encrypted; } // 调用处 var inputPwd document.getElementById(pwd).value; var timestamp Date.now(); var dataToEncrypt inputPwd | timestamp; var finalEncryptedPwd encryptPassword(dataToEncrypt);分析结果使用的库JSEncrypt。公钥直接硬编码在代码中是标准的PEM格式公钥。数据预处理密码明文与当前时间戳用|连接形成待加密字符串。加密与输出调用rsa.encrypt()该库默认使用PKCS#1 v1.5填充并输出Base64编码的字符串。3.2 使用Python进行复现现在我们在Python中复现这一过程。我们将使用pycryptodome库。from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 import time # 1. 准备公钥 (直接从JS代码中复制) public_key_pem -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN FOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2XblLPMyNahMBQYz5T4 I/1n1zF8V6dN1kC4gLThX4QJzVcM6eR5pV5t8vJc7P0Z2ZQIDAQAB -----END PUBLIC KEY----- # 2. 加载公钥 key RSA.import_key(public_key_pem) cipher PKCS1_v1_5.new(key) # 使用与JSEncrypt默认相同的PKCS#1 v1.5填充 # 3. 模拟前端的数据预处理 password_plain MySecretPassword123 timestamp int(time.time() * 1000) # JS的Date.now()是毫秒时间戳 data_to_encrypt f{password_plain}|{timestamp}.encode(utf-8) # 转为bytes # 4. 加密 # 注意PKCS1_v1_5加密的输入数据长度不能超过 (密钥长度/8 - 11)字节。 # 本例密钥1024位128字节所以最大数据长度为 128 - 11 117字节。 encrypted_bytes cipher.encrypt(data_to_encrypt) # 5. 输出Base64 final_encrypted_password base64.b64encode(encrypted_bytes).decode(utf-8) print(f加密后的密码: {final_encrypted_password})运行这段代码将password_plain替换为你想测试的密码生成的final_encrypted_password应该与浏览器发送的加密密码在相同时间戳下完全一致。3.3 处理更复杂的情况分段密钥与自定义填充并非所有网站都如此友好。更常见的情况是这样的var n a5c7d8...很长十六进制; var e 10001; // 十六进制对应十进制65537这是最常用的公钥指数 function customEncrypt(t) { // ... 一系列复杂的位运算和拼接最终调用了某个内部RSA函数 return rsaFunc(t, n, e); }应对策略构造公钥你需要将十六进制的模数n和指数e转换成Python的整数然后构造RSA公钥。from Crypto.PublicKey import RSA import base64 n_hex a5c7d8... e_hex 10001 n_int int(n_hex, 16) e_int int(e_hex, 16) # 构造RSA key对象 key RSA.construct((n_int, e_int)) # 后续加密步骤同上确定填充如果代码中没有明确显示填充方式你需要查阅库文档如果识别出是特定库如forge查看其默认填充。逆向填充函数跟踪rsaFunc内部或之前的代码看是否有对输入数据添加特定前缀如\x00\x02...的步骤这是PKCS#1 v1.5填充的特征。经验猜测与测试最常用的填充是PKCS#1 v1.5。可以先尝试用它复现如果结果对不上再尝试其他填充或无填充极少数情况。4. 核心工具链与调试技巧工欲善其事必先利其器。一套高效的JS逆向工具链能极大提升效率。4.1 浏览器开发者工具进阶用法条件断点Conditional Breakpoint当加密函数被频繁调用如按键事件你可以在该行代码的断点上右键添加条件例如t.indexOf(password) -1这样只有当加密的数据包含“password”时才会暂停避免无效中断。调用栈Call Stack在断点暂停时查看调用栈面板可以清晰地看到函数是如何被一层层调用的帮助你理解加密触发的完整路径。作用域Scope在断点暂停时查看局部作用域和闭包作用域可以直接看到当前函数内所有变量的值这是获取密钥、中间计算结果的黄金时刻。Overrides本地替换在Sources面板的Overrides标签下你可以选择一个本地文件夹然后将线上JS文件保存并映射到本地。之后你可以在本地修改这个JS文件例如添加一些console.log来打印关键变量刷新页面后浏览器会加载你修改过的本地版本。这是动态分析和修改代码逻辑的神器。4.2 辅助工具推荐CryptoJS识别与模拟很多网站使用CryptoJS库进行哈希MD5, SHA256和AES加密。如果你在代码中看到CryptoJS.MD5(...)或CryptoJS.AES.encrypt(...)就需要在Python中对应使用hashlib或pycryptodome库来模拟。注意CryptoJS的AES加密默认输出可能是一个包含盐、密文等信息的特殊对象需要解析其toString()或ciphertext属性。Pythonexecjs库当JS加密逻辑极其复杂涉及大量浏览器环境特有的对象或难以翻译的位操作时可以考虑使用execjs库直接在Python中执行JavaScript代码片段。这相当于一个“降维打击”但会牺牲一些性能和可移植性。import execjs with open(encrypt.js, r, encodingutf-8) as f: js_code f.read() ctx execjs.compile(js_code) result ctx.call(encryptPassword, myPassword, 1234567890) print(result)AST抽象语法树解析工具对于高度混淆的代码可以借助esprima、babel等工具将JS代码解析成AST然后编写脚本进行反混淆比如还原变量名、控制流平坦化等。这是高阶逆向技能入门阶段可以先了解。5. 常见问题排查与避坑指南在实际操作中你一定会遇到各种问题导致本地加密结果与前端不一致。下面是一个常见问题排查清单问题现象可能原因排查思路与解决方案加密结果长度不一致1. 编码方式不同。2. 填充模式不同。3. 公钥不一致。1. 确认JS和Python端对明文、密文的编码UTF-8, ASCII, Hex, Base64每一步都一致。2. 核对填充方案。JSEncrypt默认PKCS#1 v1.5Python的PKCS1_v1_5需对应。3. 检查公钥字符串或(N, e)值是否完全一致注意换行符、头尾标记。加密结果完全不同1. 待加密数据源不同。2. 使用了随机盐或IV多见于混合加密。3. 密钥是动态的。1. 在JS加密函数入口打断点精确捕获传入的原始字符串与Python准备的字符串进行逐字符比对包括不可见字符。2. 检查加密前是否有生成随机数并参与运算。如果是需要将随机数也一并捕获并在Python中固定使用。3. 确认公钥是否是每次页面加载或请求时动态从服务器获取的。Python报错ValueError: Plaintext is too long.待加密数据长度超过了所选填充模式下的最大允许长度。对于1024位密钥的PKCS#1 v1.5最大明文长度为117字节。检查你的明文数据转为bytes后长度。如果超长前端可能采用了分段加密或先哈希再加密的策略。能加密但服务器不认可1. 时间戳等动态参数未同步。2. 请求头如User-Agent, Referer缺失或不对触发了风控。3. 加密逻辑有细微差别如字节序。1. 确保时间戳等动态值与浏览器请求时完全一致。可以尝试硬编码一个成功请求的时间戳来测试。2. 在Python请求中尽量完整模拟浏览器的请求头。3. 使用最笨但最有效的方法在JS加密函数的每一步都打印出中间变量的值字符串、Hex、Base64然后在Python中完全复现每一步进行比对。遇到ReferenceError: window/ document is not defined(在execjs中)JS代码运行在Node.js环境execjs但代码中使用了浏览器特有的BOM对象。1. 在execjs执行前在JS代码全局注入模拟对象如window {}; document {};。2. 更彻底的方法是分析代码到底用这些对象做了什么可能是获取cookie或userAgent然后在Python中直接提供这些值。核心避坑技巧“二分法”调试。不要试图一次性复现整个复杂流程。将过程拆解先确保从相同输入如固定字符串“test”到相同输出。如果不对就在JS代码中在加密函数调用前一刻把输入数据打印出来在Python中用完全相同的数据加密。还不对就打印出JS中加载的公钥的N和e与Python中加载的进行比对。还不对就单独测试填充函数。通过这种逐步缩小范围的方法总能定位到差异点。6. 从RSA到更广阔的前端加密逆向掌握了RSA的逆向你就拿到了打开前端加密世界大门的钥匙。许多更复杂的加密方案都是基于类似原理的叠加对称加密AES/DES关键在于找到密钥Key和初始向量IV。它们可能硬编码、由RSA加密传输、或通过特定算法动态生成。逆向思路同样是定位密钥生成或获取逻辑。哈希与加盐MD5, SHA, HMAC重点是找到盐Salt值和哈希的轮次。盐可能固定、与用户相关或来自服务器。混合加密最常见的是“RSA加密AES密钥AES加密业务数据”。你需要先逆向RSA部分拿到AES密钥再用该密钥解密数据。代码混淆与反调试网站会使用obfuscator等工具混淆代码增加阅读难度还会设置反调试如检测开发者工具打开、无限debugger循环。对付混淆需要耐心和AST工具对付反调试可以禁用断点、使用override功能替换检测代码。逆向的本质是理解。理解开发者的保护思路理解加密库的运作方式理解数据在网络中的生命轨迹。这个过程没有一成不变的公式每一个网站都可能是一个新的谜题。但只要你掌握了本文所述的核心方法论——定位、分析、复现并辅以耐心和细致的调试绝大多数前端加密的壁垒都将被你逐一攻克。记住每一次成功的逆向不仅是获得了一段可用的代码更是对你逻辑思维和解决问题能力的一次锤炼。