☰
JS逆向入门:从标准加密算法到爬虫实战,一次讲透
2026/10/14 15:03:09 网站建设 项目流程

先聊几句题外话。很多刚开始接触爬虫的同学,常常会卡在同一个地方:请求头明明照着浏览器填了,Cookie 也带了,可服务器还是返回403或者data: [...]这种看不懂的密文。这个时候,大概率是遇到了JS 逆向问题——数据在浏览器端被 JavaScript 加密,服务器也只认加密后的结果。网上关于 JS 逆向的资料虽然多,但大多零散,要么只讲一个算法,要么直接扔一段扣好的代码,让人知其然不知其所以然。

所以我整理了这一篇以“标准算法 + 扣代码思路 + 完整实战”为主线的文章,把 JS 逆向里最常用的加密算法、定位方法、断点技巧、Python 还原实现都串起来。文章不会只堆代码,而是会解释每个案例的思考过程,方便你遇到新网站时能自己分析。内容偏基础,也适合刚入门想系统学习爬虫逆向的朋友。

1. JS 逆向到底在做什么

1.1 什么是 JS 逆向

JS 逆向,简单来说,就是分析网站前端 JavaScript 代码,弄清楚它对请求参数、请求头、响应数据做了哪些处理,然后把这些处理逻辑用 Python 或其他语言重新实现一遍,从而让脚本能够模拟浏览器行为,拿到服务器返回的真实数据。

举个例子。某个网站登录时,浏览器地址栏里看到的是:

POST https://example.com/api/login

Payload 是:

{ "username": "admin", "password": "a1b2c3..." }

但你直接写 Python 代码提交明文密码,服务器会提示“密码错误”。打开浏览器开发者工具,在 Sources 里搜一下,就会发现页面里有一段 JS,做了类似这样的操作:

password = md5(password + salt);

所以,真实请求里提交的 password 并不是明文,而是“明文 + 盐值”后的 MD5 值。JS 逆向要做的,就是找到这个salt和加密函数,然后在 Python 里复现。

1.2 JS 逆向的三大应用方向

概括起来,目前 Web 端的 JS 逆向主要分为三类:

方向说明常见场景
请求参数加密对提交的参数做签名、加密登录密码、搜索参数、分页参数
请求头加密对请求头做签名或动态生成反爬 Token、时间戳签名、Headers 校验
响应数据加密服务器返回密文,前端解密后渲染价格接口、订单接口、搜索列表

这三个方向并不互斥,很多网站会同时使用。例如先对参数做签名(MD5/SHA256),再对整个请求体做 AES 加密,响应数据再用 RSA 解密。这也是为什么我建议新手先把标准算法掌握熟练,再去研究魔改算法。

1.3 需要掌握的基础工具

正式进入案例之前,建议先把工具链准备好:

  • Python 3.8+:推荐 3.9 或 3.10,新项目尽量不用 2.x。
  • Node.js:用于运行、调试 JavaScript 代码,尤其是扣下来的加密函数。
  • 浏览器开发者工具:Chrome 或 Edge 的 F12 面板。
  • Charles / Fiddler:抓包工具,用于移动端或桌面端应用。
  • Burp Suite:用于分析 HTTP 请求、重放请求,尤其在自测接口时很有用。
  • Postman / Apifox:用于接口调试。

Python 环境中,常用到的库包括:

pip install requests pip install pycryptodome pip install execjs pip install nodejs

说明:execjs可以在 Python 中直接执行 JavaScript 代码,但性能一般。实际生产环境中更推荐“用 Python 重写算法”或“直接调用 Node.js 子进程”。

2. 爬虫入门:先会写一个标准 requests 请求

很多同学一上来就想搞复杂的 RSA、AES,但真正到项目里,你会发现 60% 的网站其实只做了最简单的参数校验,甚至连加密都没有。所以先掌握标准爬虫请求的写法非常重要。

2.1 环境准备

操作环境以 Windows / macOS 为例,Python 版本建议 3.9 以上。先确认环境:

