各种加密逻辑
在我们进行js逆向的时候. 总会遇见一些我们人类无法直接能理解的东西出现. 此时你看到的大多数是被加密过的密文.
一. 一切从MD5开始
MD5是一个非常常见的摘要(hash)逻辑. 其特点就是小巧. 速度快. 极难被破解(王小云女士). 所以, md5依然是国内非常多的互联网公司选择的密码摘要算法. 请求对象: url, method, header, body(page=2) scrapy 调度器. 为什么不直接存储请求对象.
- 这玩意不可逆. 所以. 摘要算法就不是一个加密逻辑.
- 相同的内容计算出来的摘要应该是一样的
- 不同的内容(哪怕是一丢丢丢丢丢不一样) 计算出来的结果差别非常大
在数学上. 摘要其实计算逻辑就是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)
我们把密文丢到网页里. 发现有些网站可以直接解密. 但其实不然. 这里并不是直接解密MD5. 而是"撞库". 就是它网站里存储了大量的MD5的值. 就像这样:
而需要进行查询的时候. 只需要一条select语句就可以查询到了. 这就是传说中的撞库. 如何避免撞库: md5在进行计算的时候可以加盐. 加盐之后. 就很难撞库了.
from hashlib import md5
salt = "我是盐.把我加进去就没人能破解了"
obj = md5(salt.encode("utf-8")) # 加盐
obj.update("alex".encode("utf-8"))
bs = obj.hexdigest()
print(bs)
扩展; sha256
from hashlib import sha1, sha256
sha = sha256(b'salt')
sha.update(b'alex')
print(sha.hexdigest())
不论是sha1, sha256, md5都属于摘要算法. 都是在计算hash值. 只是散列的程度不同而已. 这种算法有一个特性. 他们是散列. 不是加密. 而且, 由于hash算法是不可逆的, 所以不存在解密的逻辑.
二. 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里. 直接一步到位
from urllib.parse import urlencode, unquote, quote
# 单独编码字符串
wq = "米饭怎么吃"
print(quote(wq)) # %E7%B1%B3%E9%A5%AD%E6%80%8E%E4%B9%88%E5%90%83
print(quote(wq, encoding="gbk")) # %C3%D7%B7%B9%D4%F5%C3%B4%B3%D4
# 多个数据统一进行编码
dic = {
"wq": "米饭怎么吃",
"new_wq": "想怎么吃就怎么吃"
}
print(urlencode(dic)) # wq=%E7%B1%B3%E9%A5%AD%E6%80%8E%E4%B9%88%E5%90%83&new_wq=%E6%83%B3%E6%80%8E%E4%B9%88%E5%90%83%E5%B0%B1%E6%80%8E%E4%B9%88%E5%90%83
print(urlencode(dic, encoding="utf-8")) # 也可以指定字符集
# 一个完整的url编码过程
base_url = "http://www.baidu.com/s?"
params = {
"wd": "大王"
}
url = base_url + urlencode(params)
print(url) # http://www.baidu.com/s?wd=%E5%A4%A7%E7%8E%8B
解码
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了.
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)
三. 对称加密
所谓对称加密就是加密和解密用的是同一个秘钥. 就好比. 我要给你邮寄一个箱子. 上面怼上锁. 提前我把钥匙给了你一把, 我一把. 那么我在邮寄之前就可以把箱子锁上. 然后快递到你那里. 你用相同的钥匙就可以打开这个箱子. 条件: 加密和解密用的是同一个秘钥. 那么两边就必须同时拥有钥匙才可以. 常见的对称加密: AES, DES, 3DES. 我们这里讨论AES和DES
3.1 AES
# 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"))
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"))
四.非对称加密
非对称加密. 加密和解密的秘钥不是同一个秘钥. 这里需要两把钥匙. 一个公钥, 一个私钥. 公钥发送给客户端. 发送端用公钥对数据进行加密. 再发送给接收端, 接收端使用私钥来对数据解密. 由于私钥只存放在接受端这边. 所以即使数据被截获了. 也是无法进行解密的. 常见的非对称加密算法: RSA, DSA等等, 我们就介绍一个. RSA加密, 也是最常见的一种加密方案
4.1 RSA加密解密
4.1.1 创建公钥和私钥
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto import Random
import base64
# 随机
gen_random = Random.new
# 生成秘钥
rsakey = RSA.generate(1024)
with open("rsa.public.pem", mode="wb") as f:
f.write(rsakey.publickey().exportKey())
with open("rsa.private.pem", mode="wb") as f:
f.write(rsakey.exportKey())
4.1.2 加密
# 加密
data = "我要吃饭了"
with open("rsa.public.pem", mode="r") as f:
pk = f.read()
rsa_pk = RSA.importKey(pk)
rsa = PKCS1_v1_5.new(rsa_pk)
result = rsa.encrypt(data.encode("utf-8"))
# 处理成b64方便传输
b64_result = base64.b64encode(result).decode("utf-8")
print(b64_result)
4.1.3 解密
data = "e/spTGg3roda+iqLK4e2bckNMSgXSNosOVLtWN+ArgaIDgYONPIU9i0rIeTj0ywwXnTIPU734EIoKRFQsLmPpJK4Htte+QlcgRFbuj/hCW1uWiB3mCbyU3ZHKo/Y9UjYMuMfk+H6m8OWHtr+tWjiinMNURQpxbsTiT/1cfifWo4="
# 解密
with open("rsa.private.pem", mode="r") as f:
prikey = f.read()
rsa_pk = RSA.importKey(prikey)
rsa = PKCS1_v1_5.new(rsa_pk)
result = rsa.decrypt(base64.b64decode(data), gen_random)
print(result.decode("utf-8"))