JS逆向爬虫案例(1)(AES加密解析)

AES加密解析(XX租网站)

一.AES加密前置知识

AES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个。

常见的对称加密: AES, DES, 3DES. 我们这里讨论AES。

安装:

pip install pycryptodome

AES 加密最常用的模式就是 CBC 模式和 ECB模式 ,当然还有很多其它模式,他们都属于AES加密。ECB模式和CBC 模式俩者区别就是 ECB 不需要 iv偏移量,而CBC需要。

"""
长度
    16: *AES-128*   
    24: *AES-192*
    32: *AES-256*
    
MODE 加密模式. 
    常见的ECB, CBC
    ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。
    CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或或操作后再加密,这样做的目的是增强破解难度。
"""

CBC加密案例(选择aes-128):

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64

key = '0123456789abcdef'.encode()  # 秘钥: 因为aes-128模式,所以必须16字节
iv = 'abcdabcdabcdabcd'.encode()   # 偏移量:因为aes-128模式,所以必须16字节
text = 'alex is a monkey!'  # 加密内容,因为aes-128模式,所以字节长度必须是16的倍数
text = pad(text.encode(), 16)
print("完整text:", text)

aes = AES.new(key, AES.MODE_CBC, iv)  # 创建一个aes对象

en_text = aes.encrypt(text)  # 加密明文
print("aes加密数据:::", en_text)  # b"_\xf04\x7f/R\xef\xe9\x14#q\xd8A\x12\x8e\xe3\xa5\x93\x96'zOP\xc1\x85{\xad\xc2c\xddn\x86"

en_text = base64.b64encode(en_text).decode()  # 将返回的字节型数据转进行base64编码
print(en_text)  # X/A0fy9S7+kUI3HYQRKO46WTlid6T1DBhXutwmPdboY=

CBC解密案例:

from Crypto.Cipher import AES
import base64
from Crypto.Util.Padding import unpad

key = '0123456789abcdef'.encode()
iv = b'abcdabcdabcdabcd'
aes = AES.new(key, AES.MODE_CBC, iv)

text = 'X/A0fy9S7+kUI3HYQRKO46WTlid6T1DBhXutwmPdboY='.encode()  # 需要解密的文本
ecrypted_base64 = base64.b64decode(text)  # base64解码成字节流
source = aes.decrypt(ecrypted_base64)  # 解密
print("aes解密数据:::", source.decode())
print("aes解密数据:::", unpad(source, 16).decode())
  1. 在Python中进行AES加密解密时,所传入的密文、明文、秘钥、iv偏移量、都需要是bytes(字节型)数据。python 在构建aes对象时也只能接受bytes类型数据。

  2. 当秘钥,iv偏移量,待加密的明文,字节长度不够16字节或者16字节倍数的时候需要进行补全。

  3. CBC模式需要重新生成AES对象,为了防止这类错误,无论是什么模式都重新生成AES对象就可以了。

二.毛毛租网站解析爬虫

1.页面解析

返回值解密

