Python各种加密逻辑

Python各种加密逻辑

​ 在我们进行js逆向的时候. 总会遇见一些我们人类无法直接能理解的东西出现. 此时你看到的大多数是被加密过的密文.

一. 一切从MD5开始

MD5是一个非常常见的摘要(hash)算法.. 其特点就是小巧. 速度快. 极难被破解(王小云女士). 所以, md5依然是国内非常多的互联网公司选择的密码摘要算法.

  1. 这玩意不可逆. 所以. 摘要算法就不是一个加密逻辑.

  2. 相同的内容计算出来的摘要应该是一样的

  3. 不同的内容(哪怕是一丢丢丢丢丢不一样) 计算出来的结果差别非常大

在数学上. 摘要其实计算逻辑就是hash.

hash(数据) => 数字

1. 密码
2. 一致性检测  

md5的python实现:

from hashlib import md5

obj = md5()
obj.update("alex".encode("utf-8"))
# obj.update("wusir".encode('utf-8'))  # 可以添加多个被加密的内容

bs = obj.hexdigest()
print(bs)
image-20210907115443113

我们把密文丢到网页里. 发现有些网站可以直接解密. 但其实不然. 这里并不是直接解密MD5. 而是"撞库".

就是它网站里存储了大量的MD5的值. 就像这样:

image-20210907135220771

而需要进行查询的时候. 只需要一条select语句就可以查询到了. 这就是传说中的撞库.

如何避免撞库: md5在进行计算的时候可以加盐. 加盐之后. 就很难撞库了.

from hashlib import md5


salt = "我是盐.把我加进去就没人能破解了"
obj = md5(salt.encode("utf-8"))  # 加盐
obj.update("alex".encode("utf-8"))

bs = obj.hexdigest()
print(bs)

image-20210907135627573

扩展; sha256

from hashlib import sha1, sha256, sha512

obj = sha512()
obj.update("123456".encode())
mi = obj.hexdigest()
print(mi)

# 7c4a8d09ca3762af61e59520943dc26494f8941b

# https://1024tools.com/hash
# md5 -> 32位   123456 -> e10adc
# sha1 -> 40位  123456 -> 7c
# sha256 -> 64位
# sha512 -> 128位
print(mi.__len__())

不论是sha1, sha256, md5都属于摘要算法. 都是在计算hash值. 只是散列的程度不同而已. 这种算法有一个特性. 他们是散列. 不是加密. 而且, 由于hash算法是不可逆的, 所以不存在解密的逻辑.

# python的标准库hashlib支持各种hash计算
from hashlib import md5

# # 使用方式是固定的
# # 创建一个md5对象
# obj = md5()
# # 把你要计算的东西传递给obj,
# # 需要注意的是, 要计算的内容必须是字节
# obj.update("樵夫".encode("utf-8"))
#
# # 提取md5值
# md5_val = obj.hexdigest()
# print(md5_val)

# a6168d40a27a4eb79039b8350de449e1

# md5的坑
# 1.md5计算的时候. 请注意. obj是一次性的...
#   多次使用, 会产生叠加的效果. 尽量避免...
# 再算一次
# # https://1024tools.com/hash
# obj = md5()
# # 3b286131504399567b29179e7edda37a
# obj.update("樵夫".encode("utf-8"))
# print(obj.hexdigest())

# # 2.md5是不可逆的. 如何解决撞库, 加盐...
# obj = md5(b'abc')
# # obj.update("123456".encode())
# # # obj.update("456789".encode())
# obj.update("3312".encode())
#
# md5_val = obj.hexdigest()
# print(md5_val)

# 加盐到底是个什么逻辑....
# 加了盐, lkjfdsjakl,    明文, 123456
# a007609c07631b0eed91c6495b995a32
# lkjfdsjakl123456
# a007609c07631b0eed91c6495b995a32

# 加盐就是在源字节前面增加一些新的东西.

# obj = md5()
# obj.update(b"abc"+"123456".encode() + b'def')
#
# md5_val = obj.hexdigest()
# print(md5_val)

# 如何看出来js里面用的是md5.
# 在js中运行的过程中, 不经意间,看到了md5字样的东西. -> md5方向去靠拢.
# 并且看到了. 长度是32位的, 16进制的数字的字符串.  -> 也可以向md5方向靠拢

# 如何确定100%是md5(标准的)
# 计算123456 => e10adc开头. 大概率就是标准的md5
# 在console里. 计算一下js中的函数, 传递123456 如果计算的结果是e10adc  就可以确定是标准的md5了


# 案例:有道词典
# https://fanyi.youdao.com/index.html#/
'Object(n["a"])(Object(n["a"])({}, e), O(t))'
'n["a"](n["a"](e), O(t))'
"{i: 'god', from: 'auto', to: '', domain: '0', dictResult: true, …},  {sign: 'b93b6c592d96ab4274ce0a51bc747e48', client: 'fanyideskweb', product: 'webfanyi', appVersion: '1.0.0', vendor: 'web', …}"
# t是固定的. 'fsdsogkndfokasodnaso'
# O(t): {sign: '78e61f03b4765afcceb122337662996f', client: 'fanyideskweb', product: 'webfanyi', appVersion: '1.0.0', vendor: 'web', …}
# O有重大作案嫌疑..
# a.d(url, n.a(e, O(t)))
# Object(n["a"])(Object(n["a"])({}, e), O(t))

# 上述代码逻辑, 能记住就记住. 这是特征. 大概率在做合并.
# 90%的概率在合并对象.
# 是否真的是合并. 可以去控制台验证你的猜想.

import time
import requests

t = 'fsdsogkndfokasodnaso'
u = "webfanyi"
# 时间戳
e = str(int(time.time() * 1000))
d = "fanyideskweb"
s = f'client={d}&mysticTime={e}&product={u}&key={t}'

obj = md5()
obj.update(s.encode())
sign = obj.hexdigest()

data = {
    "i": "god",
    "from": "auto",
    "to": "",
    "dictResult": "true",
    "keyid": "webfanyi",
    "sign": sign,
    "client": "fanyideskweb",
    "product": "webfanyi",
    "appVersion": "1.0.0",
    "vendor": "web",
    "pointParam": "client,mysticTime,product",
    "mysticTime": e,
    "keyfrom": "fanyi.web",
}

url = "https://dict.youdao.com/webtranslate"
header = {
    "Referer": "https://fanyi.youdao.com/",
    "Cookie": "OUTFOX_SEARCH_USER_ID=-922898077@10.110.96.160; _ga=GA1.2.50793062.1656497331; OUTFOX_SEARCH_USER_ID_NCOO=1490949463.084894; search-popup-show=11-8",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
}
resp = requests.post(url, data=data, headers=header)

print(resp.text)

# 检测代码是否ok的结果(仅限于该案例)
# 如果正确, 返回的是一个被加密的字符串.
# 如果不正确, 返回的是{"code":1,"msg":"request error"}

二. URLEncode和Base64

在我们访问一个url的时候总能看到这样的一种url

https://www.sogou.com/web?query=%E5%90%83%E9%A5%AD%E7%9D%A1%E8%A7%89%E6%89%93%E8%B1%86%E8%B1%86&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=3119&sst0=1630994614300&lkt=0%2C0%2C0&sugsuv=1606978591882752&sugtime=1630994614300

此时会发现, 在浏览器上明明是能看到中文的. 但是一旦复制出来. 或者在抓包工具里看到的. 都是这种%. 那么这个%是什么鬼? 也是加密么?

