从本篇博文开始,我们将继续去学习开发中经常用到的编码、消息摘要算法和加密算法方面的知识。作为开发者,掌握这些知识可以让我们在设计反爬虫时有更丰富的搭配。而作为爬虫工程师,掌握这些知识可以让我们在面对 奇怪
的字符串时能够更快地找到突破口。在学习和掌握了 js 加密及逆向之后,我们可以处理的爬虫问题如下:
(1) 模拟登录中密码加密和其他请求参数加密处理
(2) 动态加载且加密数据的捕获和破解
注意:博主的逆向文章重点不在是否登录成功上,而是在学习找寻到 js 算法加密和解密相关流程的编码与处理套路技巧,大幅度提升处理相关问题的效率。
加密在前端开发和爬虫中是经常遇见的。掌握了加密算法且可以将加密的密文进行解密破解的,也是我们从一个编程小白到大神级别质的一个飞跃。且加密算法的熟练和剖析也是很有助于帮助我们实现高效的 js 逆向
。本篇博文先介绍一种 非对称的加密方式:RSA。
1976年,计算机科学家 Whitfield Diffie 和 Martin Hellman 二人提出了新的加密方式,这种加密方式可以在不传递秘钥的情况下实现加密和解密操作,它利用的是两种规则之间的数学关系。与对称加密不同的是,这种方式需要用到两个密钥:公钥 (public key) 和 私钥(private key)。公钥和私钥是一对,如果用该公钥对数据进行加密,那么只有用对应的私钥才能够解密数据。反之,如果用私钥对数据进行加密,那么只有用对应的公钥才能够解密数据。由于加密和解密时使用的密钥是不相同的,所以这种加密方式被称为 非对称加密
。
在非对称加密算法中,应用最广泛、强度最高的是 RSA
算法,该算法由 Rivest、Shamire 和 Adleman 三人提出。RSA
算法的版本很多,RSA 2.0
版本的 RFC 编号为 2437,RSA 2.1
版本的 RFC 编号为 3447,RSA 2.2
版本的 RFC 编号为8017。
在开始学习 RSA算法
的原理之前,我们需要了解一些数学概念,如质数、互质关系、欧拉函数、欧拉定理和模反元素。如下:
质数:质数又称素数,定义为:在大于 1 的自然数中,除了 1 和它本身之外不能被其他数整除的数 (如97)。
互质关系:公因数只有 1 的两个数 (如15和16) 构成互质关系。人们总结了一些方法来判断两个数是否构成互质关系。
欧拉函数:在给定的条件 (正整数 n) 下,求小于等于 n 的正整数中,有多少个数与 n 构成互质关系。这个求值的方法就叫作欧拉函数,欧拉函数用 φ(n) 表示。假设 n 为 7,那么在 1 到 7 之间与 7 形成 互质关系的数有 1、2、3、4、5、6 这6个数,即 φ(n)=6。
欧拉定理:欧拉定理表明,如果两个正整数 n 和 m 构成互质关系,则 n 的 φ(m) 次方恒等于 1(mod n)。
模反元素:正整数 n 和 m 构成互质关系,那么一定可以找到整数 b,使得 nb-1 被 m 整除。整数 b 就叫作正整数 n 的模反元素。如 3 和 5 互质,那么 3×2-1 能够被 5 整除,所以整数 2 就是正整数 3 的模反元素。同理,3×7-1 也能够被 5 整除,所以整数 7 也是正整数 3 的模反元素。我们也可以理解为 2 加或减 m 的整数倍都是正整数 3 的模反元素,如 -14、-3、2、7、12 等,即 n 的 φ(m) - 1 次方就是 n 的模反元素。
了解了这些数学概念之后,我们就可以开始学习 RSA 算法的公钥计算和私钥计算方面的知识了。
在了解 RSA 加密和解密的原理之后,我们来学习如何使用 pycryptodome
库 (第三方库) 完成对数据的加密和解密。对应的 Python 代码如下:
# -*- coding: UTF-8 -*-
"""
@author:AmoXiang
@file:rsa加密及解密.py
@time:2020/12/10
"""
from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
message = "amoxiang" # 消息原文
# 初始化 RSA 对象
rsa = RSA.generate(1024, Random.new().read)
# 生成私钥
private_key = rsa.exportKey()
# 生成公钥
public_key = rsa.publickey().export_key()
# 打印私钥和公钥
print(private_key.decode("utf8"))
print(public_key.decode("utf8"))
# 将私钥和公钥存入对应名称的文件
with open("private.pem", "wb") as f:
f.write(private_key)
with open("public.pem", "wb") as f:
f.write(public_key)
with open("public.pem", "r") as f:
# 从文件中加载公钥
pub = f.read()
pubkey = RSA.importKey(pub)
# 用公钥加密消息原文
cipher = PKCS1_v1_5.new(pubkey)
c = base64.b64encode(cipher.encrypt(message.encode("utf8"))).decode("utf8")
with open("private.pem", "r") as f:
# 从文件中加载私钥
pri = f.read()
prikey = RSA.importKey(pri)
# 用私钥解密消息密文
cipher = PKCS1_v1_5.new(prikey)
m = cipher.decrypt(base64.b64decode(c), "error").decode("utf8")
print(f"消息原文: {message}\n消息密文: {c}\n解密结果: {m}")
这段代码生成一对公钥和私钥,并实现了对消息原文 amoxiang
的加密和解密。代码运行结果如下:
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC/p9WrWzXmXUomqLUxTM42YX/nWYTWXC1ZPHrcj2Emg46aRDN1
exMucgCfUBpwAZD9W0J/0Y9h7tuEkusIVJlMjUkNC9oFfOk62kn9M46IG/SPH3uz
KxtPDGI066vXXjH10aN4iWhbWrxN0C1kEdHH9Fsp3jtjjOHZFN9PrZnBxQIDAQAB
AoGACjIlEVkoXGmBHN5jyUwjOkxkkAu4n+jGdtVbWel8yx47k2Rmcm0KP7HL663I
wkWrD3dv6bndzWF2Jy7jtw09HHhLzCbtnXoX1cYtm1N+Hp6Kpv96cJD4Ymxcra6f
8qNs0MqgA3NwDeVL6U0GO6I+AZqN7YG/AgHE6sUCw8gthXECQQDKZ+JF+CyzFa+g
rvw4DOPq0TqLYKSPMbVagD5hBS52GOZF2nfl9jWzCLjK0bUfk5mtg3dsBLGFRDYr
adQ2BkHpAkEA8mc/NKA1/Mw/aWC9bFRdsOqITDB+hFiqRWNfjip6G1pIrQZYKnne
er4AX+PXJhuujDDxI6WM2xajwvsrb9gbfQJAH5Kq4j0/Q8Q7PDZvk5K3Ltbqaflu
UgPwBSkCEgJL6BIkQXs9vrp0T/QpV0H1HfLZQw7B3zCwPFiSlp0QhEjfmQJAOia+
qPdOPEkbZUJJ7vUGTOzWqcBweXtzzZWbVNWn2Wv9R1TgTcBSuQtft6FG+eNmKkeL
ccvDUMPLoXjz4K7tWQJBALLqVSGP9UCy3TTVLZkE/nGOWnvahoxCkF7qQPkvVSAr
TI9P2x5TLuP+pNFm5/SpZJX8QPncGpQt9vFTC4qHGFg=
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/p9WrWzXmXUomqLUxTM42YX/n
WYTWXC1ZPHrcj2Emg46aRDN1exMucgCfUBpwAZD9W0J/0Y9h7tuEkusIVJlMjUkN
C9oFfOk62kn9M46IG/SPH3uzKxtPDGI066vXXjH10aN4iWhbWrxN0C1kEdHH9Fsp
3jtjjOHZFN9PrZnBxQIDAQAB
-----END PUBLIC KEY-----
消息原文: amoxiang
消息密文: je+lsuluYHv9JCMJbhDIrpcBr6Wb675t/TP2XSeiWv4OtlDIVehY8Fip9zKor3u4sYUV95DZzNwSBizYT8o+1WMfYdBLJnhU75Tuu9UE6Vy7vdZOJceJRJ2rJOJZyGixtG4KjoTOyEKnXmsC5rqFUIQWwu1tXs9nnNv14uBPtHA=
解密结果: amoxiang
<html>
<script src="https://cdn.bootcss.com/jsencrypt/3.0.0-beta.1/jsencrypt.js">script>
<script type="text/javascript">
//公钥
var PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALyBJ6kZ/VFJYTV3vOC07jqWIqgyvHulv6us/8wzlSBqQ2+eOTX7s5zKfXY40yZWDoCaIGk+tP/sc0D6dQzjaxECAwEAAQ==-----END PUBLIC KEY-----';
//私钥
var PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvIEnqRn9UUlhNXe84LTuOpYiqDK8e6W/q6z/zDOVIGpDb545NfuznMp9djjTJlYOgJogaT60/+xzQPp1DONrEQIDAQABAkEAu7DFsqQEDDnKJpiwYfUE9ySiIWNTNLJWZDN/Bu2dYIV4DO2A5aHZfMe48rga5BkoWq2LALlY3tqsOFTe3M6yoQIhAOSfSAU3H6jIOnlEiZabUrVGqiFLCb5Ut3Jz9NN+5p59AiEA0xQDMrxWBBJ9BYq6RRY4pXwa/MthX/8Hy+3GnvNw/yUCIG/3Ee578KVYakq5pih8KSVeVjO37C2qj60d3Ok3XPqBAiEAqGPvxTsAuBDz0kcBIPqASGzArumljkrLsoHHkakOfU0CIDuhxKQwHlXFDO79ppYAPcVO3bph672qGD84YUaHF+pQ-----END PRIVATE KEY-----';
//使用公钥加密
var encrypt = new JSEncrypt();//实例化加密对象
encrypt.setPublicKey(PUBLIC_KEY);//设置公钥
var encrypted = encrypt.encrypt('amoxiang');//对指定数据进行加密
alert(encrypted)
//使用私钥解密
var decrypt = new JSEncrypt();
decrypt.setPrivateKey(PRIVATE_KEY);//设置私钥
var uncrypted = decrypt.decrypt(encrypted);//解密
alert(uncrypted);
script>
html>
点击 此处 进入到 STEAM
的主页面,输入Steam 帐户名称以及密码,如下图所示:
在键盘中按下
快捷键或者是鼠标右键单击选择 检查(inspect)
,打开浏览器开发者工具 (这里使用谷歌浏览器),然后在顶部导航条中选择 Network
选项,又由于登录一般都是使用的 ajax
请求,所以接着我们单击下方的 XHR
,在这些准备工作都做好之后,我们就可以点击页面中的 登录
按钮,监听请求的 URL
,如下图所示。
我们在全局当中对 password =
进行搜索,如下图所示:
打开我们的 JS 调试工具,进行 JS 代码的组合,代码如下:
function getPwd(password){
var encryptedPassword = RSA.encrypt(password, pubKey);
}
发现这里还需要一个参数 pubKey,所以我们还需要在当前文件中去查找 pubKey 这个变量,经过观察发现:
所以将代码进行下一步的改进:
function getPwd(password){
var pubKey = RSA.getPublicKey(publickey_mod, publickey_exp);
var encryptedPassword = RSA.encrypt(password, pubKey);
}
注意,我们这里是没有 results 的,所以要将它去掉,然后又观察发现我们这里没有 getPublicKey 函数 和 publickey_mod,publickey_exp 两个变量。我们挨个挨个来进行解决,首先找到 getPublicKey 函数,如下图所示:
紧接着我们要去找 publickey_mod,publickey_exp 两个变量的值,经过观察信息可以发现,这两个值的来源是在另一个 URL 请求中进行返回的,如下图所示:
为了测试,我们先进行复制,分别定义两个变量进行存储,如下图所示:
但是此时又发现了新的问题,所以此时我们需要去找到 BigInteger 的定义,将它相关的代码全部复制过来,如下图所示:
接下来我们就是用 Python 的第三方模块去执行上面找到的 JS 代码,示例代码如下:
# -*- coding: UTF-8 -*-
"""
@author:AmoXiang
@file:steam.py
@time:2020/12/11
"""
import requests
import execjs
# TODO 1. 获取 publickey_mod, publickey_exp
url = "https://store.steampowered.com/login/getrsakey/"
form_data = {
"donotcache": "1607651536919",
"username": "[email protected]"
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (K"
"HTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
}
response = requests.post(url=url, headers=headers, data=form_data) # 注意是post请求
data = response.json()
publickey_exp = data["publickey_exp"]
publickey_mod = data["publickey_mod"]
print(publickey_exp, publickey_mod)
# TODO 2. 加密
node = execjs.get() # 实例化一个对象
ctx = execjs.compile(open("./getPwd.js", "r", encoding="utf8").read()) # 编译
funcName = 'getPwd("{0}","{1}","{2}")'.format('amoxiang', publickey_mod, publickey_exp)
pwd = ctx.eval(funcName)
print(pwd)
至此今天的案例就到此结束了,笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习Python基础的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!
好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请点赞
、评论
、收藏
一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了关注
我哦!