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 密钥交换)

  • 用于安全地在不安全网络上交换密钥,但不能直接加密数据。

注意:

  • 使用时都是使用公钥加密使用私钥解密,公钥可以公开,私钥自己保留。
  • 算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使加密解密速度慢于对称加密

image-20250303183928534

应用场景

  1. 数据加密
    • 如 HTTPS 传输时使用 TLS/SSL 加密通信,RSA 保护对称密钥传输。
  2. 身份认证
    • 数字证书(如 SSL 证书)用于服务器身份验证。
  3. 数字签名
    • 发送方用私钥对数据签名,接收方用公钥验证签名,确保数据未被篡改。
  4. 密钥交换
    • 非对称加密用于安全交换对称密钥(如 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 一个实例对象的操作;
  • 搜索关键词 setPublicKeysetKeysetPrivateKeygetPublicKey 等,一般实现的代码里都含有设置密钥的过程。
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):更安全的填充方式。
  • 填充过程会引入 随机性,即使同样的明文 每次加密都会生成不同的密文

解密后为什么数据仍然相同?

  • 虽然密文每次都不一样,但解密过程会 自动去掉填充,还原原始明文。
  • 由于加密时的随机填充不会影响明文本身,所以 解密后的数据仍然是原来的明文

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)

image-20250303185744862

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

image-20250303185846126

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}')

image-20250303192057264

2.中国移动登录逆向

地址:https://login.10086.cn/html/login/email_login.html

接口:https://login.10086.cn/login.htm

我们发现请求参数accountpassword进行了加密

image-20250303192823038

2.1jsencrypt(ReferenceError: window is not defined)

jsencrypt 依赖 window 对象,默认只能在 浏览器环境 下运行。如果你一定要在 Node.js 环境中使用 jsencrypt,可以用 global.window = {} 伪造一个 window 对象 来兼容。

global.window = {};
var JSEncrypt = require('jsencrypt')

image-20250303200040999

2.2关键字搜索

首先accountpassword一定是可以解密的,因为后端也需要获取

推断关键字:

  • 不确定是对称还是非对称,但是一定会有加密的方法,所以我们可以搜索encrypt
  • 因为是accountpassword加密,所以有给2字段加密的地方,可以搜索accountpassword
  • JSEncrypt 是一个基于 RSA(非对称加密算法)的 JavaScript 加密库,主要用于在 浏览器端 进行 公钥加密、私钥解密,常用于前端加密数据后传输给后端解密的场景。可以搜索JSEncrypt
encrypt
account
password
JSEncrypt

我们搜索encrypt没出来,搜索account看见代码注释都写出来了

我们在这里打断点尝试一下

image-20250303200204834

就是et进行加密的

image-20250303200501322

2.2xhr断点

添加xhr断点

/login.htm

image-20250303200708106

进入断点后,在r.send方法中i.data已经进行了加密

所以我们在调用堆栈中继续向前寻找

其实正常来说加密逻辑肯定在发送请求前

r.send(i.hasContent && i.data || null)

image-20250303201010309

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

image-20250303201311223

2.3hook拦截

我这里还是hookxhr,其实和xhr断点一样

image-20250303201850713

2.4加密逆向

找到加密的地方,就开始逆向加密代码了

跳转到et的实现

    params.password = et(params.password);
    params.account = et(params.account);

image-20250303202036281

这里的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;
}

image-20250303202432505

这段代码比较简单,使用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'))

image-20250303202641165

3.苏宁易购登录逆向

地址:https://passport.suning.com/ids/login

接口地址:https://passport.suning.com/ids/login

image-20250303215654636

3.1关键字搜索

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

image-20250303215711635

只搜索到了1处,查看pwd2赋值代码很像加密的逻辑,先打断点调试

var encrypt = new JSEncrypt();
encrypt.setPublicKey(loginPBK);
var pwd2 = encrypt.encrypt(pwd);

image-20250303215945315

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

image-20250303220545350

3.2xhr断点

添加请求xhr断点

/ids/login

image-20250303222606123

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

image-20250303222851811

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);
    };
})();

image-20250303223114775

请求是https://passport.suning.com/ids/login

但是hookajaxurl,要不hook不住

image-20250303225029996

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

image-20250303225402047

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
        }
    } 

image-20250303220729967

所以我们需要整个自执行函数(初始化函数)

var JSEncryptExports = {};
(function(ap) {
    ...
    ap.JSEncrypt = a3
}
)(JSEncryptExports);
var JSEncrypt = JSEncryptExports.JSEncrypt;

image-20250303221230348

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

image-20250303221531512

我们把代码拷贝出来后,尝试执行一下

找不到navigator

ReferenceError: navigator is not defined

image-20250303221920825

ctrl + g定位到代码

image-20250303222043279

navigator是浏览器对象,我们使用pycharm执行,给个固定值即可

navigator = {}
navigator.appName = 'Netscape'

image-20250303222140169

再次执行,发现window is not defined

image-20250303222250016

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

image-20250303222440371

4.中国观鸟记录中心

地址:http://birdreport.cn/

4.1关键字搜搜

4.2xhr断点

4.3hook拦截

4.4逆向

📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!

wxzf
posted @ 2025-04-09 16:10  peng_boke  阅读(264)  评论(0)    收藏  举报