JavaScript逆向之AES和DES加解密

简介

加密算法总共分为两类:对称加密和非对称加密。对称加密顾名思义就是加密和解密时使用的密钥是同一个,例如AES、DES、3DES;非对称加密就是加密和解密时使用的密钥不是同一个,例如RSA。
在python中有一个第三方库提供了加密逻辑,库名为PyCrypto,安装命令为pip install PyCrypto,但是装这个库很可能会出现问题。所以我们可以用另外一个库来平替,库名叫pycryptodome,安装命令为pip install pycryptodome,这个库里面包含了PyCrypto
安装pycryptodome库成功之后,有可能加密包无法使用,如果无法使用,可以去site-packages文件夹里找crypto文件夹,把它的名字改为Crypto就可以了。

AES算法

进行AES加密的时候需要三个参数:
(1)key:通常是16位的字节
(2)mode:CBC、ECB(常见的就这两种)
(3)iv:CBC模式需要,通常是16位的字节;ECB模式下不需要iv。
下面就介绍如何用python代码实现AES加解密。
AES加密代码:

点击查看代码
# 导入AES,请注意大小写问题
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad     # 填充
import base64

# 如何进行aes加密
# 1.创建一个aes加密器
aes = AES.new(key=b'1234567891234567', IV=b'1234567891234567', mode=AES.MODE_CBC)
ming = "威海龙王和东海龙王谁猛".encode("utf-8")    # 必须处理成字节,加密或者解密处理的都是字节

ming = pad(ming, 16)  # 填充到16位
mi = aes.encrypt(ming)      # 加密之后的内容是杂乱无章的字节,没有任何规律的字符

# 最合适的base64
s = base64.b64encode(mi).decode()
print(s)  # 7ieeKO9ArGM9Ngs/MWshUA+L5S+J+6sk1ozroSTVhwvPcAvziA9gMQwDE2flBney

AES解密代码:

点击查看代码
# 导入AES,请注意大小写问题
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad     # 填充
import base64

mi = "7ieeKO9ArGM9Ngs/MWshUA+L5S+J+6sk1ozroSTVhwvPcAvziA9gMQwDE2flBney"

# 解密逻辑(对称加密里)加密和解密逻辑几乎一样
aes = AES.new(key=b'1234567891234567', IV=b'1234567891234567', mode=AES.MODE_CBC)
mi = base64.b64decode(mi)
ming = aes.decrypt(mi)
ming = unpad(ming, 16)   # 干掉填充即可
print(ming.decode("utf-8"))  # 威海龙王和东海龙王谁猛

DES算法

进行DES加密的时候需要三个参数:
(1)key:通常是8位的字节
(2)mode:CBC、ECB(常见的就这两种)
(3)iv:CBC模式需要,通常是8位的字节;ECB模式下不需要iv。
下面就介绍如何用python代码实现DES加解密。
DES加密代码:

点击查看代码
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad     # 填充
import base64

s = "我爱吃鱼"
ming = s.encode("utf-8")
des = DES.new(key=b'12345678', iv=b'12345678', mode=DES.MODE_CBC)
ming = pad(ming, 8)
mi = des.encrypt(ming)
mi_s = base64.b64encode(mi).decode()
print(mi_s)  # bXZ8WcOzh/SW0yuFPTTZog==

DES解密代码:

点击查看代码
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad     # 填充
import base64

mi_s = "bXZ8WcOzh/SW0yuFPTTZog=="
mi = base64.b64decode(mi_s)
des = DES.new(key=b'12345678', iv=b'12345678', mode=DES.MODE_CBC)
ming = des.decrypt(mi)
ming = unpad(ming, 8)
ming_s = ming.decode("utf-8")
print(ming_s)  # 我爱吃鱼

下面就用两个例子来介绍DES和AES的具体应用

某招标网站——DES加解密

url地址:https://ctbpsp.com/#/
访问网站,打开开发者工具,这个网站用右键和快捷键都打不开开发者工具,需要手动的去浏览器的工具栏里的更多工具处调用。刷新界面,锁定Fetch/XHR,只触发了一条流量。

看到响应数据中是一段加密后的字符串。

想要知道这段字符串的意思就得知道加密逻辑是什么。切换到initiator,看到async关键词,说明是异步加载,可以直接搜索interceptors关键词来定位。

总共有5处地方,其中有三处地方是interceptors的源码,可以不用看。

看看另外两处地方。

