js逆向-非对称加密
前言
生活的改变从来不是偶然,而是无数个选择的累计
把握节奏,让时间为自己所用
有所坚持,用行动为未来奠基
心态积极,保持对生活的热爱与希望
⚠️声明:本文所涉及的爬虫技术及代码仅用于学习、交流与技术研究目的,禁止用于任何商业用途或违反相关法律法规的行为。若因不当使用造成法律责任,概与作者无关。请尊重目标网站的
robots.txt协议及相关服务条款,共同维护良好的网络环境。
1.非对称加密
1.1简介
概述
非对称加密是一种使用公钥(Public Key)和私钥(Private Key)进行加密和解密的加密算法。相比于对称加密(Symmetric Encryption)使用相同的密钥进行加解密,非对称加密使用两个密钥,确保数据传输的安全性和完整性。
基本原理
- 公钥(Public Key):可以公开,用于加密数据。
- 私钥(Private Key):必须保密,仅拥有者可用,用于解密数据。
- 发送方使用接收方的公钥对数据进行加密,只有接收方的私钥才能解密。
- 由于公私钥是成对生成的,使用其中一个密钥加密的数据只能用另一个密钥解密。
常见的非对称加密算法
RSA(Rivest-Shamir-Adleman)
- 最流行的非对称加密算法,基于大素数分解难题。
- 常用于 SSL/TLS 证书、数字签名和安全数据传输。
- 典型密钥长度:1024/2048/4096 位。
ECC(Elliptic Curve Cryptography,椭圆曲线加密)
- 基于椭圆曲线离散对数问题,安全性更高,密钥更短。
- 相比 RSA,ECC 在相同安全级别下计算量更小,更适合移动设备和 IoT 场景。
DSA(Digital Signature Algorithm,数字签名算法)
- 主要用于数字签名认证,而非数据加密。
- 变种 ECDSA 结合 ECC,效率更高。
Diffie-Hellman(DH 密钥交换)
- 用于安全地在不安全网络上交换密钥,但不能直接加密数据。
注意:
- 使用时都是使用公钥加密使用私钥解密,公钥可以公开,私钥自己保留。
- 算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使加密解密速度慢于对称加密

应用场景
- 数据加密
- 如 HTTPS 传输时使用 TLS/SSL 加密通信,RSA 保护对称密钥传输。
- 身份认证
- 数字证书(如 SSL 证书)用于服务器身份验证。
- 数字签名
- 发送方用私钥对数据签名,接收方用公钥验证签名,确保数据未被篡改。
- 密钥交换
- 非对称加密用于安全交换对称密钥(如 AES 密钥),然后再使用对称加密进行数据通信。
对称加密 vs. 非对称加密 vs. 哈希算法
| 特性 | 对称加密 (Symmetric Encryption) | 非对称加密 (Asymmetric Encryption) | 哈希算法 (Hashing) |
|---|---|---|---|
| 密钥 | 使用相同的密钥进行加密和解密 | 使用一对密钥(公钥加密,私钥解密 或 私钥签名,公钥验证) | 无密钥,仅计算哈希值 |
| 加密/解密方式 | 加密和解密使用同一密钥 | 公钥加密,私钥解密(或反向) | 仅加密,不可逆 |
| 安全性 | 密钥泄露后不安全,需要安全存储 | 安全性高,适用于身份认证和数据保护 | 防篡改,不能反向破解 |
| 速度 | 快,适合大数据加密 | 慢,计算复杂,不适合大文件加密 | 快速计算,适合数据完整性校验 |
| 用途 | 数据加密、通信加密 | 数字签名、身份认证、密钥交换 | 数据完整性校验、密码存储 |
| 可逆性 | 可解密 | 可解密 | 不可逆 |
| 密钥管理 | 需要安全分发密钥 | 只需保护私钥,公钥可公开 | 无需密钥管理 |
| 抗攻击性 | 易受密钥泄露和暴力破解影响 | 易受私钥泄露和量子计算攻击影响 | 易受彩虹表攻击(需加盐) |
| 算法示例 | AES、DES、3DES、ChaCha20 | RSA、ECC、DSA、Diffie-Hellman | MD5(不安全)、SHA-256、SHA-512、Bcrypt、Argon2 |
总结
| 类别 | 适合的应用场景 |
|---|---|
| 对称加密 | 大文件加密、数据库加密、安全通信 |
| 非对称加密 | 身份认证、数字签名、密钥交换 |
| 哈希算法 | 密码存储、完整性校验、区块链 |
1.2特征
常见JavaScript调试算法
- 搜索关键词
new JSEncrypt(),JSEncrypt等,一般会使用JSEncrypt库,会有 new 一个实例对象的操作; - 搜索关键词
setPublicKey、setKey、setPrivateKey、getPublicKey等,一般实现的代码里都含有设置密钥的过程。
new JSEncrypt()
JSEncrypt
setPublicKey
setKey
setPrivateKey
getPublicKey
RSA 的私钥、公钥、明文、密文长度也有一定对应关系,也可以从这方面初步判断:
| 私钥长度 | 公钥长度 | 明文长度 | 密文长度 |
|---|---|---|---|
| 428 | 128 | 1~53 | 88 |
| 812 | 216 | 1~117 | 172 |
| 1588 | 392 | 1~245 | 344 |
1.3RSA密文
为什么每次加密的密文不同?
- RSA 不能直接加密明文,而是需要先进行 填充(Padding),常见填充模式:
- PKCS#1 v1.5(默认):
node-rsa默认使用该填充模式。 - OAEP(Optimal Asymmetric Encryption Padding):更安全的填充方式。
- PKCS#1 v1.5(默认):
- 填充过程会引入 随机性,即使同样的明文 每次加密都会生成不同的密文。
解密后为什么数据仍然相同?
- 虽然密文每次都不一样,但解密过程会 自动去掉填充,还原原始明文。
- 由于加密时的随机填充不会影响明文本身,所以 解密后的数据仍然是原来的明文。
1.3JavaScript实现RSA
代码
-
使用
node-rsa生成 RSA 密钥对(公钥 & 私钥)。 -
使用公钥加密,将明文转换为 Base64 编码的密文。
-
使用私钥解密,将密文还原回 UTF-8 编码的明文。
-
密钥格式 采用
PKCS#8(更通用)。
var nodersa = require('node-rsa')
// 加密
function rsaEncrypt(publicKey, str) {
pubKey = new nodersa(publickKey, 'pkcs8-public');
return pubKey.encrypt(str, 'base64');
}
// 解密
function rsaDecrypt(privateKey, str) {
prikey = new nodersa(privareKey, 'pkcs8-private')
return prikey.decrypt(str, 'utf8')
}
// 生成512密钥
var key = new nodersa({b: 1024});
// 生成公钥
var publicKey = key.exportKey('pkcs8-public');
// 生成私钥
var privateKey = key.exportKey('pkcs8-private');
// 原文
var str = 'peng';
// 加密数据
var rsaEncryptData = rsaEncrypt(publicKey,str)
// 解密数据
var rsaDncryptData = rsaDecrypt(privateKey,rsaEncryptData)
console.log("加密:"+rsaEncryptData)
console.log("解密:"+rsaDncryptData)