python --version pip --version

如果还没有安装 requests,执行:

pip install requests

2.2 最小请求示例

我们以常用的requests库为例,写一个最基本的 GET 请求:

import requests url = "https://httpbin.org/get" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } resp = requests.get(url, headers=headers, timeout=10) print(resp.status_code) print(resp.text)

这是一个非常简单的请求,但已经包含了两个核心点:

  • 设置User-Agent,避免被一些基础反爬拦截。
  • 设置timeout,防止请求卡死。

2.3 带参数的 POST 请求

实际爬虫中,更多请求是 POST 类型,需要传表单或 JSON 数据。

import requests url = "https://httpbin.org/post" data = { "username": "admin", "password": "123456" } resp = requests.post(url, data=data, timeout=10) print(resp.json())

如果网站要求提交 JSON,使用json参数:

resp = requests.post(url, json=data, timeout=10)

需要注意的是,data与json的编码方式不同,服务器解析方式也不同,具体要看抓包工具里原始请求的Content-Type。

2.4 Session 保持会话

涉及登录的网站,通常需要携带 Cookie。用requests.Session()可以自动管理 Cookie:

import requests session = requests.Session() login_url = "https://example.com/api/login" login_data = { "username": "admin", "password": "123456" } session.post(login_url, data=login_data) profile_url = "https://example.com/api/profile" resp = session.get(profile_url) print(resp.text)

Session对象会自动保存服务端返回的 Cookie,并在后续请求中携带,这能减少很多手动处理 Cookie 的麻烦。

这一步做完后,你会发现大部分“无需加密”的网站已经能正常爬取了。接下来我们再进入逆向的核心场景。

3. 逆向前的准备:定位加密位置

3.1 通过抓包确认加密参数

假设你遇到了一个需要加密参数的接口。第一步永远是打开浏览器开发者工具——Network 面板,重新触发一次请求,找到对应的 XHR 请求。

例如,某网站搜索接口:

Request URL: https://example.com/api/search Request Method: POST Payload: { "keyword": "手机", "sign": "b2f0c9d1e8a74b6f9d3e..." }

肉眼观察,keyword是明文,但sign是一串十六进制字符。这个sign很可能就是加密签名。接下来要做的,就是找到sign是怎么生成的。

3.2 全局搜索关键词

按Ctrl+Shift+F,在开发者工具的 Search 面板里搜索sign关键字,通常能找到相关函数。如果网站过了混淆,搜索不到,还可以尝试搜索:

  • 加密特征值:md5、sha1、sha256、AES、RSA、encrypt
  • 算法特征库:CryptoJS、jsencrypt、bcrypt
  • 参数名特征:sign、token、key、secret

以 CryptoJS 为例,很多网站会直接在 HTML 里引入:

<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.js"></script>

这就意味着,你能直接在全局对象上找到CryptoJS。

3.3 通过 XHR 断点定位调用栈

如果全局搜索找不到,可以用 XHR 断点:

  1. 在 Sources 面板右侧,找到XHR/fetch Breakpoints。
  2. 点击加号,输入目标接口的 URL 片段,比如api/search。
  3. 刷新页面或重新触发请求,浏览器会在发起请求的那一行 JS 自动暂停。
  4. 在 Call Stack 面板中,层层向上查看调用栈,找到参数生成的位置。

这是目前最常用、也最不容易误判的定位方式。很多复杂的混淆网站,靠搜索关键词是搜不出结果的,但断点一定能触发。

3.4 分清“算法加密”和“环境校验”

在定位到加密函数后,不要急着扣代码。先判断一下,加密函数是纯粹的标准算法,还是依赖了浏览器环境。

  • 标准算法:如 MD5、SHA256、AES、RSA,这些是纯数学运算,不依赖浏览器环境,可以直接用 Python 或 Node.js 重写。
  • 环境校验:如navigator.userAgent、window、document、canvas指纹,这些值在 Python 里并不存在,需要特殊处理,例如用execjs注入 mock 环境,或者扣取完整代码后手动补环境。

