(干货)对称加密1.2非对称加密1.3信息摘要算法/签名算法
前言
现在前端开发为了提高爬虫的难度及加强安全性,都会在数据包提交前进行加密,最典型的就是传参加密,相信大家在测试的时候都遇到过,那么我们在抓取数据包并修改之后,修改之后的参数无法通过后端程序数据完整性的校验,就无法进行进一步测试。如果我们逆向解析出加密的过程,就可以模拟出相同的密文,通过后端接口的校验。
最近由于工作需要,在搜索资料的时候,学到了很多爬虫大佬们的关于JS逆向、APK逆向、代码分析等方面的经验和技巧,后续会分部分记录并总结下来。
0x01 常见加密算法
比较简单的base64、hex等这些编码就不再说了。
1.1 对称加密1.2 非对称加密
<pre class="code-snippet__js">两种加密算法常见结合套路:
`1、随机生成密钥2、密钥用于AES/DES/3DES加密数据
3、RSA对密钥加密`4、提交加密后的密钥和加密后的数据给服务器
</pre>
1.3 信息摘要算法/签名算法0x02 JS逆向流程以登录为例的基本流程:
如果网页有跳转,必须勾选preserve log(F12-Network)防止丢包
看一下有没有框架,右键查看框架源代码(弹出式登陆界面)
登陆尽量使用错误密码,防止跳转
查看关键登陆包,分析哪些参数是加密的
使用别的浏览器分析哪些参数是固定的值
初步猜测加密的方法
搜索
找到加密的地方(重点)
进行代码调试
找出所有的加密代码
0x03 实例操作
打开网站,抓包
修改个数据,比如修改num为1000
有些杠精该说了:我渗透测试改这有毛用。
是没用,只是以此为例。。。
可以看到,修改过参数数据后,后端数据校验之后不合法,所以没有返回数据。
打开测试网站 -> F12控制台 -> 切换至 XHR
这里我们看下需要做的有什么?
请求中有token(t明显是时间戳),token和数据不匹配后端不返回数据
返回的数据是加密的,需要解密
接下来我们就定位具体的加密函数和解密函数。
按照上面提到的流程步骤
打开控制台 -> source ->搜索
搜索加密参数名 token
根据搜索结果的文件名判断,基本上就是第二个文件,点击打开
token的生成代码
<pre class="code-snippet__js" data-lang="javascript">var token = md5(String(page) + String(num) + String(timestamp));
</pre>
设置断点,刷新
成功进入断点js网页计数器,没毛病了
根据加密函数,编写脚本
可以看到,生成的token和URL中的一致,至此,加密部分完成。
解密部分同样的道理,搜索返回包中的参数,直接搜索list发现有点多,不太好观察,还有一种方法
可以看到数据部分html的id为ip-list,再次搜索
成功找到解密数据包的代码
设置断点,进一步确认
没毛病,可以看到decode_str后就开始出现我们需要的明文数据了,所以这里的 decode_str 就是我们要的解密方法。剩下就是分析代码,编写解密脚本了。
<pre class="code-snippet__js" data-lang="javascript">function decode_str(scHZjLUh1) {
` scHZjLUh1 = Base64"x64x65x63x6fx64x65"; key = '\x6e\x79\x6c\x6f\x6e\x65\x72';
len = key["x6cx65x6ex67x74x68"]; code = '';
for (i = 0; i < scHZjLUh1["x6cx65x6ex67x74x68"]; i++) { var coeFYlqUm2 = i % len;
code += window["x53x74x72x69x6ex67"]"x66x72x6fx6dx43x68x61x72x43x6fx64x65" ^ key"x63x68x61x72x43x6fx64x65x41x74") }
return Base64"x64x65x63x6fx64x65"`}
</pre>
先运行下看看
报错,提示Base64未定义,设置断点js网页计数器,找到Base64的具体代码
复制粘贴进代码,再次运行
艹,提示Windows未定义,根据流程,缺啥补啥,debug,找对应的值
可以看到分别对应的是String和fromCharCode
那就是调用了String.fromCharCode方法了,替换掉,再次运行
bingo~
成功解密获取到明文数据。
0x04 Python实现加密方法合集
关于上述第一部分的常见加密算法,GitHub有对应的仓库,直接可以用的
GitHub:
<pre class="code-snippet__js" data-lang="python"># -*- coding:utf-8 -*-
`import base64import rsa
from Crypto.Cipher import AESfrom Crypto.PublicKey import RSA
from pyDes import des, CBC, PAD_PKCS5from Crypto.Cipher import DES3
import hashlibimport hmac
class USE_AES: """
AES 除了MODE_SIV模式key长度为:32, 48, or 64,
其余key长度为16, 24 or 32 详细见AES内部文档
CBC模式传入iv参数 本例使用常用的ECB模式
"""
def __init__(self, key): if len(key) > 32:
key = key[:32] self.key = self.to_16(key)
def to_16(self, key):
""" 转为16倍数的bytes数据
:param key: :return:
""" key = bytes(key, encoding="utf8")
while len(key) % 16 != 0: key += b'\0'
return key # 返回bytes
def aes(self): return AES.new(self.key, AES.MODE_ECB) # 初始化加密器
def encrypt(self, text):
aes = self.aes() return str(base64.encodebytes(aes.encrypt(self.to_16(text))),
encoding='utf8').replace('n', '') # 加密
def decodebytes(self, text): aes = self.aes()
return str(aes.decrypt(base64.decodebytes(bytes( text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密
class USE_RSA: """
生成密钥可保存.pem格式文件 1024位的证书,加密时最大支持117个字节,解密时为128;
2048位的证书,加密时最大支持245个字节,解密时为256。 加密大文件时需要先用AES或者DES加密,再用RSA加密密钥,详细见文档
文档:https://stuvel.eu/files/python-rsa-doc/usage.html#generating-keys """
def __init__(self, number=1024): """
:param number: 公钥、私钥 """
self.pubkey, self.privkey = rsa.newkeys(number)
def rsaEncrypt(self, text): """
:param test: str :return: bytes
""" content = text.encode('utf-8')
crypto = rsa.encrypt(content, self.pubkey) return crypto
def rsaDecrypt(self, text):
""" :param text:bytes
:return: str """
content = rsa.decrypt(text, self.privkey) con = content.decode('utf-8')
return con
def savePem(self, path_name, text): """
:param path_name: 保存路径 :param text: str
:return:bytes """
if "PEM" in path_name.upper(): path_name = path_name[:-4]
with open('{}.pem'.format(path_name), 'bw') as f: f.write(text.save_pkcs1())
def readPem(self, path_name, key_type):
""" :param path_name: 密钥文件
:param key_type:类型 :return:
""" if 'pubkey' in key_type:
self.pubkey = rsa.PublicKey.load_pkcs1(path_name) else:
self.privkey = rsa.PublicKey.load_pkcs1(path_name) return True
def sign(self, message, priv_key=None, hash_method='SHA-1'):
""" 生成明文的哈希签名以便还原后对照
:param message: str :param priv_key:
:param hash_method: 哈希的模式 :return:
""" if None == priv_key:
priv_key = self.privkey return rsa.sign(message.encode(), priv_key, hash_method)
def checkSign(self, mess, result, pubkey=None):
""" 验证签名:传入解密后明文、签名、公钥,验证成功返回哈希方法,失败则报错
:param mess: str :param result: bytes
:param pubkey: :return: str
""" if None == pubkey:
pubkey = self.privkey try:
result = rsa.verify(mess, result, pubkey) return result
except: return False
class USE_DES: """
des(key,[mode], [IV], [pad], [pad mode]) key:必须正好8字节
mode(模式):ECB、CBC iv:CBC模式中必须提供长8字节
pad:填充字符 padmode:加密填充模式PAD_NORMAL or PAD_PKCS5
""" def __init__(self, key, iv):
if not isinstance(key, bytes): key = bytes(key, encoding="utf8")
if not isinstance(iv, bytes): iv = bytes(iv, encoding="utf8")
self.key = key self.iv = iv
def encrypt(self, text):
""" DES 加密
:param text: 原始字符串 :return: 加密后字符串,bytes
""" if not isinstance(text, bytes):
text = bytes(text, "utf-8") secret_key = self.key
iv = self.iv k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
en = k.encrypt(text, padmode=PAD_PKCS5) return en
def descrypt(self, text):
""" DES 解密
:param text: 加密后的字符串,bytes :return: 解密后的字符串
""" secret_key = self.key
iv = self.iv k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
de = k.decrypt(text, padmode=PAD_PKCS5) return de.decode()
class USE_DES3: """
new(key, mode, args, kwargs) key:必须8bytes倍数介于16-24
mode: iv:初始化向量适用于MODE_CBC、MODE_CFB、MODE_OFB、MODE_OPENPGP,4种模式
MODE_CBC
, MODE_CFB
, and MODE_OFB
长度为8bytes
`MODE_OPENPGP加密时8bytes解密时10bytes`` 未提供默认随机生成`` nonce:仅在 ``MODE_EAX`` and ``MODE_CTR``模式中使用`` ``MODE_EAX``建议16bytes`` ``MODE_CTR``建议[0, 7]长度`` 未提供则随机生成`` segment_size:分段大小,仅在 ``MODE_CFB``模式中使用,长度为8倍数,未指定则默认为8`` mac_len:适用``MODE_EAX``模式,身份验证标记的长度(字节),它不能超过8(默认值)`` initial_value:适用
MODE_CTR`,计数器的初始值计数器块。默认为**0**。
""" def __init__(self, key):
self.key = key self.mode = DES3.MODE_ECB
def encrypt(self, text):
""" 传入明文
:param text:bytes类型,长度是KEY的倍数 :return:
""" if not isinstance(text, bytes):
text = bytes(text, 'utf-8') x = len(text) % 8
text = text+b'0'x cryptor = DES3.new(self.key, self.mode)
ciphertext = cryptor.encrypt(text) return ciphertext
def decrypt(self, text):
cryptor = DES3.new(self.key, self.mode) plain_text = cryptor.decrypt(text)
st = str(plain_text.decode("utf-8")).rstrip('0') return st
def USE_MD5(test): if not isinstance(test, bytes):
test = bytes(test, 'utf-8') m = hashlib.md5()
m.update(test) return m.hexdigest()
def USE_HMAC(key, text): if not isinstance(key, bytes):
key = bytes(key, 'utf-8') if not isinstance(text, bytes):
text = bytes(text, 'utf-8') h = hmac.new(key, text, digestmod='MD5')
return h.hexdigest()
def USE_SHA(text):
if not isinstance(text, bytes): text = bytes(text, 'utf-8')
sha = hashlib.sha1(text) encrypts = sha.hexdigest()
return encrypts
if __name__ == '__main__':
aes_test = USE_AES("assssssssdfasasasasa") a = aes_test.encrypt("测试")
b = aes_test.decodebytes(a) rsa_test = USE_RSA()
a = rsa_test.rsaEncrypt("测试加密") b = rsa_test.rsaDecrypt(a)
des_test = USE_DES(b"12345678", b"12345678") a = des_test.encrypt("测试加密")
b = des_test.descrypt(a) des3_test = USE_DES3(b"123456789qazxswe")
a = des3_test.encrypt("测试加密") b = des3_test.decrypt(a)
md5_test = USE_MD5("测试签名") hmac_test = USE_HMAC("123456", "测试")
sha_test = USE_SHA("测试加密")`</pre>
参考资料
发表评论
热门文章
Spimes主题专为博客、自媒体、资讯类的网站设计....
一款个人简历主题,可以简单搭建一下,具体也比较简单....
仿制主题,Typecho博客主题,昼夜双版设计,可....
用于作品展示、资源下载,行业垂直性网站、个人博客,....
尘集杂货铺和官网1t5-cn
11月11日
[已回复]
希望主题和播放器能支持SQLite数据库,AI能多个讯飞星火