JavaScript逆向之RSA演算法

sbhg發表於2024-03-06

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資料包,其中引數usernameimageCaptchaCode都是明文,唯獨引數password是經過加密的,所以我們就要去找到加密的邏輯。全域性搜尋passwordLogin關鍵詞,有三條記錄。

第一處如下。由於程式碼中的變數名和資料包的引數名一致,所以很有可能加密邏輯在這裡,打上斷點。

第二、三處如下。第二處程式碼種的變數名和資料包的引數名也一致,所以加密邏輯也有可能在這裡,也打上斷點。第三處被註釋了,不用看。

點選登入,看觸發的哪一處的程式碼。

跳轉到第二處,說明加密邏輯在這裡。主要語句為:password: encryptFn(pwd + '' + ress.data),關注點就兩個,一個是encryptFn函式是怎麼實現的,一個是ress.datapwd的值是什麼。定位到encryptFn的實現程式碼。

根據程式碼中的關鍵詞JSEncryptsetPublicKey,就可以很清楚的知道此為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"));

相關文章