爬虫逆向加密入门之MD5、URLEncode和Base64

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

一、一切从MD5开始

MD5是一个非常常见的摘要(hash)算法。其特点就是小巧、速度快、极难被破解。

所以,md5依然是国内非常多的互联网公司选择的密码摘要算法.。有如下特点:

  1. 这玩意不可逆. 所以. 摘要算法就不是一个加密逻辑.
  2. 相同的内容计算出来的摘要应该是一样的
  3. 不同的内容(哪怕是一丢丢丢丢丢不一样) 计算出来的结果差别非常大

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

hash(数据)  => 数字

  1. 密码
  2. 一致性检测  

md5的python实现:

from hashlib import md5

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

bs = obj.hexdigest()
print(bs)
图片[1]-爬虫逆向加密入门之MD5、URLEncode和Base64-不念博客

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

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

图片[2]-爬虫逆向加密入门之MD5、URLEncode和Base64-不念博客

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

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

from hashlib import md5


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

bs = obj.hexdigest()
print(bs)
图片[3]-爬虫逆向加密入门之MD5、URLEncode和Base64-不念博客

扩展; sha256

from hashlib import sha1, sha256
sha = sha256(b'salt')
sha.update(b'felix')
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)

请注意.

由于标准的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"-_")
© 版权声明
THE END