由于响应内容是加密的,所以大概率加密逻辑是响应拦截器处。在响应拦截器中看到了非常明显的关键函数JSON.parse,该函数用于将json数据进行解析,所以C(e.data)大概率就是加密过后的数据了。在该行打断点进行调试。

定位到C函数的具体实现位置。

从C函数的代码中可以得到C函数其实是个DES加密算法。接下来就是得到key、iv和mode这三个关键参数了。一行一行代码往下执行,查看每一个变量的值。


t其实是我们需要的key,但是这里有个坑,因为t的长度是10位,而DES算法中的key应该是8位,所以在我们进行解密的时候需要对t进行处理。ciphertext是对数据进行base64编码后的结果,所以在解密的时候要先对其进行base64解码。mode为ECB,就不需要iv了。知道这三个参数后,就可以写python代码了。

点击查看代码
import requests
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad     # 填充
import base64
import json

url = "https://ctbpsp.com/cutominfoapi/recommand/type/5/pagesize/10/currentpage/1"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 "
    "Safari/537.36"
}
resp = requests.get(url, headers=headers)
mi_str = resp.text  # 加密的相应内容
mi_str = mi_str.strip('"')

# 解密逻辑
# 用的什么加密方式    DES
# key:"1qaz@wsx3e"
# iv
# mode:ECB(没有iv)

mi = base64.b64decode(mi_str)
des = DES.new(key="1qaz@wsx3e"[:8].encode("utf-8"), mode=DES.MODE_ECB)      # key取前8位即可
ming = des.decrypt(mi)
ming = unpad(ming, 8)
s = ming.decode("utf-8")
dic = json.loads(s)
print(dic)

运行结果:

看准网——AES加解密

url:https://www.kanzhun.com/search?cityCode=7&industryCodes=&pageNum=1&query=操作员&type=4
访问网站,看触发的数据包。

两条数据包,其中screen.json这条数据包都是明文不需要解密,salary.json数据包中的payload和response都是加密后的字符串。


开始找加密逻辑了。这里还是以搜索url关键词来定位,搜索salary.json,只找到一条符合的。

发现search是个字典,将/api_to/search/salary.json赋值给了getSearchSalaryList,接下来搜索哪里调用了getSearchSalaryList,有两处地方。


由于不能确定触发哪个,所以在两处都打断点。


切换一个城市,看到底触发哪个。停在了第二处,说明第一处没用,可以把第一处的断点删除。

根据me.search.getSearchSalaryList(pe(pe({}, y), {}, {pageNum: f.current,limit: 15}));这行代码可以知道getSearchSalaryList是个函数,看下getSearchSalaryList的具体实现。


在return处打断点,让程序运行到这里。

可以看到变量e和url中的传参很相似。该函数还调用了U函数,定位U函数。

打上断点,运行到这里。

变量e是当前url的地址,变量t是url中的传参。看下T(T({data: t,url: e}, n), {}, {method: "get"})得到的是什么。

其实就是将传过来的变量e和变量t进行了整合。再看a函数的具体实现。

打上断点,运行到这里。

看下运行到黄框处时,变量tnras的值。

变量r的赋值过程如下所示。

也是跟时间戳相关,这里不需要很关心。继续往下看代码。
0 == a.url.indexOf("/api_to") && -1 != a.url.lastIndexOf(".json") && (a.crypto = !0, a.responseType = "text", "string" == typeof a.data && a.data.indexOf("kiv") > -1 ? (t = (n = "string" == typeof a.data ? JSON.parse(a.data) : a.data).b, s = n.kiv) : (s = (0, M._A)(), n = "object" == typeof a.data ? JSON.stringify(a.data) : a.data, t = (0, M.mA)(n, { iv: s }).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "~")))
这段代码其实是个比较长的三目运算,为了方便,将其拆分成三段。
(1)条件判断:
0 == a.url.indexOf("/api_to") && -1 != a.url.lastIndexOf(".json") && (a.crypto = !0, a.responseType = "text", "string" == typeof a.data && a.data.indexOf("kiv") > -1
(2)操作一:
(t = (n = "string" == typeof a.data ? JSON.parse(a.data) : a.data).b, s = n.kiv)
(3)操作二:
(s = (0, M._A)(), n = "object" == typeof a.data ? JSON.stringify(a.data) : a.data, t = (0, M.mA)(n, { iv: s }).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "~"))
如果条件判断成立,执行操作一;不成立则执行操作二。
先看条件判断中的第一个语句0 == a.url.indexOf("/api_to"),看url中是否包含/api_to,如果包含则该语句为假,不包含为真。输出下a.url