非也, 其实我们在访问一个url的时候. 浏览器会自动的进行urlencode操作. 会对我们请求的url进行编码. 这种编码规则被称为百分号编码. 是专门为url(统一资源定位符)准备的一套编码规则.

一个url的完整组成:

scheme://host:port/dir/file?p1=v1&p2=v2#anchor

http  ://www.baidu.com/tieba/index.html?name=alex&age=18
    参数: key=value
    服务器可以通过key拿value

此时. 如果参数中出现一些特殊符号. 比如'=' 我想给服务器传递a=b=c这样的参数. 必然会让整个URL产生歧义.

所以, 把url中的参数部分转化成字节. 每字节的再转化成2个16进制的数字. 前面补%.

看着很复杂. 在python里. 直接一步到位

# 一个完整的url是什么样子的

# URL:  统一资源定位符 -> 访问互联网上的某个资源的(电影)
# http://www.baidu.com/
# https://www.baidu.com/
# 协议://域名:端口/虚拟路径/虚拟路径/资源?参数名=参数值&参数名=参数值#锚点

# 域名 -> ip地址
# 端口 -> 应用程序

# 使用http协议. 给百度的服务器上的某个应用程序发送消息.
# 在我们眼里. 服务器 -> 运行在某一台机器上的应用程序.

# 假设需求是访问'樵夫'机器上的`一部电影`
#  `协议://域名:端口` 已经可以访问到樵夫的程序了.
# 樵夫的机器上可能会有好多部电影.
# `协议://域名:端口/日韩/2003/天龙八部.html?username=peiqi&password=123456#第五章`


# 如果传递的参数是: 用户名是: `p&1=1`
# `协议://域名:端口/日韩/2003/天龙八部.html?username=p1=1&password=123456#第五章`

# 在url中. 有些符号是不能做为参数.
# 浏览器就发明了一个东西. 叫urlencode. 处理的是url的参数.
# 浏览器会自动的把参数中的内容. 进行urlencode计算.
# 计算的结果. 会把特殊符号(中文)进行转义. 转义成%两位的十六进制数字 (%XX)表示一个字节
# `协议://域名:端口/日韩/2003/天龙八部.html?username=p%4D1%F3&password=123456#第五章`
# 中文 -> 字节(gbk, utf-8) 2-3个字节
# %4D1%F3 => 转化成正常的字符串, 是几个字符?

# python如何进行urlencode

from urllib.parse import quote, unquote, quote_plus, unquote_plus, urlencode

# params = {
#     "username": "樵夫"
# }
# # # 只能处理字典. 用不到..
# # ret = urlencode(params)
# # print(ret)
#
# # 上面这个urlencode咱用不到. 因为在requests模块里面. 帮你完成了urlencode
# # requests.get(url, params=params)

# 我们聊它的原因是: 在某些场景下. 需要对cookie的内容进行urlencode.
# 我们一般情况下是: 用正常的cookie进行访问. 也是不需要的. 但是, 有的时候. cookie的某个值是一个b64
# 在传递的时候. 需要对b64字符串进行urlencode操作.才能使用.
# 看不出来...
# 注意, 你计算好的cookie的值.
# 能用就正常用. 用不了的时候. 可以把urlencode作为备选方案.


# # # %E6%A8%B5%E5%A4%AB
# # print(quote("樵夫"))
#
# s = "%E6%A8%B5%E5%A4%AB"
# print(unquote(s))  # 樵夫

# print(quote("a /b/c=", safe=""))  # 传递safe=""  可以保持和浏览器一致
# print(quote_plus("a /b/c="))

解码

s = "http://www.baidu.com/s?wd=%E5%A4%A7%E7%8E%8B"
print(unquote(s))  # http://www.baidu.com/s?wd=大王

base64其实很容易理解. 通常被加密后的内容是字节. 而我们的密文是用来传输的(不传输谁加密啊). 但是, 在http协议里想要传输字节是很麻烦的一个事儿. 相对应的. 如果传递的是字符串就好控制的多. 此时base64就应运而生了. 26个大写字母+26个小写字母+10个数字+2个特殊符号(+和/)组成了一组类似64进制的计算逻辑. 这就是base64了.

  • # base64:字节转化成字符串的编码规则
    # 规则是: 把字节处理成二进制, 进行六个01组合成一个新的字节. 把新的字节处理成十进制.
    # 然后去数组中进行映射
    
    # s = b"abc"  # =》
    # # 011000010110 001001100011
    # # 正常字节
    # # 01100001 01100010 01100011
    # # b64转换
    # # 011000 010110 001001 100011
    # # 24     22     9      35
    # arr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    # print(len(arr))
    
    # print(arr[24] + arr[22] + arr[9] + arr[35])
    # # YWJj
    
import base64

bs = "我要吃饭".encode("utf-8")
# 把字节转化成b64
print(base64.b64encode(bs).decode())

# 把b64字符串转化成字节
s = "5oiR6KaB5ZCD6aWt"
print(base64.b64decode(s).decode("utf-8"))

注意, b64处理后的字符串长度. 一定是4的倍数. 如果在网页上看到有些密文的b64长度不是4的倍数. 会报错

例如,

import base64

s = "ztKwrsTj0b0"
bb = base64.b64decode(s)
print(bb)


此时运行出现以下问题
Traceback (most recent call last):
  File "D:/PycharmProjects/rrrr.py", line 33, in <module>
    bb = base64.b64decode(s)
  File "D:\Python38\lib\base64.py", line 87, in b64decode
    return binascii.a2b_base64(s)
binascii.Error: Incorrect padding

解决思路. base64长度要求. 字符串长度必须是4的倍数. 填充一下即可

import base64

s = "ztKwrsTj0b0"
s += ("=" * (4 - len(s) % 4))
print("填充后", s)
bb = base64.b64decode(s).decode("gbk")
print(bb)

请注意.

由于标准的Base64编码后可能出现字符+和/,但是这两个字符在URL中就不能当做参数传递,所以就出现了Base64URL,下面是它们的区别:

1. Base64编码后出现的+和/在Base64URL会分别替换为-和_
2. Base64编码中末尾出现的=符号用于补位,这个字符和queryString中的key=value键值对会发生冲突,所以在Base64URL中=符号会被省略,去掉=后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上=把Base64字符串的长度变为4的倍数,就可以正常解码了。

我们的应对方案:
在处理base64的时候.如果遇到了没有+和/的情况. 可以采用下面的方案来替换掉+和/
b64 = base64.b64decode(mi, b"-_")
# 计算机的底层是 二进制.
# s = "樵夫喜欢唱歌"  # 转化成二进制 -> 十进制(十六进制) 数字
# 加密的算法: 数学公式 -> 计算出来的东西一定是乱序的.乱七八糟的样子
# s = 325416545475645676848978464894 => 二进制 => 分组(8bit为一组)byte
#  00001010 10101000 0101011 1111011 0001100 1101100
#  一堆字节. 这堆字节就是计算之后的结果.

# bs = b'\x1a\x03\xa5\xe6\x33'  # 转化成字符串. 是很蛋疼的. 没办法转. 乱的