其实多执行几次可以发现每次加密的数据都是不一样的,但是不会影响解密数据

1.4Python实现RSA
代码
import rsa
import base64
def rsa_encrypt(public_key, str):
"""
加密
:param public_key: 公钥
:param str:明文
:return:
"""
return base64.b64encode(rsa.encrypt(str.encode('utf-8'), public_key))
def rsa_decrypt(private_key, str):
"""
解密
:param private_key: 密钥
:param str: 密文
:return:
"""
return rsa.decrypt(base64.b64decode(str), private_key).decode('utf-8')
if __name__ == "__main__":
# 生成512公钥、私钥
public_key,private_key = rsa.newkeys(512)
print(f'公钥:{public_key}')
print(f'私钥:{private_key}')
str = 'peng'
encrypt_data = rsa_encrypt(public_key,str)
print(f'加密:{encrypt_data}')
decrypt_data = rsa_decrypt(private_key,encrypt_data)
print(f'解密:{decrypt_data}')

2.中国移动登录逆向
地址:https://login.10086.cn/html/login/email_login.html
接口:https://login.10086.cn/login.htm
我们发现请求参数account和password进行了加密

2.1jsencrypt(ReferenceError: window is not defined)
jsencrypt 依赖 window 对象,默认只能在 浏览器环境 下运行。如果你一定要在 Node.js 环境中使用 jsencrypt,可以用 global.window = {} 伪造一个 window 对象 来兼容。
global.window = {};
var JSEncrypt = require('jsencrypt')

2.2关键字搜索
首先account和password一定是可以解密的,因为后端也需要获取
推断关键字:
- 不确定是对称还是非对称,但是一定会有加密的方法,所以我们可以搜索
encrypt - 因为是
account和password加密,所以有给2字段加密的地方,可以搜索account和password JSEncrypt是一个基于 RSA(非对称加密算法)的 JavaScript 加密库,主要用于在 浏览器端 进行 公钥加密、私钥解密,常用于前端加密数据后传输给后端解密的场景。可以搜索JSEncrypt
encrypt
account
password
JSEncrypt
我们搜索encrypt没出来,搜索account看见代码注释都写出来了
我们在这里打断点尝试一下

就是et进行加密的

2.2xhr断点
添加xhr断点
/login.htm

进入断点后,在r.send方法中i.data已经进行了加密
所以我们在调用堆栈中继续向前寻找
其实正常来说加密逻辑肯定在发送请求前
r.send(i.hasContent && i.data || null)

就是在发送$.ajax({})进行加密的

2.3hook拦截
我这里还是hook了xhr,其实和xhr断点一样

2.4加密逆向
找到加密的地方,就开始逆向加密代码了
跳转到et的实现
params.password = et(params.password);
params.account = et(params.account);