包含/api_to,故0 == a.url.indexOf("/api_to")为假,又由于几个条件之间采用的是短路与,第一个为假,后面的就不用判断了,整条语句的结果就为假,执行操作二。操作二中又分为以下几个语句:
(1)s = (0,M._A)()
(2)n = "object" == typeof a.data ? JSON.stringify(a.data) : a.data
(3)t = (0,M.mA)(n, {iv: s}).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "~")
先分析第一个语句:(0,M._A)相当于M._A

s = M._A(),定位到M._A函数。

从代码中可以知道M._A就是随机产生一个长度为16的字符串,故s就是长度为16的字符串。
再分析第二个语句:判断a.data的类型是不是object,如果是,则n = JSON.stringify(a.data),否则n = a.data。输出一下a.data的数据类型。

a.data的数据类型是object,所以n = JSON.stringify(a.data),相当于把a.data转换成字符串类型,输出一下n。

其实就是url中的参数,将其转换成了字符串。
再分析第三个语句:(0,M.mA)相当于M.mA

(0,M.mA)(n, {iv: s})相当于M.mA(n, {iv: s}),看下M.mA是如何实现的。

打上断点,让程序运行到这里。

也是一个三元运算,分析一下。e ? ("string" != typeof e && (e = e.toString()),l(e, t.iv)) : "",e如果有值就执行冒号前面的语句,如果没值,那e还是为空。
具体看下"string" != typeof e && (e = e.toString()),l(e, t.iv)的执行。
e的数据类型为string,故逗号前面的语句直接为假,只需要看l(e, t.iv)就可以了。定位到l函数的实现。

终于找到最最重要的地方了。是一个AES加密,接下来就是找key,iv和mode了。key和mode都跟变量n相关,所以看下n是如何生成的。

打断点,让程序运行到这里。

i为空,tnr是三个正则匹配器,e做了一个替换操作,可是你会发现是一个空字符串进行的替换操作,那么结果不就是个空字符串吗?其实不然,这个双引号里是有东西的,只不过我们看不见。往下运行就可以知道变量e是什么了。跳出看函数,返回到n=u()处,直接查看n是什么。

那么key就为n.keyiv就为t,既然有了iv,就可以大胆猜测mode为CBC。

n.key是字节形式,可以采用toString方法把它转换成字符串。

知道了采用的是AES算法,key、iv、mode都获取到了,就可以写python代码了。还要注意一点,在第三个语句中还会将\替换成_+替换成-=替换成~
python代码如下:

点击查看代码
"""
/**
 * 加密方式:AES
 * key:'G$$QawckGfaLB97r'
 * iv:'Qxdi5Py0QZg6Itj1'
 * mode:CBC
 *
 * 加密之后的base64  把/处理成_,+处理成-,=处理成~
 */
"""
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
import requests
import json

url = "https://www.kanzhun.com/api_to/search/salary.json"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 "
                  "Safari/537.36",
}
data = {
    "query": "操作员",
    "cityCode": 7,
    "industryCodes": "",
    "pageNum": 1,
    "limit": 15
}

kiv = 'Qxdi5Py0QZg6Itj1'

aes = AES.new(key='G$$QawckGfaLB97r'.encode("utf-8"), iv=kiv.encode("utf-8"), mode=AES.MODE_CBC)
ming_bytes = json.dumps(data).encode("utf-8")
ming_bytes = pad(ming_bytes, 16)
mi_bytes = aes.encrypt(ming_bytes)
mi = base64.b64encode(mi_bytes).decode()
mi = mi.replace("/", "_").replace("+", "-").replace("=", "~")
params = {
    "b": mi,
    "kiv": kiv
}
resp = requests.get(url, headers=headers, params=params)
# print(resp.request.url)

resp_text = resp.text  # 获取到密文
resp_text_byte = base64.b64decode(resp_text)

# 解密,不可以用上面加密的那个aes,需要重新创建
de_aes = AES.new(key='G$$QawckGfaLB97r'.encode("utf-8"), iv=kiv.encode("utf-8"), mode=AES.MODE_CBC)
resp_ming_bytes = de_aes.decrypt(resp_text_byte)
resp_ming_bytes = unpad(resp_ming_bytes, 16)
resp_ming = json.loads(resp_ming_bytes.decode("utf-8"))
print(resp_ming)

运行结果如下:

posted @ 2024-03-05 20:19  死不悔改奇男子  阅读(974)  评论(3编辑  收藏  举报