# 网站在进行数据加密的时候.
# 加密完之后的数据需要网络传输....http协议发送字节是很蛋疼的玩意..
# 聪明的程序员发明了base64 可以以最小的代价把字节转化成字符串.
# 转化成字符串之后. 再进行http传输就会方便很多.
# base64:字节转化成字符串的编码规则
# 规则是: 把字节处理成二进制, 进行六个01组合成一个新的字节. 把新的字节处理成十进制.
# 然后去数组中进行映射

# s = b"abc"  # =》
# # 011000010110 001001100011
# # 正常字节
# # 01100001 01100010 01100011
# # b64转换
# # 011000 010110 001001 100011
# # 24     22     9      35
# arr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
# print(len(arr))
# # YWJj
# print(arr[24] + arr[22] + arr[9] + arr[35])

# base64的算法就是上面的逻辑
# 作用: 不论多么复杂(混乱)的字节, 都可以转化成`base64的字符串`, 方便数据传输和传递

# 标准库
import base64

# # python完成base64的计算
# b = b"abc"
# # 把字节处理成base64, 再处理成字符串
# r = base64.b64encode(b).decode()
# print(r)

# YWJj

#
# s = "YWJj"
# # 把base64的字符串处理成字节
# bs = base64.b64decode(s)
# print(bs)

"""
字节 => base64.b64encode().decode() => 字符串
字符串 => base64.b64decode() => 字节
"""

# # 特殊情况
# # base64的组成: A-Z a-z 0-9 + / =
# bs = b"\x01\x02\x44\x33\x12110"
# r = base64.b64encode(bs).decode()
# print(r)

# s = 'Z21kD9ZK1ke6ugku2ccWu-MeDWh3z252xRTQv-wZ6jddVo3tJLe7gIXz4PyxGl73nSfLAADyElSjjvrYdCvEP4pfohVVEX1DxoI0yhm36ytQNvu-WLU94qULZQ72aml6gP7DV2OhPDxjE_tACTadwYErIaL4Lvp9yIEN41RmjHKwTXMOBryJIi44zbiIym7iXK1VQoXVl3JTZf-L5yAWo3NPd5ZggpUC3w2KlKT1UOceQab_yqmnIfi1k39rBvuFPmEZOm_wOBX47Pj_tTU0SE_MDSU0mUHEuCtiCsKRngRPsB8j5f7qcV5_tXYnIBt2HffX61Kzq5Myb2QvZzOKtUiJfsPGqxldOQAleM7h2Co8knKyEGq4Z9Tq3hy2KhZiJejbYrZRq7AnasVcQ9N84bRdKbJUNPFmU9pG64N2mBdWKjqyCELQCRZnC0WrHO-um8BSRqoY_uSAbJiHeCypwYGWZm76sRP7EPZtEsrPbiZbMzHYgUJyHtQvJ71krXnh0pzvP3Pub50GJIcwVxNMq1u_VNAKlbhTiFV7bp9KIQ5wudB99OLtXgXdm9XmdGqIAQ60QR0g6WItU5p-FBrqZbiXqBtYq3irV6tljO_JHlsFv1V6pmHS1ZGH2U8U9ndr3bIfeFVZcCE8-rGbTG8O8REczovUUbg1gJj5S72RI-xeShMEnKLJriihW3UtYHjom7chXlGRK5E3kLvT2BuICFLjfKcoNv_NbMJDCr1Qu3VWpMmAd8VdijRK_rmHEPgq3rF158D6DYoHTmc0LENfcfyVgVsSDfyhekeueFnz0UyRdOujXs0XrCDIhui658FZUq-KTv3nRvWOzE3oMQAStqotDA5qp7BA3OYkXiO5zYpDYgR7lSiAPuOnyN3d0ZRJorrEQ-dWJWgkdhbVEdp_fGke7vUAC99Lo90vG1uvN60='
# s1 = s.replace("-", "+").replace("_", "/")  # 我们的办法
# bs1 = base64.b64decode(s1)
# bs2 = base64.b64decode(s, b'-_')  # 官方的办法来解决
# print(bs1 == bs2)

# # 你写的所有东西. 在计算机底层都是字节. 都可以转化成数字
# s = "樵夫".encode("utf-8")
# print(s)
# # b'\xe6\xa8\xb5\xe5\xa4\xab'
# # 字节还可以被转化成16进制的字符串
# # 每一个字节. 单独处理成一个两位的十六进制的数字

# ss = 'e6a8b5e5a4ab'
#       e6a8b5e5a4ab

import binascii
# #  把字节转化成十六进制的数字
# # b: 字节
# # 2: to
# # a: ascii
# # hex十六进制
# r = binascii.b2a_hex(s).decode()
# print(r)

# 把十六进制的数字转化成字节
# s = "e6a8b5e5a4ab"
# bs = binascii.a2b_hex(s)
# print(bs.decode("utf-8"))

# 从m3u8中看到的IV的值: 0x00000000000000000000000000000000
# 这个iv在正常计算的时候是16位的字节.
# 你需要把数字转化成字节

s = '00000000000000000000000000000000'

bs = binascii.a2b_hex(s)
print(bs)   # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
print(len(bs))  # 16


# b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# b'0000000000000000'   # 表示空字符
# 0 对应   0x30