这里的js代码进行了逆向,看到使用JSEncrypt包进行了加密,
setPublicKey设置了公钥(说明可能是rsa)
那我们也可以使用JSEncrypt重写这个方法
function et(_0x32033c) {
var _0x283d00 = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsgDq4OqxuEisnk2F0EJFmw4xKa5IrcqEYHvqxPs2CHEg2kolhfWA2SjNuGAHxyDDE5MLtOvzuXjBx/5YJtc9zj2xR/0moesS+Vi/xtG1tkVaTCba+TV+Y5C61iyr3FGqr+KOD4/XECu0Xky1W9ZmmaFADmZi7+6gO9wjgVpU9aLcBcw/loHOeJrCqjp7pA98hRJRY+MML8MK15mnC4ebooOva+mJlstW6t/1lghR8WNV8cocxgcHHuXBxgns2MlACQbSdJ8c6Z3RQeRZBzyjfey6JCCfbEKouVrWIUuPphBL3OANfgp0B+QG31bapvePTfXU48TYK0M5kE+8LgbbWQIDAQAB';
var _0x1defd6 = new JSEncrypt();
_0x1defd6['setPublicKey'](_0x283d00);
var _0x4bd6d3 = _0x1defd6['encrypt'](_0x32033c);
return _0x4bd6d3;
}

这段代码比较简单,使用jsencrypt进行加密
global.window = {};
// npm install jsencrypt
var JSEncrypt = require('jsencrypt')
function et(t) {
var _0x283d00 = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsgDq4OqxuEisnk2F0EJFmw4xKa5IrcqEYHvqxPs2CHEg2kolhfWA2SjNuGAHxyDDE5MLtOvzuXjBx/5YJtc9zj2xR/0moesS+Vi/xtG1tkVaTCba+TV+Y5C61iyr3FGqr+KOD4/XECu0Xky1W9ZmmaFADmZi7+6gO9wjgVpU9aLcBcw/loHOeJrCqjp7pA98hRJRY+MML8MK15mnC4ebooOva+mJlstW6t/1lghR8WNV8cocxgcHHuXBxgns2MlACQbSdJ8c6Z3RQeRZBzyjfey6JCCfbEKouVrWIUuPphBL3OANfgp0B+QG31bapvePTfXU48TYK0M5kE+8LgbbWQIDAQAB';
var _0x1defd6 = new JSEncrypt();
_0x1defd6['setPublicKey'](_0x283d00);
var _0x4bd6d3 = _0x1defd6['encrypt'](t);
return _0x4bd6d3;
}
console.log(et('11231231@qq.com'))

3.苏宁易购登录逆向
地址:https://passport.suning.com/ids/login
接口地址:https://passport.suning.com/ids/login

3.1关键字搜索
password2字段界面,可以尝试先进行关键字搜索

只搜索到了1处,查看pwd2赋值代码很像加密的逻辑,先打断点调试
var encrypt = new JSEncrypt();
encrypt.setPublicKey(loginPBK);
var pwd2 = encrypt.encrypt(pwd);

再次请求进入到了断点,我们进入encrypt方法查看实现

3.2xhr断点
添加请求xhr断点
/ids/login

在调用堆栈中寻找,依然可以找到在请求ajax之前进行加密

3.3hook拦截
因为是加密,是在发送请求前进行加密的,所以还是hook请求(xhr)
ctrl + enter运行hook-xhr脚本
(function () {
var open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url, async) {
if (url.indexOf("login") != -1) {
debugger;
}
return open.apply(this, arguments);
};
})();

请求是https://passport.suning.com/ids/login
但是hook是ajax的url,要不hook不住

hook住login请求后,接着从调用堆栈中依然可以找到发送ajax之前加密的代码

3.4逆向
进入encrypt.encrypt(pwd);实现
类名是JSEncrypt,进入确是a3,说明有地方给JSEncrypt赋值
搜索JSEncrypt,确实看到ap.JSEncrypt = a3
a3.prototype.encrypt = function(t) {
try {
return ae(this.getKey().encrypt(t))
} catch (z) {
return false
}
}

所以我们需要整个自执行函数(初始化函数)
var JSEncryptExports = {};
(function(ap) {
...
ap.JSEncrypt = a3
}
)(JSEncryptExports);
var JSEncrypt = JSEncryptExports.JSEncrypt;

加密的时候还执行了encrypt.setPublicKey(loginPBK);,我们在代码中搜索一下loginPBK(公钥)

我们把代码拷贝出来后,尝试执行一下
找不到navigator
ReferenceError: navigator is not defined

ctrl + g定位到代码

navigator是浏览器对象,我们使用pycharm执行,给个固定值即可
navigator = {}
navigator.appName = 'Netscape'

再次执行,发现window is not defined

window也是浏览器对象,给个默认值继续执行,发现可以正常加密了

4.中国观鸟记录中心
4.1关键字搜搜
4.2xhr断点
4.3hook拦截
4.4逆向
📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!

浙公网安备 33010602011771号