JavaScript逆向之RSA算法
RSA算法
简介
RSA算法属于非对称加密,加密的密钥称为公钥,解密的密钥称为私钥,公钥和私钥不是同一个。公钥是可以放在外面的,给谁都可以;但是私钥不可以放在外面,只能服务器自己保留,如果私钥泄露了,数据安全将有极大的风险。
RSA的公钥和私钥是成对的,不能拆开。
python中的RSA
在python中主要有两个库支持rsa加密,只需要掌握一个就可以了,本文采用的是Crypto库中的RSA加密逻辑。
在网页的前端js处,RSA的加密逻辑有两种。
(1)No Padding:这种不推荐直接用python去搞,推荐直接用js库干。
(2)PKCS Padding:这种加密逻辑跟python中实现RSA的逻辑一致,可以直接用python搞。
在网页的前端js处,如果采用的是RSA算法,会有以下4种明显的特征:
(1)出现了十进制的65537,大概率是No Padding,建议选择用js来干。
(2)出现了十六进制的010001,其转换成十进制也是65537,大概率是No Padding,建议选择用js来干。
(3)出现了JSEncrypt关键词,这是js的一个第三方加密库,专门用来做RSA加密的,大概率是PKCS Padding,可以直接用python来干。
(4)出现了PublicKey关键词,这一定是RSA加密,如果传递的参数是一个base64字样的字符串,大概率是PKCS Padding,可以直接用python来干。
python实现RSA算法
如何生成公钥和私钥
点击查看代码
from Crypto.PublicKey import RSA # 这个rsa我们看到的样子是帮我们生成和管理公钥和私钥的
# 如何生成公钥和私钥
key = RSA.generate(bits=1024) # bits最小是1024,还可以是2048,3072
# 生成的key是私钥
with open("private.pem", mode="wb") as f:
f.write(key.exportKey())
# 默认导出的格式是pem
# 用私钥可以生成公钥
with open("public.pem", mode="wb") as f:
f.write(key.public_key().exportKey())
如何加密
点击查看代码
import base64
from Crypto.Cipher import PKCS1_v1_5 # 导包
from Crypto.PublicKey import RSA
# 如何加密
s = "我喜欢每天早上起来看NBA"
# 处理成字节
ming_bs = s.encode("utf-8")
# 加载公钥
f = open("public.pem", mode="rb")
pub_key = RSA.importKey(f.read())
f.close()
# 创建加密器
rsa = PKCS1_v1_5.new(pub_key)
# 加密
mi_bs = rsa.encrypt(ming_bs)
# 加密结果处理成base64
mi_s = base64.b64encode(mi_bs).decode()
print(mi_s)
运行结果:
如何解密
我们在网页上最常见的是RSA的加密逻辑,解密逻辑看看就行了,因为一般解密逻辑都在服务器处执行。
点击查看代码
import base64
from Crypto.Cipher import PKCS1_v1_5 # 导包
from Crypto.PublicKey import RSA
mi_str = "hz3uWcGAadmJA2478FxrcQJBdxUDk+3ys+ZjmxdxVhqUdb/nIS4OyNAnvrIxwL+rXTSs77P1CrtAoWf0FzYt/pJMzJcFShD3W3RnJ4mR0/iKQle5r75QqAMFQiaEhmvL6gYjfxsNSzEMmt1okmZdtiLhXgexpzRSCl74rx5378E="
# 创建解密器
# 获取私钥
f = open("private.pem", mode="rb")
pri_key = RSA.importKey(f.read())
# print(pri_key.public_key().export_key()) # 可以通过私钥计算出公钥
f.close()
rsa = PKCS1_v1_5.new(pri_key)
ming_bs = rsa.decrypt(base64.b64decode(mi_str), None)
print(ming_bs.decode("utf-8"))
运行结果:
RSA算法实战——某网校登录
url:https://user.wangxiao.cn/login
打开网址,可以看到该网站的登录需要进行图片验证码的校验。
既然需要进行验证码的校验,那么这个验证码肯定是跟你当前登录的这个cookie是绑定的,所以多点击下图片,看cookie是否会变化。
可以看到,cookie是不会变化的,所以如果想在这里实现登录的话,需要采用requests.session机制。接下来就要去解决图片验证码了。先看图片验证码会触发哪些流量。
就一条流量包,看它的相应内容。
可以看到响应的内容其实就是图片经过base64编码之后的内容,所以想要绕过验证码,第一步就是要拿到图片,第二步就是要识别图片中的验证码。
拿到图片比较简单,直接访问对应的url,拿到响应内容后进行base64解码,再写入图片即可;识别验证码需要用到一个第三方库叫做ddddocr
,安装命令pip install ddddocr
,但是该库的要求是python版本小于等于3.9。
识别验证码测试登录的python代码:
点击查看代码
import requests
import base64
import ddddocr
session = requests.session()
session.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 "
"Safari/537.36"}
# 一开始应该有个加载cookie的过程,一个session类比成一个客户端
# 用来加载第一个cookie
session.get("https://user.wangxiao.cn/login?redirect_uri=https://ke.wangxiao.cn/")
# 获取验证码
img_url = "https://user.wangxiao.cn/apis//common/getImageCaptcha"
img_resp = session.post(img_url,
headers={"Referer": "https://user.wangxiao.cn/login?redirect_uri=https://ke.wangxiao.cn/",
"Content-Type": "application/json;charset=UTF-8", })
img_base64 = img_resp.json()['data'].strip().split(",")[-1]
with open("tu.png", mode="wb") as f:
f.write(base64.b64decode(img_base64))
ocr = ddddocr.DdddOcr()
img_path = "tu.png"
# 读取图片
with open(img_path, 'rb') as f:
img_bytes = f.read()
# 识别验证码
result = ocr.classification(img_bytes)
print(result)
验证码的问题解决了,就看下登录的数据包是咋样的。点击登录,触发三条数据包。
第一条getTime
数据包,根据其响应内容的data字段,猜测大概率是获取时间戳的。
第二条passwordLogin
,根据payload就可以知道这是登录的数据包。
第三条getImageCaptcha
就是用来获取图片验证码的。主要关注passwordLogin
数据包,其中参数username
和imageCaptchaCode
都是明文,唯独参数password
是经过加密的,所以我们就要去找到加密的逻辑。全局搜索passwordLogin
关键词,有三条记录。
第一处如下。由于代码中的变量名和数据包的参数名一致,所以很有可能加密逻辑在这里,打上断点。
第二、三处如下。第二处代码种的变量名和数据包的参数名也一致,所以加密逻辑也有可能在这里,也打上断点。第三处被注释了,不用看。
点击登录,看触发的哪一处的代码。
跳转到第二处,说明加密逻辑在这里。主要语句为:password: encryptFn(pwd + '' + ress.data)
,关注点就两个,一个是encryptFn
函数是怎么实现的,一个是ress.data
和pwd
的值是什么。定位到encryptFn
的实现代码。
根据代码中的关键词JSEncrypt
和setPublicKey
,就可以很清楚的知道此为RSA加密,而且还是PKCS padding类型的,公钥也给出了。所以接下来只需要弄清楚要加密的内容是什么就可以了。再回到刚刚打断点处,查看pwd
的值。
根据输出,可以很清楚的知道pwd
就是我们输入的密码明文。再看ress.data
是什么。
这个值跟getTime
数据包获取到的时间戳很相似,实际上它就是时间戳,综合一下,要加密的明文就是我们输入的密码拼接上时间戳。这样就可以写python代码了。
点击查看代码
import json
import requests
import base64
import ddddocr
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
session = requests.session()
session.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 "
"Safari/537.36"}
# 一开始应该有个加载cookie的过程,一个session类比成一个客户端
# 用来加载第一个cookie
session.get("https://user.wangxiao.cn/login?redirect_uri=https://ke.wangxiao.cn/")
# 获取验证码
img_url = "https://user.wangxiao.cn/apis//common/getImageCaptcha"
img_resp = session.post(img_url,
headers={"Referer": "https://user.wangxiao.cn/login?redirect_uri=https://ke.wangxiao.cn/",
"Content-Type": "application/json;charset=UTF-8", })
img_base64 = img_resp.json()['data'].strip().split(",")[-1]
with open("tu.png", mode="wb") as f:
f.write(base64.b64decode(img_base64))
ocr = ddddocr.DdddOcr()
img_path = "tu.png"
# 读取图片
with open(img_path, 'rb') as f:
img_bytes = f.read()
# 识别验证码
result = ocr.classification(img_bytes)
print(result)
# 1.请求getTime获取到 data(时间戳)
# 2.对密码进行rsa加密,加密的内容(明文密码+data(时间戳))
# 3.发送请求到login
rsa_pub_key_base64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA5Zq6ZdH/RMSvC8WKhp5gj6Ue4Lqjo0Q2PnyGbSkTlYku0HtVzbh3S9F9oHbxeO55E8tEEQ5wj/+52VMLavcuwkDypG66N6c1z0Fo2HgxV3e0tqt1wyNtmbwg7ruIYmFM+dErIpTiLRDvOy+0vgPcBVDfSUHwUSgUtIkyC47UNQIDAQAB"
ress = session.post("https://user.wangxiao.cn/apis//common/getTime",
headers={"Referer": "https://user.wangxiao.cn/login?redirect_uri=https://ke.wangxiao.cn/",
"Content-Type": "application/json;charset=UTF-8", })
password = "xxxxx"
password_ming = password + ress.json()['data']
# rsa加密 把页面上的base64的公钥处理成字节,直接处理
pub_key = RSA.importKey(base64.b64decode(rsa_pub_key_base64))
rsa = PKCS1_v1_5.new(pub_key)
mi_password = rsa.encrypt(password_ming.encode("utf-8"))
mi_password_base64 = base64.b64encode(mi_password).decode()
print(mi_password_base64)
data = {"imageCaptchaCode": result, "password": mi_password_base64, "userName": "xxxxxx"}
login_resp = session.post("https://user.wangxiao.cn/apis//login/passwordLogin",
data=json.dumps(data, separators=(',', ':')),
headers={"Referer": "https://user.wangxiao.cn/login?redirect_uri=https://ke.wangxiao.cn/",
"Content-Type": "application/json;charset=UTF-8", })
如果用户名和密码都正确的话,就会得到如下的结果。
RSA的加密过程,除了可以用python,还可以直接用js代码实现。
这里需要介绍一个js库——node-jsencrypt
,官网上直接搜,找到安装命令,直接在终端输入命令即可。
安装完成之后,直接调用该库,在把页面上代码复制出来即可。
点击查看代码
var JSEncrypt=require("node-jsencrypt");
function fn(msg) {
var o = new JSEncrypt;
return o.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA5Zq6ZdH/RMSvC8WKhp5gj6Ue4Lqjo0Q2PnyGbSkTlYku0HtVzbh3S9F9oHbxeO55E8tEEQ5wj/+52VMLavcuwkDypG66N6c1z0Fo2HgxV3e0tqt1wyNtmbwg7ruIYmFM+dErIpTiLRDvOy+0vgPcBVDfSUHwUSgUtIkyC47UNQIDAQAB"),
o.encrypt(msg)
}
console.log(fn("123456"));