print(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' == b'0000000000000000') # False

三. 对称加密

​ 所谓对称加密就是加密和解密用的是同一个秘钥. 就好比. 我要给你邮寄一个箱子. 上面怼上锁. 提前我把钥匙给了你一把, 我一把. 那么我在邮寄之前就可以把箱子锁上. 然后快递到你那里. 你用相同的钥匙就可以打开这个箱子.

条件: 加密和解密用的是同一个秘钥. 那么两边就必须同时拥有钥匙才可以.

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

测试网站: https://the-x.cn/cryptography/Aes.aspx

3.1 AES

pip install pycrypto  => 很多人装不上....
pip install pycryptodome => 可以装..
安装之后依然不好用.. 发现只有crypto   而Crypto用不了. 
去模块的安装目录. 改一下文件名, 把小写的c改成大写的C

如果报错vc++14.0

解决方案:

  1. 升级pip
  2. 降低你的python版本..(3.10)
  3. 去找whl文件 https://www.lfd.uci.edu/~gohlke/pythonlibs/
# AES加密
from Crypto.Cipher import AES

"""
长度
    16: *AES-128*
    24: *AES-192*
    32: *AES-256*
    
MODE 加密模式. 
    常见的ECB, CBC
    以下内容来自互联网~~
    ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。
    CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或或操作后再加密,这样做的目的是增强破解难度。
    CFB/OFB:实际上是一种反馈模式,目的也是增强破解的难度。
    FCB和CBC的加密结果是不一样的,两者的模式不同,而且CBC会在第一个密码块运算时加入一个初始化向量。
"""
aes = AES.new(b"alexissbalexissb", mode=AES.MODE_CBC, IV=b"0102030405060708")
data = "我吃饭了"
data_bs = data.encode("utf-8")
# 需要加密的数据必须是16的倍数
# 填充规则: 缺少数据量的个数 * chr(缺少数据量个数)
pad_len = 16 - len(data_bs) % 16
data_bs += (pad_len * chr(pad_len)).encode("utf-8")

bs = aes.encrypt(data_bs)
print(bs)

AES解密

from Crypto.Cipher import AES


aes = AES.new(b"alexissbalexissb", mode=AES.MODE_CBC, IV=b"0102030405060708")
# 密文
bs = b'\xf6z\x0f;G\xdcB,\xccl\xf9\x17qS\x93\x0e'
result = aes.decrypt(bs)  # 解密
print(result.decode("utf-8"))

# 安装的是pycrypto或者pycryptodome
# 使用的是Crypto

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
import binascii

ming = "我叫樵夫, 我很大.."
#
# # 1. 创建加密器
# # key, 秘钥, 长度必须是16(最常用), 24, 32位的字节.
# # mode,  加密模式,
# #   ECB,  可以没有iv
# #   CBC,  需要iv 的
# # iv, 必须在CBC模式下, 长度是16位字节
#
aes = AES.new(key=b'aaaaaaaaaaajjjjj', mode=AES.MODE_CBC, iv=b'uuuuuuuuuuuuuuuu')
#
# # 加密
# # 加密之前,需要对数据进行填充. 不填充会报错
data = pad(ming.encode("utf-8"), 16)
result = aes.encrypt(data)
print(result)  # b"\x9fv\xcf\x8f\x8d\xb2i\xff\x03\xaf\x96E\xbd\x13|\x12\xbd\xa5\x8a\x95\x1bi\x9e\x88\xc8:'0\x07u4J"
# # 下面这个处理方案是错误的. 因为加密之后的内容是杂乱无章的字节. 是乱序的. 是无法识别的
# # print(result.decode("utf-8"))  # ok  777   不ok 999
# # print(result)
#
#
# # 加密之后的内容想要传输....需要base64, hex
# # b64encode()  把字节进行b64的编码
print(base64.b64encode(result).decode())
#
print(result.hex())
print(binascii.b2a_hex(result).decode())
#
# # base64: n3bPj42yaf8Dr5ZFvRN8Er2lipUbaZ6IyDonMAd1NEo=
# # hex:    9f76cf8f8db269ff03af9645bd137c12bda58a951b699e88c83a27300775344a




# 解密的过程
s = 'n3bPj42yaf8Dr5ZFvRN8Er2lipUbaZ6IyDonMAd1NEo='
# 1.创建解密器.
aes = AES.new(key=b'aaaaaaaaaaajjjjj', mode=AES.MODE_CBC, iv=b'uuuuuuuuuuuuuuuu')
# 2. 把密文处理成字节(看好, 是b64还是hex)
data = base64.b64decode(s)
# 3. 解密
r = aes.decrypt(data)
# 4. 去掉填充
r = unpad(r, 16)
# 5. 还原成字符串.
print(r.decode("utf-8"))

3.2 DES

# DES加密解密
from Crypto.Cipher import DES

# key: 8个字节
des = DES.new(b"alexissb", mode=DES.MODE_CBC, IV=b"01020304")
data = "我要吃饭".encode("utf-8")
# # 需要加密的数据必须是16的倍数
# # 填充规则: 缺少数据量的个数 * chr(缺少数据量个数)
pad_len = 8 - len(data) % 8
data += (pad_len * chr(pad_len)).encode("utf-8")

bs = des.encrypt(data)
print(bs)
# 解密
des = DES.new(key=b'alexissb', mode=DES.MODE_CBC, IV=b"01020304")
data = b'6HX\xfa\xb2R\xa8\r\xa3\xed\xbd\x00\xdb}\xb0\xb9'
result = des.decrypt(data)
print(result.decode("utf-8"))
from Crypto.Cipher import DES, AES, DES3
from Crypto.Util.Padding import pad, unpad
import base64

#
# ming = "樵夫喜欢上天"
#
# # DES的key是8个字节
# des = DES.new(key=b'abcdefgh', mode=DES.MODE_CBC, iv=b'uuuuuuuu')
#
# r = des.encrypt(pad(ming.encode("utf-8"), 8))
# r = base64.b64encode(r)
# print(r)


mi = 'KNu/ZVt18Bsi+FTXdNLL1VOL6vPzwWGx'
des = DES.new(key=b'abcdefgh', mode=DES.MODE_CBC, iv=b'uuuuuuuu')
r = des.decrypt(base64.b64decode(mi))
r = unpad(r, 8)
print(r.decode("utf-8"))

对DES3的补充

from Crypto.Cipher import AES, DES, DES3
from Crypto.Util.Padding import pad
import base64
import binascii

# AES.new(key=b'', mode=AES.MODE_CBC, iv=b'')
# DES.new(key=b'', mode=AES.MODE_CBC, iv=b'')
ming = "我叫樵夫"
# 由于3DES要求key的前半部分和后半部分不能一致.
# 一般的网站的做法是 使用一个key, 计算它的md5(sha1) => 32 取其中的一半就可以了.
des3 = DES3.new(key=b'1234567812345670', mode=AES.MODE_CBC,
                iv=b'12345678')  # ValueError: Triple DES key degenerates to single DES
mi = des3.encrypt(pad(ming.encode("utf-8"), des3.block_size))
print(mi)  # b'\x02\x19)\x03\xa3\xab\\\x9c:\xc3\n\xf4\xb0\x03|\xc1'a
print(base64.b64encode(mi).decode())  # AhkpA6OrXJw6wwr0sAN8wQ==
print(binascii.b2a_hex(mi).decode())  # 02192903a3ab5c9c3ac30af4b0037cc1

四.非对称加密

非对称加密. 加密和解密的秘钥不是同一个秘钥. 这里需要两把钥匙. 一个公钥, 一个私钥. 公钥发送给客户端. 发送端用公钥对数据进行加密. 再发送给接收端, 接收端使用私钥来对数据解密. 由于私钥只存放在接受端这边. 所以即使数据被截获了. 也是无法进行解密的.

常见的非对称加密算法: RSA, DSA等等, 我们就介绍一个. RSA加密, 也是最常见的一种加密方案

4.1 RSA加密解密

4.1.1 创建公钥和私钥

import base64

from Crypto.Cipher import PKCS1_v1_5, AES, DES, DES3  # 导包
from Crypto.PublicKey import RSA  # 这个RSA我们看到的样子是 帮我们生成和管理公钥和私钥的...

# # 如何生成公钥和私钥 ....
key = RSA.generate(bits=1024)  # 生成私钥

# 生成的key是私钥...
with open("private.pem", mode="wb") as f:
    f.write(key.exportKey())  # 导出

    # 默认导出的格式是dem格式

# 用私钥可以生成公钥,  public_key() 拿到公钥
with open("public.pem", mode="wb") as f:
    f.write(key.public_key().exportKey())

print(key.export_key("DER"))  # 二进制编码   默认PEM

bs = key.export_key("DER")
import base64

print(base64.b64encode(bs).decode())  # 二进制编码 -> base64处理 -> base64字符串

print(key.export_key("PEM").decode()
      .replace("-----BEGIN RSA PRIVATE KEY-----", "")
      .replace("-----END RSA PRIVATE KEY-----", "")
      .replace("\r", "")
      .replace("\n", ""))  # 默认文本编码 -> 处理成字符串 -> 去掉头尾及换行符

4.1.2 加密

from Crypto.Cipher import PKCS1_v1_5  # rsa加密
from Crypto.PublicKey import RSA  # 加载key的
import base64

ming = "我喜欢上天"

# 导入的key可以是pem格式的秘钥, 或者是字节形式的秘钥都可以
f = open("rsa_public_key.pem", mode="r", encoding="utf-8")
pub_key = f.read()
f.close()

# 导入key
rsakey = RSA.importKey(pub_key)

# 创建加密器
rsa = PKCS1_v1_5.new(key=rsakey)

# 加密
mi_bs = rsa.encrypt(ming.encode("utf-8"))
mi = base64.b64encode(mi_bs).decode()
print(mi)

# # rsa加密的结果可能会不一样.. 解密的过程如果没问题. 得到的结果是相同的.
# # Cwx01IndwCNkn/aupsWjgSNuGaUgGIYvvIT+cUau8JgNI4ZCQiiJi/cIJQ7tYmcf4TkCeDlgWSM1FlZrB4wPvRi3ivjEZEIa2q0vwM3zm+Hd7gEYh/uRQ9jb8j07uXsXJ5ejvM9Vgm4hSalP8GXbS6f7i9rQea3nTAw9zC70HAoTxUo17YsJRh/bhZcTD8jjflAEpkhsZwpPvQ/LbLai262/5myNaiykIf2RV+VjfxkN8WM5aDcOELA4UQE8Oi9ORh6T1aWVjWH5pLdVmhHsViRtw12tRbVWyyUmOEldggYpDRd3S+mIcRFPcIamiNW7tnkZEg8OkGWKmdVN0xZ9mQ==
# # MblRaFJFIHX0XeI+NUfLDsTmEWWy8seBM4FnPnWztZOcvt9w9OjtgolxqKJyqowczIfzqOlKK72BZWOT3ntPcQqlD6wDI1OhDz7Pp0DWq8xQA16+2Mk7dNeL/zVTt6vhqHNc9R0llQ3UxwIrSSorOzxuxh6YBQWpb+/woMYPjWKQ0MTbgYB8p9aFwNjLbMwHHSChV3yjAydnn30TlZIXC0R4LraJjxGOmz8fbciFmQRq08nv/UVPIzKn0uJzItRGNT65N8mcW3vzq2AOIznoyXWY5ClQq4zB60tgFWziMCeLOrYFmMyEzUR3tNMMJxNNQn5TkBw8jLgmnhRCQJ+gFw==
# # Qjw1koDFhQKupnvc5tRJMUoN4F0ljtR7wshXrLfdqj6yTXPlpHnGx2yATvVFkqJM9BtR+HUgoL40sza5ETMSQnxLg+RK18XZ8VMy3DPhY+u8WP6KESFbSXTNYFJph3ASFzIcdTn0+5EflQ/o+9upLSzbV/O9ceGUOpgJSwkz0b9PI/Kxsc1uvOxrDideEldf3vqH2xEPPogaZNhCtfegkaAqMBliARH5X13rWo8xNcgjTnbd6jQ2psT5M6uLghNkUeonfRSk9rb5/W3iPu8VbN1IDMAMwAP6uJrF0bA+9fuH5MbVEQXJdtgHRRyH9hJJgU/IaHtNtpiS99lMHweA7Q==
#
# # 如果用的是没有填充的算法. 那么算出来的东西就是固定的. 这种情况用python除非你去自己写算法. 否则很难完成..
# # 如果同一个明文经过反复加密. 计算的密文是一样的. 考虑用js来完成逆向工作. 不要使用python来完成.

4.1.3 解密

# 解密的过程
f = open("rsa_private_key.pem", mode="r", encoding="utf-8")
pri_key = f.read()
f.close()

rsakey = RSA.importKey(pri_key)

rsa = PKCS1_v1_5.new(key=rsakey)

mi = "Cwx01IndwCNkn/aupsWjgSNuGaUgGIYvvIT+cUau8JgNI4ZCQiiJi/cIJQ7tYmcf4TkCeDlgWSM1FlZrB4wPvRi3ivjEZEIa2q0vwM3zm+Hd7gEYh/uRQ9jb8j07uXsXJ5ejvM9Vgm4hSalP8GXbS6f7i9rQea3nTAw9zC70HAoTxUo17YsJRh/bhZcTD8jjflAEpkhsZwpPvQ/LbLai262/5myNaiykIf2RV+VjfxkN8WM5aDcOELA4UQE8Oi9ORh6T1aWVjWH5pLdVmhHsViRtw12tRbVWyyUmOEldggYpDRd3S+mIcRFPcIamiNW7tnkZEg8OkGWKmdVN0xZ9mQ=="
# 解密, 第一个参数是解密内容, 第二个参数是 如果计算过程中出错了. 返回什么东西
ming = rsa.decrypt(base64.b64decode(mi), None)
print(ming.decode("utf-8"))


# 总结:
# 在逆向的时候, 我们怎么办?
# 第一套逻辑
# "010001"  =转换成十进制=>  65537
# setMaxDigits,
# RSAKeyPair,
# encryptedString
# 统一使用js来完成逆向.

# 另外一套rsa逻辑
# var o = new JSEncrypt;
# return o.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA5Zq6ZdH/RMSvC8WKhp5gj6Ue4Lqjo0Q2PnyGbSkTlYku0HtVzbh3S9F9oHbxeO55E8tEEQ5wj/+52VMLavcuwkDypG66N6c1z0Fo2HgxV3e0tqt1wyNtmbwg7ruIYmFM+dErIpTiLRDvOy+0vgPcBVDfSUHwUSgUtIkyC47UNQIDAQAB"),
# o.encrypt(e)
# 固定的js的第三方库 JSEncrypt
# 由于JSEncrypt是第三方js库.不能在浏览器以外的地方使用.
# 我们用的是node环境. 无法正常使用
# 选择一个平替库node-jsencrypt
# 也可以选择用python来完成该加密.


# print(0x10001)


# 网易的加密逻辑:
"""
1.明文
2.生成一个随机值() => i
3.对明文进行AES加密 => 固定秘钥 => g => 结果: encText
4.对encText进行AES加密 => 不固定的秘钥 => i => 结果: encText  => params
5.对变量i进行rsa加密. => 密文  => encSecKey


服务器:
拿到的参数有: params, encSecKey
encSecKey => rsa解密 => i
对params 进行AES解密(i) => encText
对encText 进行AES解密(g) => 明文
"""

五. 肝个案例看看

我们以https://www.endata.com.cn/BoxOffice/BO/Year/index.html为案例. 来完成该网站数据解密 OB混淆

分析:

image-20210907164633235

很明显, 该网站的数据是经过加密的. 接下来. 我们到Initiator里看看.

image-20210907164735018

image-20210907164858074

image-20210907165246405 image-20210907165539435

接下来就是逆向的过程了... 各位..还是看视频吧. 视频里有详细的分析过程.

给出完整破解代码

import binascii  # 二进制和ascii之间转换
from Crypto.Cipher import DES

def func(a, b, c):
    if b == 0:
        return a[c:]
    d = a[:b] + a[b+c:]
    return d


def process(data):
    e = int(data[len(data)-1], base=16) + 9
    f = int(data[e], base=16)
    data = func(data, e, 1)
    e = data[f:f+8]
    data = func(data, f, 8)
    jiemi(data, str(e), str(e))


def jiemi(data, key, iv):

    des = DES.new(key.encode("utf-8"), mode=DES.MODE_ECB)
    # de_text = base64.standard_b64decode(data)
    #              十六进制表示的二进制数据 -> 十六进制 -> 二进制
    print(binascii.a2b_hex(data))
    ee = des.decrypt(binascii.a2b_hex(data))
    print(ee.decode("utf-8"))


if __name__ == '__main__':
    data = """BB95CA71E232FD88B71C758B19BDDDCB2BCF1E7ACCDC3B8777E875407841A1CE202F0F0028269913ADDA6050FED043DC8656C0F9FB3364603D5FB9C3B361B750AA36C06FC5500480DC8B3A1E0A994600FC0CEF28AF89450FC46C6E078FF9D3AA063D0303BE4A7CC74A3C70516ACE9F8944D7540071DF7DE33DB23B02B5A76D6AF455117F429EA2926F52C76F69CB9317F644A93A80D43FDB778C5E5B619578CBA12D958DE67163EFA55E1DB4C28D3D541FB7A4C426E2B592399B08BD67A3A4A23A953620B07EA643D69E6C1E93E8FB1971F9B95F4A71201149F60EBF38B3B86C042D786710415370A8514FC981E85B5D3C5198ACB740232AB600EF42623AA67703D9E43755F11E8B8DB2B3F5BCF8E833CA1DE7C1FC5DE615B949F421A16077DC964E4080795A37CE998736638753D181CD5D4B09FCB28F1671837114A55BCEC34F3905E9266B17B8C0B1C958F7BC245F5360937F4C3FA97DA9396A4B94E16CFCFA6C190B7527EC4E5DAD68C7C2641FCFAA93064487EE495A3BA567747EA8B1A504B45C2D186A661A66A287790959BB63321C3EFC500A3EEE7C0B5488149D960235C8A1751E2F3995DF11689353FFB4F92EE1B2A9ED66D12AE8DDC8AAE8CC120BBA64D93AA7C041BB6B5B1F752386E3B26ED20CC564C9BA498BE1FE0EEC80DA48DEA7CD92A52EA4764B55FB2FDAB5CF4D680B3E428C5C002AD0A12EBD3BE329F063E0FBBF9BBEB76CAD8C9CD10E2FE25AD1A9BC341BDC9F790616A1920AA9990D582E153393CCAAF65F8F5669C843D469B7EC692F41B3CCFD3A98D63153E7BF96A8268D338787E888F530285B52183523122230AD9F175667CD5E27E0A231A3BE5A9C24A28674FC768A9BA2338C890189E9C2CA8E608AA2470288E6FEEC64BDB6D06AA5E10681A5111D5E1FEDADBD16F12DED12EE99E8FCA112B8EC1C9E3EA5435315A7D2D14A7B9BBF5137010AB2A95E66B8FFA1695ACD708488A5620E1F147BFD985BC8EA097C836EC412B8B816E3F5936A23D82056212DA93DFDA7F7F6FE588338CF96CAD0AABB217C4F1C8646E1EB9A277874B7DE1C756362596B949B87AA00E23E301C6DCBCDBE1C9454E0A227F80F08EA86BA75B06FB7FD817DB9AE2729A0034DDDE18A213E4682C37A326A0FEF977DCC96A035E126AC71C754574A4D8FE0FF093BC14E34069AAC4C32DAF11CD687FE5766681C2181EE9C98514BD7AF44D79C97183F0E8CF60618F9B99FBAE66B139C8A0FEAD488E947D92092B5A80908F38B2DD73DC19C19550CF589661B1D8160C3568B8616025047318F889531F9CABB7AACAC62E5EA16C3804CD168C67F019A48CB0910F1EE3A2460B84251DB754038DEFE707463CB65C1666DFB25937A0C4BE17872A2DDE2090EB5687D09CD51AAF652FBE33A240A3B1F28617701E6EF6E733914BB32B05D1C0649C4ECA69D3075830DA732ED54DBC1F6A687EB13AE7505AD33F363A98241A0D30FAC9EB41D4FD53153B9403A14B3F71DDD6065FF7477EEED35A7E0F3E1CE0D545564AD812752817D32E52E5B08694252A69F25100CCD275D6C38F8AA3933D4F3FED4B2760CA0367A4E3FC7A4F15295050135F46F03C79914ACFE571527EC4E5DAD68C7CD1661A0FC37A62FC5FD275EBC1499F8EEA8B1A504B45C2D1233CF886DB4DC387959BB63321C3EFC5ABE261E2D0FF0E1E49D960235C8A1751451E687EAEBB700564D50B0C195DE84DAB29B668B737EC7E459AC26FBC734F5D7444C19860D9015D64AA91ED197D825957C2338ED354BE34AEABACDB4C32BF3A592838630E4FF8EF4A26611780CDC0F1AB0239DBAD1978A7BFA1587473E68FE2EF89B1144E3B6A99712C80A8BE7AE349695B250FDE76A54375C166A8767D41D838EFB9CB504C23563EA9E8EC3D1E94BDEE0791A86F1B31E9C81F872D38EB7F3AAC2593FF32DFCAECE1AC88882ED99F52F6466B2F437236C3241D0CACB5BBDC801AC086E0B2C0B87CE8158C0830A834ACF18EED58E6FA8B0E3DDA66031DFE0EC3C28A5F8D9CD9CD3A27EBE2F1F054A4EC9A108F283A0EC6698B66C2B05D1820D6ACCF5CB326100BDE4AD2D6221A818378DD1A18DAFCED301A9A39B85216D0370A047B1714A115A1E08A44217EE0A74AC18550D15370350F125734177A12B0BF26D079490D3A554232A1C738006CEBADCD6568A8D75801DD6D90BF6C2D8620033A98E1F9B93B9DFDA7BCE69DAB9AB241402929475CEAF6A8159CB1665965CE332E769D44CBEDF6C2902BE7755BBFEC2AD16669799D723493F8D4F8160E0507FBFABE103381917445735209BDEF1B373DBF5B9003947C952DC4150CE58CA6A08C112946C3A0684250C92F9727B92ED109394D4DB572F2324E01AE7D448A841D256D83360C70027E90F3EFF227D949E50B9F3C44141F66FDBE0413B43CF26912BEA8190633B2256927123E4E0CA56BADE27CF6A452AEBC574ED2CF00ADAC31534ECB8B8F7583E26126294025CFDA81D9B51453D2DEE57E0DAA501CE77328A5A3DE42966204659481C827CD0AD28EB502E0AF1DDC018155FD23CD6B34E395D1DC1492ED15DCCF4F2E9D5787B1A9CA775BA0AABFCAE959E9CE343BE15B3868761E6AC670E745C47A3CA77C8010CE5DF12FBA1FF47D1F3111502835E520BF1D4D07F7C589D6DBAEBBC5C3CA6BDE40A910A8FD98F1CDB7C1032DB93A4A4603950D9B2BC2EDB301F3B8A3733803B0363696805FA0C890189E9C2CA8E608AA2470288E6FEEC64BDB6D06AA5E10B9960959DB04E0FC6B4AE42919E6362D9E8FCA112B8EC1C9E3EA5435315A7D2D41F74C4DC73C4C32AB2A95E66B8FFA1677B8260867324907A601F44EB2F37C7BCF00ADAC31534ECB8B8F7583E26126294025CFDA81D9B514E24A4786C16FAE911CE77328A5A3DE42966204659481C827CD0AD28EB502E0AF1DDC018155FD23CD6B34E395D1DC1492ED15DCCF4F2E9D57017B8E2FD4ACA194414AC4306FFDDD0ECBAEEBD0DD9BEE40672653571AB681E1DD4BB5C3895A3AD65890B8F97FBF9C88E520BF1D4D07F7C589D6DBAEBBC5C3CA0AA59350F5B06A9CF1CDB7C1032DB93A600F905CAFDCC89426064368226E0398DA90A11148933932FBAE66B139C8A0FE40A1074779B3381A5A80908F38B2DD73A95E97608666FC205A5816B287D14A95616025047318F88974AB0F6F328BA18C2E5EA16C3804CD16649D903C38FF661E0F1EE3A2460B84251DB754038DEFE707463CB65C1666DFB25937A0C4BE17872A4D4793FB5D321EA59CD51AAF652FBE33A240A3B1F28617701E6EF6E733914BB32B05D1C0649C4ECA69D3075830DA732ED54DBC1F6A687EB113EF467AD942DE82433568E6C35F7F8E69C80EBCE3D0525690FE7F11E65866B52618E95AFC9AEAA6AC154CFAE9BAE61312752817D32E52E55B34B1FF2C048783FF73DCB5363871110B3326011985285A42FD2256723731435B9789386DF396F4B7E907756E3EB3BA14D41724B9938BE3E08E5416A728F2A1D9AD54738DA5F74F8CE3A2504A481D81616025047318F889FF5C244FEF7A458AAEC30C56BF88A048AB2A95E66B8FFA1677B8260867324907E1F147BFD985BC8EA097C836EC412B8B816E3F5936A23D825DB7BDA9B61D75577F6FE588338CF96CAD0AABB217C4F1C8646E1EB9A277874B7DE1C756362596B949B87AA00E23E3015D490874901DE069CC9E784D7C8167F87E9705A623E1DC9E4506DE4DE279B83475D14D9CD01A927321BFB61307DBAB51035E126AC71C754574A4D8FE0FF093BCDA0F829386AA0119AF11CD687FE576661F4C9E198A9BF44453B9BCD8DDF4F9DFAF4CD6FADF2A0EFDFBAE66B139C8A0FEB5298E04873FAF465A80908F38B2DD7346A3AB0F1D88AE6A3C7E4B72445DF2059E8FCA112B8EC1C991F981BCD6E9013914283CA7809B0D10AB2A95E66B8FFA1677B8260867324907E1F147BFD985BC8EA097C836EC412B8B816E3F5936A23D82242CAC4E1D58DFB67F6FE588338CF96CAD0AABB217C4F1C8646E1EB9A277874B7DE1C756362596B949B87AA00E23E3015D490874901DE0696DFA53B227EFCFEFDD39AE8E57EB577B9176ED83D6431466D9F3E9DB9C0C28549CBDCBF30D9D2825035E126AC71C754574A4D8FE0FF093BCCCEFCB727BA620B2AF11CD687FE57666F3633A86CF7124A5E782C0A0236A3B4AAE8E269FE0E302F8FBAE66B139C8A0FEAD488E947D92092B5A80908F38B2DD73633125B1DBA8CA0390DFF227F8D5F0D4270F87A346DD8D7777F104C4E22319AB41F74C4DC73C4C32AB2A95E66B8FFA1677B8260867324907E1F147BFD985BC8EA097C836EC412B8B816E3F5936A23D82ED937131BC18FCAF7F6FE588338CF96CAD0AABB217C4F1C8646E1EB9A277874B7DE1C756362596B949B87AA00E23E3015D490874901DE069AC018A4AF210397DEC31572E1772A5DE68760C413C81BC650E3A2546AAE47214A0723D94F61CDFB6035E126AC71C754574A4D8FE0FF093BC3CE90A942BC9A267AF11CD687FE57666F8AA317690E44ED5F1F5535D6515E747EE0A07D93CEB4AF2B7E907756E3EB3BA17664C4C9DEFDE32A7056D8B191D39B37EBDC765807597018950115302DD3A9B616025047318F889D00A2A799AA39A690A256C4D657A2CB7AB2A95E66B8FFA1677B8260867324907E1F147BFD985BC8EA097C836EC412B8BEDF7348D88FA841C4DBCF56EA2F0D9037F6FE588338CF96CAD0AABB217C4F1C8646E1EB9A277874B7DE1C756362596B949B87AA00E23E3015D490874901DE069925BBE4AABDDC6D5B1C4CE3F72F804EF9BF7363B37B6F6D8577AAFD3D9FB3D2786D6B026B51B877A035E126AC71C754574A4D8FE0FF093BCF07C1B608B727A0CAF11CD687FE57666E1E117A1853A9FB55DF08E0D036D7D917A70F27FFD1D2239E8158C0830A834AC70FA36D3655C5BBE9BF05D89E933E74BC28A5F8D9CD9CD3A65DE8659196B73AF959BB63321C3EFC5ABE261E2D0FF0E1E49D960235C8A1751451E687EAEBB7005730CE729EB0318CAAB29B668B737EC7E459AC26FBC734F5DBE252FF94BDA41E675192671BFA50C0857C2338ED354BE34AEABACDB4C32BF3A592838630E4FF8EF4A26611780CDC0F1AB0239DBAD1978A7BFA1587473E68FE234BC03311BE64FDD8D16CB98901ACA1CF18367A85B5C7EE32D4E08C530A378B48687343D377740591410021C7FECFB1EEE0791A86F1B31E920E6CE797374C688E4046D6659B5E7BBE1AC88882ED99F523390FB264826B70FFCECC22A8B51BEAAD5C7DB122168126B2F9727B92ED10939E1ED793B9BDCCCFB125285EA628230F583360C70027E90F3894262EC2FDBF79B9A108F283A0EC669E4418550BB47F7E5ACCF5CB326100BDE6B26049D936BE2CC3FFB4F92EE1B2A9ED66D12AE8DDC8AAE8CC120BBA64D93AA7C041BB6B5B1F75292BBAC28FEDD8FED4C9BA498BE1FE0EEC80DA48DEA7CD92A52EA4764B55FB2FDAB5CF4D680B3E428C5C002AD0A12EBD3BE329F063E0FBBF988D38205D195E158B41152D0A44EF17D2DD62B7E16212818EC0BAD6C5663DD1404D9C33BCB15712659E45B7EB74488D71B3CCFD3A98D6315A8816B73919ACD39100AC0519A6E42F22183523122230AD984CBBC8A33A398F0D4038C5799D60BB374E859046B362BA3EF84BB56F1426FB38A4BB1B673C1CD4A12A420C6EDFE64492A170AA4626ACA1490D5A3802BB6C5D8A64F836F68F8CECC2B072969E237DB1E0D43FDB778C5E5B639806A514B4665F667163EFA55E1DB4C28D3D541FB7A4C426E2B592399B08BD67A3A4A23A953620B07EA643D69E6C1E93E8FB1971F9B95F4A71201149F60EBF38B3B86C042D786710415370A8514FC981E85B5D3C5198ACB740232AB600EF42623AA67703D9E43753A2FEF37ADE3045C6C07652F260125EA7CD253EA717EE222E8A6AE60FF02A6A29C10AC9D0F134D52753D181CD5D4B09FCB28F1671837114AE8FD05DDB01347D966B17B8C0B1C958FCE712FF5C4F39582EF84BB56F1426FB38A4BB1B673C1CD4A5905CCDF47FB190C2A170AA4626ACA1459901C16526A7DB16DDC02368BA29D342629FE793C9285D30D43FDB778C5E5B6263F20252F7EAE10F6A452AEBC574ED2CF00ADAC31534ECB8B8F7583E26126294025CFDA81D9B514033E1DA16E0D6E2EDAD574660443C7B9966204659481C827CD0AD28EB502E0AF1DDC018155FD23CD6B34E395D1DC1492ED15DCCF4F2E9D5787B1A9CA775BA0AA301F074A41B09C10F499115961E8BCBA2E4C2BCD5E02C1AFB278FAAD8A5C1B8F378F43386163FFF6E520BF1D4D07F7C589D6DBAEBBC5C3CA58F4666BE22C60CBF1CDB7C1032DB93A68D5FD9E0242B2AAC4AE55BF4518A9094FA25D978F37D88698C3646292167449B7E907756E3EB3BAABAD6BCC780E4D722EF8636B8207C789BE65F9F20C180B36560EFDC6845569CA616025047318F889B5627A35D6ADA7552E5EA16C3804CD16FF3CBBE1641ED1060F1EE3A2460B84251DB754038DEFE707463CB65C1666DFB25937A0C4BE17872A4D4793FB5D321EA59CD51AAF652FBE33A240A3B1F28617701E6EF6E733914BB32B05D1C0649C4ECA69D3075830DA732ED54DBC1F6A687EB113EF467AD942DE82433568E6C35F7F8E34DB487999A43722A42612F9C87B74E86F4C225322A6B95F5FA8DBAFEC6A86FA12752817D32E52E526A256CAE0BDF92C489A3C26DF4611660B3326011985285AD85AEFD6FDFB3CF014BB6EACD809D727E8158C0830A834ACF18EED58E6FA8B0E3DDA66031DFE0EC3C28A5F8D9CD9CD3A8A6A2F55AC3BAC2B959BB63321C3EFC5D7DBBCEDAB91121F49D960235C8A1751451E687EAEBB7005730CE729EB0318CAAB29B668B737EC7E459AC26FBC734F5D7444C19860D9015DC9BE46BA8C7AA43757C2338ED354BE34AEABACDB4C32BF3A592838630E4FF8EF4A26611780CDC0F1AB0239DBAD1978A7BFA1587473E68FE29D9822CEEBA91EB0232F1980678945BEB80F1813FBCCC379C0575B46E785F9C887830B3F723381B9B30515FC666EB8DBEE0791A86F1B31E940EA8BBAA6A33A3C1B5DDC9FB5F858DFE1AC88882ED99F52732E823ABD0B99266196CF3C68AEB770527EC4E5DAD68C7C8A0DBC920C874289DE5AB4E2D55D4804EA8B1A504B45C2D1A4E0A78E4F53A9BBEBB7602D39E849606B948890C7374840DDAC9159048DF564649D903C38FF661E0F1EE3A2460B84251DB754038DEFE707463CB65C1666DFB25937A0C4BE17872A1812F285E1956F769CD51AAF652FBE33A240A3B1F28617701E6EF6E733914BB32B05D1C0649C4ECA69D3075830DA732ED54DBC1F6A687EB13AE7505AD33F363AF7F3C0B0F2BF265A576BA40BC1FF1F1C5E4031404E60275802625CFB9A91F08C3E1CE0D545564AD812752817D32E52E57890E43643956C90FF73DCB5363871117929489C3A49D2D69636606C0DE4CBE0EF84BB56F1426FB38A4BB1B673C1CD4A150A5AC4F5C91398F045BE8E4E7FF45C1DF7DE33DB23B02BDB684E633911D9143C44141F66FDBE0485325C6E1516D547190633B225692712EAAD58F14667BB31AB19346E5E7CB67A2C89DC7AF56F53B5047B1714A115A1E08A44217EE0A74AC1D4CC67E203D866CD5734177A12B0BF26D079490D3A554232A1C738006CEBADCD6568A8D75801DD6D90BF6C2D8620033A98E1F9B93B9DFDA7BCE69DAB9AB24140D712C263959F3D86F9C82368F6F563DFA4AA1A75046DEB07C93B9FC3258E0F733B1B0DDFD60DECBDD4F8160E0507FBFABE1033819174457387C72C91CC1FC9FB5B9003947C952DC4A97202EF1F394DC78B1913BB7F73668B9AF5A21903708BB0FF9D3AA063D0303B10DBACD99CE24DE49A795002212705C51DF7DE33DB23B02B95F0FE9899782C0C3C44141F66FDBE04E5FE403075DAF970190633B2256927120AD527349FD5BE3EDD1A18DAFCED301A9A39B85216D0370A047B1714A115A1E08A44217EE0A74AC176B2205F658155AA5734177A12B0BF26D079490D3A554232A1C738006CEBADCD6568A8D75801DD6D90BF6C2D8620033A98E1F9B93B9DFDA7BCE69DAB9AB241404FFFE180CA04A1CA1D5799E281A0D976F5F87CDC93940A3793CDD54147241E24A6ECD683DD48F6CED4F8160E0507FBFA59685C3E9FE5919931FB5C1F26DE2EF65B9003947C952DC42B4B52B1E1422177E6367AD9F82A5B842F9727B92ED10939E1ED793B9BDCCCFBA285FDAF205A97C883360C70027E90F3E0AD10D8E244C5859A108F283A0EC6692300CA78EFB1C810ACCF5CB326100BDE7B7572132E8DD6EA8588DBEB2A9915DBC2956D781AF560F28CC120BBA64D93AA7C041BB6B5B1F7526670F160CA7248AA4C9BA498BE1FE0EEC80DA48DEA7CD92A52EA4764B55FB2FDAB5CF4D680B3E428C5C002AD0A12EBD3BE329F063E0FBBF988D38205D195E158A51161601072E7BA6A7BF4BC01A6AB1599A59972BAA5DB345C7E6F624CC7A91F873188C48AFE02DE1B3CCFD3A98D6315F948D1D944E2EC7B5D3519A652388EC32183523122230AD93EAE96299EFE09E550E11D244658141DA4AE42E98DFAB55B0CE49AECE56C95AEB7E907756E3EB3BAABAD6BCC780E4D722EF8636B8207C789F5CF931C25657252CD0B80599D612B38616025047318F889EAF442FB833A0DE4D8FABE15A561310AAB2A95E66B8FFA162AD4D9A4CA339BC004D421F162767523A097C836EC412B8B816E3F5936A23D8235FE44809B75EFA57F6FE588338CF96CAD0AABB217C4F1C8646E1EB9A277874B7DE1C756362596B949B87AA00E23E3015D490874901DE06900B47779801159ED09DAA66235343ED7E62F9F2105AB5E75A53CBE5B02CC27A359B1B7273C79375A035E126AC71C754574A4D8FE0FF093BCC69CBC4ACA3FE291AF11CD687FE57666D2135483AAC3969BB68E075C73055510FF9D3AA063D0303BE4A7CC74A3C70516CB8FD925E00C28A21DF7DE33DB23B02BE430FEAEE9D5BE853C44141F66FDBE04826FA7F6BAA451BD190633B22569271216F372A5CAC4C59AF6A452AEBC574ED2CF00ADAC31534ECB8B8F7583E26126294025CFDA81D9B5141C691927E798BE3DE10CE475A92E59EF966204659481C827CD0AD28EB502E0AF1DDC018155FD23CD6B34E395D1DC1492ED15DCCF4F2E9D5787B1A9CA775BA0AA2796E63311615D51EB98E4DA5BDD38E1126CF3D39CA1C6D999110275DACABA9E8220DC566AE0AEF5773B9E0DE18A163ED833C30B73D5FE1FF3A78353E3239B7DD03434631610158B"""
    process(data)

posted @ 2023-03-11 15:55  凫弥  阅读(524)  评论(0编辑  收藏  举报