刷新页面,通过F12控制台可找到每个页面中具体商品的信息都是通过一个post请求获取在这里插入图片描述
点击预览,和负载可以看到都是显示密文,显然信息是经过加密的
在这里插入图片描述
在这里插入图片描述
进入网站源代码,搜索关键字decrypt(,找到四个函数:
在这里插入图片描述

逐一打上断点,并刷新页面,最后发现页面在第四个函数完成后页面信息完全显示,并且查看函数传入的数据与刚刚请求发过来的数据是相同的,故判断这就是对信息解密的函数
在这里插入图片描述
通过函数名知道信息是使用的AES加密方式。
进入函数内部,在这里插入图片描述

可以发现有一行函数
return this.d(a, e)
打上断点并刷新页面,查看函数执行到次时两个参数的值,可以发现一个是前面post请求发过来的加密数据,另一个是一个16位的字符串,与AES加密时用到的key与iv格式相同,判断这就是加密的密钥,通过python代码使用此字符串对密文经行解密验证

response = requests.post('https://www.maomaozu.com/index/build.json', cookies=cookies, headers=headers, data=data)

# print(response.text)
data_text = response.text.encode()
# print(data_text)

key = '0a1fea31626b3b55'.encode()
iv = '0a1fea31626b3b55'.encode()
aes = AES.new(key, AES.MODE_CBC, iv)
data = base64.b64decode(data_text)
# print(data)

data = aes.decrypt(data)
# print(unpad(data,16).decode())  # 去除补位并转化为utf-8
data = json.loads(unpad(data,16).decode())
print(data)

程序运行结果:
在这里插入图片描述
数据解析成功。

请求负载解析

同样的方法,在源代码处搜索encript(,也有四处函数
在这里插入图片描述
同样是第四个函数在这里插入图片描述
可以看到函数名与解密的函数相近,并且携带的参数中有page:页码,expire:请求时间的时间戳
进入函数内部
在这里插入图片描述
通过控制台输出可以发现return函数中输入的两个参数中一个是json格式的刚刚页码与时间戳等信息,另一个则是16位的字符串,判断这就是post请求的加密密钥,在python程序中使用相同的信息与此密钥进行加密

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
key = '55b3b62613aef1a0'.encode()
iv = '55b3b62613aef1a0'.encode()

# text = {
#     "Type": 0,
#     "page": 1,
#     "expire": 1708684791449
# }
text = "{\"Type\":%d,\"page\":%d,\"expire\":%d}"
text = text % (0, 1, 1708877591496)
text = pad(text.encode(), 16)
print("完整text:", text)

aes = AES.new(key, AES.MODE_CBC, iv)  # 创建一个aes对象

en_text = aes.encrypt(text)  # 加密明文
en_text = base64.b64encode(en_text).decode()  # 将返回的字节型数据转进行base64编码
print(en_text)

输出:

i1gpLEJyKvluv3sQVGr/hx6/EpsN1QrE5hHyWMBPknue1jJY9yGqT7opkH3fpldv

请求负载:
在这里插入图片描述
可以看到通过python加密的数据与post请求所携带的字符是一样的。

2.代码实现

import json
import time
import openpyxl
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

cookies = {
    'PHPSESSID': 'i6b3tpitsiabdl1hu7s7otlvu2',
    'SECKEY_ABVK': 'dUTeRdm1X/JsorkJAHMX/sbf6B0RB1PMlY7Nk1//I2U%3D',
    'Hm_lvt_6cd598ca665714ffcd8aca3aafc5e0dc': '1708677440',
    'Hm_lpvt_6cd598ca665714ffcd8aca3aafc5e0dc': '1708677440',
    'BMAP_SECKEY': 'GJ4PmYY3-7GF_CRLolSRulPhQ_CTu9oLk5BGLix_d-W_ZsK_690aVPyx7QqqpyrKEmHAlxMdA3T6wQn8Ub6OHaabK11_i-lUea0RgCtWq6yelMerVNapfTQXIMP2gGs7-ZyaxErsWAX_sfIdWZ2Vxjz4WFuD_D3Q-TuU5ndyYwCaiBDdm_bN9RK4IDaVTU2h',
}

headers = {
    'Accept': '*/*',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
    'Connection': 'keep-alive',
    'Content-Type': 'application/json; charset=UTF-8',
    # Requests sorts cookies= alphabetically
    # 'Cookie': 'PHPSESSID=i6b3tpitsiabdl1hu7s7otlvu2; SECKEY_ABVK=dUTeRdm1X/JsorkJAHMX/sbf6B0RB1PMlY7Nk1//I2U%3D; Hm_lvt_6cd598ca665714ffcd8aca3aafc5e0dc=1708677440; Hm_lpvt_6cd598ca665714ffcd8aca3aafc5e0dc=1708677440; BMAP_SECKEY=GJ4PmYY3-7GF_CRLolSRulPhQ_CTu9oLk5BGLix_d-W_ZsK_690aVPyx7QqqpyrKEmHAlxMdA3T6wQn8Ub6OHaabK11_i-lUea0RgCtWq6yelMerVNapfTQXIMP2gGs7-ZyaxErsWAX_sfIdWZ2Vxjz4WFuD_D3Q-TuU5ndyYwCaiBDdm_bN9RK4IDaVTU2h',
    'Origin': 'https://www.maomaozu.com',
    'Referer': 'https://www.maomaozu.com/',
    'Sec-Fetch-Dest': 'empty',
    'Sec-Fetch-Mode': 'cors',
    'Sec-Fetch-Site': 'same-origin',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',
    'sec-ch-ua': '"Not A(Brand";v="99", "Microsoft Edge";v="121", "Chromium";v="121"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
}

encrypt = '55b3b62613aef1a0'.encode()  # 加密密钥
aes_encrypt = AES.new(encrypt, AES.MODE_CBC, encrypt)
decrypt = '0a1fea31626b3b55'.encode()  # 解密密钥

for i in range(1, 11):
    """ 对post请求参数进行加密 """
    text = "{\"Type\":%d,\"page\":%d,\"expire\":%d}"
    text = text % (0, i, time.time())  # 获取当前时间戳
    text = pad(text.encode(), 16)  # 进行十六进制补位
    en_text = aes_encrypt.encrypt(text)  # 加密明文
    data = base64.b64encode(en_text).decode()  # 将返回的字节型数据转进行base64编码

    response = requests.post('https://www.maomaozu.com/index/build.json', cookies=cookies, headers=headers,
                             data=data)  # 发送请求

    """ 对返回数据进行解析 """
    data_text = response.text.encode()  # 转换为16进制
    data_text = base64.b64decode(data_text)  # 转换为字节流

    aes_decrypt = AES.new(decrypt, AES.MODE_CBC, decrypt)
    data_text = aes_decrypt.decrypt(data_text)  # 解密

    data_json = json.loads(unpad(data_text, 16).decode())  # 去除补位并转换为utf-8

    # print(unpad(data_text, 16).decode())
    """ 保存数据 """
    wb = openpyxl.load_workbook("excel_test.xlsx")
    ws = wb.active
    for index, value in enumerate(data_json['list'], start=(i - 1) * 10 + 2):
        try:
            RoomList = value['RoomList'][0][0]
        except IndexError:
            RoomList = ''
        data_liat = [
            value['ID'], value['Name'], '-'.join(['Address']),
            value['Image'], value['Line'][0], RoomList,
            value['Price'], value['UpdateTimeStr']
        ]
        for column in range(1, 9):
            ws.cell(row=index, column=column).value = data_liat[column - 1]
    print(f'第{i}个页面保存完成')
    wb.save("excel_test.xlsx")

    time.sleep(2)

小细节:
  • 为什么加密请求头时使用的是 text = "{\"Type\":%d,\"page\":%d,\"expire\":%d}"而不直接在程序里设置一个字典然后转为json再加密:
    这个网站中是通过JS代码将JS对象转换为了json形式,其中需要对 ” 号进行转义,而python中是不需要的,所以直接使用python字典转为字符串的话加密之后就与网站发送给服务器的字符不相同了
  • 为什么不将解密的AES对象放在循环外重复使用:
    AES对象作为解密作用时二次解密会出现错误
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值