新手建议先从“标准算法”入手,这类案例占比很高,也最容易获得成就感。

4. 标准算法实战:Base64 与 MD5 签名

这一章开始进入实战。每个案例我会按照“接口特征 -> 定位方式 -> Python 实现 -> 运行结果”的顺序来写。

4.1 案例一:Base64 编码型参数

现在很多网站的搜索参数或翻页参数,并没有做真正的加密,只是用 Base64 把 JSON 编码了一下。虽然 Base64 严格来说不算加密算法,但它却是逆向入门最常见的“低垂果实”。

假设某网站搜索请求的参数如下:

https://example.com/api/list?data=eyJwYWdlIjoxLCJzaXplIjoyMH0=

看到这种eyJ开头的数据,几乎可以立刻判断是 Base64。因为 Base64 编码后的 JSON 字符串,开头通常是eyJ(对应{")。

定位方式:在开发者工具中搜索btoa或Base64,就能找到类似代码:

function encodeParam(obj) { return btoa(JSON.stringify(obj)); }

Python 实现:

import base64 import json def encode_param(page, size): payload = { "page": page, "size": size } # 先转为 JSON 字符串,再 Base64 编码 json_str = json.dumps(payload, separators=(",", ":")) encoded = base64.b64encode(json_str.encode("utf-8")).decode("utf-8") return encoded print(encode_param(1, 20))

输出结果:

eyJwYWdlIjoxLCJzaXplIjoyMH0=

这样,我们在 Python 端就还原了前端逻辑。

4.2 案例二:MD5 加盐签名

MD5 是目前最常见的签名算法之一。很多网站在提交表单时,会先把密码做一次 MD5,然后再加盐拼接,或者把请求参数按字典序排序后拼接成一个字符串,整体做 MD5。

假设某登录接口的参数是:

{ "username": "admin", "password": "e10adc3949ba59abbe56e057f20f883e", "timestamp": "1731234567890" }

其中password的明文是123456,加密逻辑看起来是:

password = md5(password + timestamp);

这里额外加了一个时间戳,所以每次密码的 MD5 值都不同,也算是动态签名的一种常见形式。

Python 实现:

import hashlib import time def md5_encrypt(text): md5 = hashlib.md5() md5.update(text.encode("utf-8")) return md5.hexdigest() password = "123456" timestamp = str(int(time.time() * 1000)) sign = md5_encrypt(password + timestamp) print("timestamp:", timestamp) print("sign:", sign)

输出结果类似:

timestamp: 1731234567890 sign: 8a4b7f2c9e6d3f1a...

需要特别注意的是,MD5 加盐时盐值放在前面、后面、还是中间,结果完全不同。实际逆向时,一定要去 JS 代码里确认拼接顺序。

4.3 案例三:SHA256 请求体签名

部分网站会对整个请求体做哈希签名。比如请求体是:

{ "keyword": "手机", "page": 1 }

前端会把 JSON 字符串按特定规则排序后,做 SHA256,得到sign,再把sign放入 Header 或请求体。

伪装前的 JavaScript 可能如下:

const crypto = require('crypto'); function signRequest(params) { const keys = Object.keys(params).sort(); let raw = ''; for (let key of keys) { raw += `${key}=${params[key]}&`; } raw = raw.slice(0, -1); return crypto.createHash('sha256').update(raw).digest('hex'); }

Python 实现:

import hashlib def sha256_sign(params): # 按 key 排序,拼接成 key=value&key=value 形式 sorted_keys = sorted(params.keys()) raw = "&".join(f"{key}={params[key]}" for key in sorted_keys) print("待签名串:", raw) sign = hashlib.sha256(raw.encode("utf-8")).hexdigest() return sign params = { "keyword": "手机", "page": 1 } print("签名:", sha256_sign(params))

输出结果:

待签名串: keyword=手机&page=1 签名: 0e3a5c8f8b8c9e1d2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e

SHA256 是一种不可逆哈希算法,应用非常广。尤其是在一些大厂的开放平台 API 中,签名逻辑基本都是这种思路:参数排序 -> 拼接 -> 哈希 -> 放入请求头或参数中。

5. 对称加密实战:AES 与 DES

MD5 和 SHA256 属于哈希算法,主要用来做完整性校验和签名。而真正意义上的“加密”,是能在加密后还原明文的算法。Web 端最常用的对称加密就是 AES。

5.1 AES 的核心知识点

AES 对称加密需要掌握几个关键概念:

  • 密钥长度:AES-128、AES-192、AES-256。
  • 填充模式:PKCS7、PKCS5、ZeroPadding、NoPadding。
  • 工作模式:ECB、CBC、CFB、OFB、GCM。
  • 偏移量:CBC 模式下需要 IV,ECB 模式不需要。

网站里最常见的是 AES-128-CBC 和 AES-256-CBC,配合 PKCS7 填充。如果遇到 GCM 模式,还需要处理额外的 AuthTag,复杂度会高一些。

5.2 定位 AES 加密代码

AES 加密代码在浏览器里有几个明显特征:

  • 引入了 crypto-js:CryptoJS.AES.encrypt(...)
  • 引入了 WebCrypto API:crypto.subtle.encrypt(...)
  • 自研了 S 盒替换逻辑(少见)

在开发者工具里搜索AES或CryptoJS,通常都能直接定位到加密函数。一个典型的加密逻辑:

const key = CryptoJS.enc.Utf8.parse("0123456789abcdef"); const iv = CryptoJS.enc.Utf8.parse("abcdef9876543210"); function encryptByAES(content) { const encrypted = CryptoJS.AES.encrypt(content, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); }

这段代码的意思是:

  • 密钥key是一个 16 字节字符串。
  • 偏移量iv是另一个 16 字节字符串。
  • 使用 CBC 模式,PKCS7 填充。

5.3 Python 还原 AES-CBC

Python 中推荐使用pycryptodome库。安装:

pip install pycryptodome

下面是上述 JavaScript 逻辑的完整 Python 实现:

from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import base64 def aes_cbc_encrypt(content, key, iv): # 将明文转为字节 data = content.encode("utf-8") key = key.encode("utf-8") iv = iv.encode("utf-8") cipher = AES.new(key, AES.MODE_CBC, iv) # PKCS7 填充 padded_data = pad(data, AES.block_size, style='pkcs7') encrypted = cipher.encrypt(padded_data) # 转 Base64 输出 return base64.b64encode(encrypted).decode("utf-8") def aes_cbc_decrypt(encrypted_b64, key, iv): encrypted = base64.b64decode(encrypted_b64) key = key.encode("utf-8") iv = iv.encode("utf-8") cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = cipher.decrypt(encrypted) # 去掉填充 return unpad(decrypted, AES.block_size, style='pkcs7').decode("utf-8") key = "0123456789abcdef" iv = "abcdef9876543210" encrypted_text = aes_cbc_encrypt("hello js spider", key, iv) print("加密结果:", encrypted_text) decrypted_text = aes_cbc_decrypt(encrypted_text, key, iv) print("解密结果:", decrypted_text)

运行结果:

加密结果: 3b7dceb0e1ba8ea4ca8491d33a1a0b76 解密结果: hello js spider

这里有几个坑需要提醒:

  1. key 和 iv 的字节长度必须符合要求。AES-128 需要 16 字节,AES-256 需要 32 字节。
  2. CBC 模式的 IV 长度必须等于块大小,即 16 字节。
  3. 填充方式不一致会导致解密失败。JS 端如果使用的是Pkcs7,Python 端也要对应选择pkcs7。

5.4 案例:登录密码 AES 加密

某网站在登录时,对密码做了 AES 加密。定位后,发现加密逻辑是:

let encryptedPwd = CryptoJS.AES.encrypt(plainPwd, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString();

此时我们不需要知道密钥是怎么来的,只需要在 JS 代码中找到固定的key和iv。比如:

var key = CryptoJS.enc.Utf8.parse("www.xxx.com.key"); var iv = CryptoJS.enc.Utf8.parse("www.xxx.com.iv!");

那么 Python 就可以直接复用上面的aes_cbc_encrypt函数:

key = "www.xxx.com.key" iv = "www.xxx.com.iv!" pwd = "mypassword123" encrypted_pwd = aes_cbc_encrypt(pwd, key, iv) print(encrypted_pwd)

这里只是示例,网上很多网站的 key 和 iv 长度实际上并不标准,比如某个字符串长度不是 16 字节,前端在 parse 后会自动截断或补零。遇到这种情况,Python 端也需要做同样的字节截断或补零处理。

6. 非对称加密实战:RSA

6.1 RSA 加密特征

RSA 是非对称加密,公钥加密、私钥解密。在浏览器端,服务器会把公钥下发给前端,前端用公钥加密敏感数据,后端再用私钥解密。

RSA 加密在 JS 中的典型识别特征:

  • 出现setPublicKey、encrypt等字眼。
  • 引入jsencrypt库。
  • 出现MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...这样的公钥字符串。

一个典型的 JS 加密逻辑:

const encryptor = new JSEncrypt(); encryptor.setPublicKey(publicKey); const encrypted = encryptor.encrypt(password);

6.2 Python 实现 RSA 加密

Python 中常用rsa或Crypto.PublicKey.RSA来实现。这里以pycryptodome为例:

from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 def rsa_encrypt(public_key_str, content): # 加载公钥 pub_key = RSA.import_key(public_key_str) cipher = PKCS1_v1_5.new(pub_key) # 加密 encrypted = cipher.encrypt(content.encode("utf-8")) return base64.b64encode(encrypted).decode("utf-8") public_key = """-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END PUBLIC KEY-----""" result = rsa_encrypt(public_key, "123456") print(result)

有几点要注意:

  • RSA 加密有长度限制。使用 1024 位密钥时,PKCS1_v1_5 最多只能加密 117 字节;2048 位密钥最多 245 字节。如果明文太长,需要分段加密。
  • 如果 JS 端使用了setPublicKey传入的是不带BEGIN PUBLIC KEY头的裸 Base64 字符串,Python 端需要先拼装成标准 PEM 格式再导入。
  • JSEncrypt 默认使用 PKCS1 v1.5 填充。如果遇到 OAEP 填充,Python 端要改用PKCS1_OAEP。

6.3 RSA 分段加密示例

长文本分段加密是实际项目中的常见需求。比如密码字段可能不长,但如果加密的是手机号+验证码+时间戳拼接的长字符串,就必须分段。

def rsa_long_encrypt(public_key_str, content, chunk_size=100): pub_key = RSA.import_key(public_key_str) cipher = PKCS1_v1_5.new(pub_key) content_bytes = content.encode("utf-8") encrypted_chunks = [] for i in range(0, len(content_bytes), chunk_size): chunk = content_bytes[i:i + chunk_size] encrypted_chunk = cipher.encrypt(chunk) encrypted_chunks.append(encrypted_chunk) return base64.b64encode(b"".join(encrypted_chunks)).decode("utf-8")

分段大小需要根据密钥位数计算,1024 位密钥时,chunk_size最大 117;2048 位时最大 245。这个值写得太小不会出错,但会多一些冗余;写太大则会直接报错。

7. 进阶实战:从 JS 中扣取加密函数

前面的案例,我们都是根据算法逻辑在 Python 中重新实现。但有些网站的加密逻辑比较复杂,比如魔改的 Base64、自定的字符映射表、多层嵌套的混淆算法,这时手动重写往往比较费劲。更高效的做法是:直接把浏览器里的 JS 加密函数扣下来,用 Python 调用。

7.1 使用 Node.js 运行扣下的函数

假设我们从某网站扣下了这样一段加密函数:

const CryptoJS = require("crypto-js"); function encryptContent(content, secretKey) { const key = CryptoJS.enc.Utf8.parse(secretKey); const iv = CryptoJS.enc.Utf8.parse("1234567890abcdef"); const encrypted = CryptoJS.AES.encrypt(content, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } // 导出供其他模块调用 module.exports = { encryptContent };

保存为encrypt.js,在终端里执行:

npm init -y npm install crypto-js node -e "const { encryptContent } = require('./encrypt.js'); console.log(encryptContent('hello', 'abcdef0123456789'));"

输出:

U2FsdGVkX1+4n9Z4cH8XpYxUO...

这样我们就验证了扣下来的 JS 函数能独立运行。接下来在 Python 中调用它。

7.2 Python 调用 JS:execjs 方式

安装:

pip install PyExecJS

Python 调用代码:

import execjs with open("encrypt.js", "r", encoding="utf-8") as f: js_code = f.read() ctx = execjs.compile(js_code) result = ctx.call("encryptContent", "hello", "abcdef0123456789") print(result)

7.3 Python 调用 JS:Node.js 子进程方式

execjs虽然方便,但偶尔会遭遇编码问题或环境依赖问题。更稳的方式是直接调用 Node.js 子进程。

Python 端代码:

import subprocess import json def call_js_function(js_file, func_name, *args): # 将参数通过 stdin 传给 Node.js payload = json.dumps({ "func": func_name, "args": args }) result = subprocess.run( ["node", js_file], input=payload, text=True, capture_output=True, encoding="utf-8" ) if result.returncode != 0: raise RuntimeError(f"Node.js 执行失败: {result.stderr}") return result.stdout.strip() # 使用方式 encrypted = call_js_function("encrypt_cli.js", "encryptContent", "hello", "abcdef0123456789") print(encrypted)

encrypt_cli.js内容:

const { encryptContent } = require('./encrypt.js'); process.stdin.on('data', (data) => { const input = JSON.parse(data.toString()); const func = global[input.func] || decryptContent || encryptContent; const result = func(...input.args); process.stdout.write(result); });

这种方式的优势是:Python 与 Node.js 完全隔离,不会因为 JS 库的兼容性问题影响 Python 主流程。如果加密逻辑复杂,推荐优先用这种方式。

7.4 扣代码时的环境补齐

有些 JS 加密函数会依赖window、document、navigator等浏览器对象。扣出来后直接用 Node.js 运行,会报错window is not defined。

解决方法有两种:

  • 在 Node.js 脚本顶部手动 mock 环境变量。
  • 在 Python 中用execjs配合外部 JS 运行时,先注入 mock 对象。

比如:

const window = { userAgent: "Mozilla/5.0 ...", navigator: {}, document: {} }; const navigator = { userAgent: "Mozilla/5.0 ...", platform: "Win32" }; // 然后引入加密函数

这种方法属于“补环境”,是逆向里比较进阶的操作。平时练习时,可以刻意选择一些加密不依赖环境变量的网站,先练好基本扣函数能力,再慢慢接触复杂的补环境场景。

8. 常见问题与排查思路

逆向过程最让人头疼的不是算法复杂,而是报错后不知道从哪里入手。我把新手最常碰到的问题整理成一张表,方便你对照排查。

问题现象常见原因解决思路
请求返回 403缺少请求头或 Cookie 校验失败检查 User-Agent、Referer、Origin、Cookie
返回内容是一串密文响应数据被加密,前端解密后渲染搜索decrypt、decode、JSON.parse定位解密函数
Python 加密结果与浏览器不一致算法顺序、填充模式、编码方式不同对比多次加密结果,优先检查字符串拼接和 Base64 编码
控制台报xxx is not defined扣代码时缺少依赖函数或环境对象补全依赖 JS 文件,或在 Node.js 中 mock 环境变量
AES 解密报错key/iv 长度不对,或填充模式不一致确认密钥、IV 字节长度,填充模式统一为 PKCS7
RSA 加密报错明文过长,超出单次加密上限使用分段加密
浏览器能请求,Python 请求失败HTTP 版本、TLS 指纹或 headers 顺序不同尝试使用curl_cffi或调整 headers 顺序

这里特别说一个新手容易犯的错误:很多人拿到加密函数后,直接在浏览器控制台复制执行结果,然后在 Python 里设定固定的值去请求。这样做虽然能跑通一次,但一旦加密参数携带时间戳或随机数,下一次请求就会失效。所以正确的做法,是在 Python 端动态计算加密参数,或者动态调用 JS 函数,而不是粘贴一次性结果。

9. 标准算法识别的快速判断口诀

实际逆向过程中,我们可以通过密文特征快速判断算法类型,减少试错时间。整理了几个常见特征:

特征判断
以eyJ开头大概率是 Base64 编码的 JSON,直接解码查看
32 位小写十六进制大概率是 MD5
40 位小写十六进制可能是 SHA1
64 位小写十六进制大概率是 SHA256
有+、/、=的密文大概率是 Base64 编码后的加密结果
密文长度远大于明文长度可能是 RSA 或 AES 再做了 Base64 输出
密文开头有U2FsdGVkX1这是 CryptoJS AES 加密的默认格式特征

这些特征只能作为初步判断,不能直接当成定论。比如 MD5 也可以是 32 位大写,AES 加密后也可以转成 Hex 而非 Base64。最终还是要以 JS 代码为准。

10. 逆向的原则与合规提醒

爬虫和 JS 逆向技术本身是中性工具,但在实际使用中需要注意边界。我从工程实践角度给几点建议:

  • 只对你有权访问的网站进行逆向分析,比如自己公司的系统、已授权的接口,或公开的练习靶场。
  • 不要将逆向技术用于绕过登录态、破解付费内容、抓取用户隐私数据,这些行为可能违反相关法律法规。
  • 练习时优先选择公开的模拟站点,或使用本地搭建的靶场环境。
  • 在生产环境中,控制请求频率,合理设置延时,避免给目标服务器造成压力。
  • 如果目标网站有明确的 robots 协议或服务条款限制,应当遵守。
  • 涉及账号密码、支付信息等敏感数据时,不要在代码中硬编码密钥和口令。

这里也顺便解释一下:很多大厂网站上看到的复杂加密,并不是为了“防爬”,而是为了保护用户数据和接口安全。我们学习 JS 逆向,更重要的是理解常见的加密算法和前端安全设计思路,而不是把它当作绕过一切限制的万能钥匙。

11. 总结与下一步学习路线

这篇文章从 JS 逆向的基础概念讲起,介绍了抓包定位、算法识别、Python 还原以及扣取 JS 函数的完整流程。核心内容包括:

  • Base64、MD5、SHA256 等常见摘要与编码算法的识别和还原。
  • AES 对称加密的密钥、IV、填充模式概念,以及 Python 端实现。
  • RSA 非对称加密的公钥加载与分段加密。
  • 从浏览器中扣取 JS 加密函数,并在 Python 中通过execjs或 Node.js 子进程调用的方法。
  • 常见报错的排查思路和算法特征判断口诀。

下一步,你可以按照下面的路线继续深入:

  1. 先熟悉 Node.js 的基本用法,能独立运行一个 JS 文件。
  2. 练习从简单网站中定位加密参数,用 Python 重写。
  3. 尝试扣取完整的 JS 文件,并补全依赖。
  4. 学习 AST 抽象语法树,用于处理混淆代码。
  5. 了解接口签名、时间戳防重放、行为验证码等进阶反爬手段。

最后想说的是,逆向这条路没有捷径,唯有多练。每遇到一个新网站,把它当作一道新的算法题,先观察,再定位,最后动手实现。你会发现,很多所谓的“魔改加密”,底层依然是标准算法。只要掌握了底层逻辑,剩下的都是耐心活。

如果你觉得这篇文章对你有帮助,可以收藏备用,也欢迎在评论区交流你在逆向过程中遇到的坑。下一篇我会继续整理更复杂的混淆对抗与代码补环境实战,感兴趣的话可以关注。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询