Python 爬虫逆向破解案例实战 (二):STEAM密码加密 (RSA) 逆向

从本篇博文开始,我们将继续去学习开发中经常用到的编码、消息摘要算法和加密算法方面的知识。作为开发者,掌握这些知识可以让我们在设计反爬虫时有更丰富的搭配。而作为爬虫工程师,掌握这些知识可以让我们在面对 奇怪 的字符串时能够更快地找到突破口。在学习和掌握了 js 加密及逆向之后,我们可以处理的爬虫问题如下:

(1) 模拟登录中密码加密和其他请求参数加密处理
(2) 动态加载且加密数据的捕获和破解

注意:博主的逆向文章重点不在是否登录成功上,而是在学习找寻到 js 算法加密和解密相关流程的编码与处理套路技巧,大幅度提升处理相关问题的效率。

加密在前端开发和爬虫中是经常遇见的。掌握了加密算法且可以将加密的密文进行解密破解的,也是我们从一个编程小白到大神级别质的一个飞跃。且加密算法的熟练和剖析也是很有助于帮助我们实现高效的 js 逆向。本篇博文先介绍一种 非对称的加密方式:RSA

1. 非对称加密与 RSA

1.1 介绍

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 的自然数中,除了 1 和它本身之外不能被其他数整除的数 (如97)。

  2. 互质关系:公因数只有 1 的两个数 (如15和16) 构成互质关系。人们总结了一些方法来判断两个数是否构成互质关系。

    1. 相邻的两个自然数构成互质关系,如 17 和 18。
    2. 相邻的两个奇数构成互质关系,如 7 和 9。
    3. 两个数之间,数值较大的数为质数时,两个数构成互质关系,如 97 和 50。
  3. 欧拉函数:在给定的条件 (正整数 n) 下,求小于等于 n 的正整数中,有多少个数与 n 构成互质关系。这个求值的方法就叫作欧拉函数,欧拉函数用 φ(n) 表示。假设 n 为 7,那么在 1 到 7 之间与 7 形成 互质关系的数有 1、2、3、4、5、6 这6个数,即 φ(n)=6。

  4. 欧拉定理:欧拉定理表明,如果两个正整数 n 和 m 构成互质关系,则 n 的 φ(m) 次方恒等于 1(mod n)。

  5. 模反元素:正整数 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 算法的公钥计算和私钥计算方面的知识了。
在这里插入图片描述

1.2 Python 实现 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

1.3 JavaScript 实现 RSA 加密

<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>

2. STEAM 网站逆向分析

点击 此处 进入到 STEAM 的主页面,输入Steam 帐户名称以及密码,如下图所示:
在这里插入图片描述
在键盘中按下 <Fl2> 快捷键或者是鼠标右键单击选择 检查(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 的定义,将它相关的代码全部复制过来,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 编码实现

接下来我们就是用 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基础的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!


在这里插入图片描述

    好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
    如果我的博客对你有帮助、如果你喜欢我的博客内容,请 点赞评论收藏 一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
 编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了 关注 我哦!

猜你喜欢

转载自blog.csdn.net/xw1680/article/details/110964404
今日推荐