情景:
某平臺支付訂單時,採用搶單模式:同一個商品可被多個人下單,先付款成功者,才可獲得商品。該平臺對接了第三方錢包負責使用者的支付功能。
需求:
新的搶單模式,導致曾經實現簡單地對平臺商品進行鎖單的功能,不再滿足對平臺商品進行搶購的要求,需要實現下單後的餘額支付功能。
問題分解:
1):獲取支付連結(得到請求引數uuid與mer_cust_id)
2):確定所需請求
3):逆向請求中的加密引數
一:獲取支付連結(簡略,各網站不同)
點選平臺訂單介面的立即支付後,跳轉到第三方錢包支付介面
將這一過程透過瀏覽器自帶的開發者工具進行抓包,找到會話發起請求
透過python模擬請求,檢視返回資料,發現支付連結。
def pay(self, o_id):
data = {
"id": o_id,
"pay_type": "140",
"return_url": "https://h5.XXX.XXX/#/pages/userCenter/orderDetail?order_id={}".format(o_id),
"timestamp": self.s_t()
}
self.h['x-token'] = self.my_md5(self.s10_format(o_id))
r = requests.post("https://pay.XXX.XXX/pay/order/submit",data=json.dumps(data), headers=self.h)
print(r.text)
url = json.loads(r.text)['data']
# {"code":1,"msg":"發起支付","time":"17093XXXXX","data":"https:\/\/hfpay.cloudpnr.com\/h5\/pages\/cashier\/index?uuid=hfpwallet6666000137XXXpay84ddcd15-9033-4e42-8163-0c128223603e","test":0}
二:請求分析
透過手動支付,記錄該過程中出現的各請求,經過測試發現必要請求如下:
1):支付密碼檢驗
data ={
"password": Triple DES生成的以等號結尾的12位密文;
}
h = {"Content-Type":"application/json", "Uuid": hfpwallet6666000137XXXpay84ddcd15-9033-4e42-8163-0c128223603e, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", 'check_value': hmac_sha256生成的64位密文, 'mer_cust_id': '6666000137XXX', 'Hide_head': '0'}
r = requests.post("https://hfpay.cloudpnr.com/api/hfpwalleth5/transpasswordcheck", data=json.dumps(data), headers=h)
2):狀態確定?
data = {
"trans_type": "30",
"dev_info_json": '{"devType":"2","devSysType":"H5","mobileFlag":"Y"}'
}
r = requests.post("https://hfpay.cloudpnr.com/api/hfpwalleth5/transverifyquery", data=json.dumps(data), headers=h)
3):支付
data = {
"dev_info_json": '{"devType":"2","devSysType":"H5","mobileFlag":"Y"}'
}
r = requests.post("https://hfpay.cloudpnr.com/api/hfpwalleth5/balancepay", data=json.dumps(data), headers=h)
PS:對於單個使用者而言,雖然該錢包的支付過程採用了加密演算法,請求需攜帶加密引數check_value,支付密碼加密過程所需引數uuid中pay之後的內容會發生變化,然而每次check_value與password的值都是固定的,造成這一結果的原因在下文加密引數逆向中進行了推測
三:加密引數逆向
1):password
以支付密碼檢驗為例,透過開發者工具對該請求的呼叫堆疊進行分析,發現其中有個匿名函式對傳入的明文密碼111111進行了加密並賦值給了password
跟棧進入,打上斷點進行除錯
顯然發現其呼叫了TripleDES進行加密,key為uuid,iv為chinapnr,mode為CBC,pad為pkcs7
使用python進行演算法模擬,發現直接傳入uuid報錯ValueError: Invalid triple DES key size. Key must be either 16 or 24 bytes long,後查詢發現其解決辦法為直接截斷選擇前24位。
又根據使用該處理辦法之後的python模擬結果正確以及多次請求中相同密碼明文的密文不變,故推測js的TripleDES加密演算法也採用了此處理辦法,而呼叫的時候似乎忽略了這一問題。
所以每次pay之後生成的隨機字串就這樣被截斷,不參與加密過程,其密文對於各使用者而言也就不變(笑
下附python模擬程式碼
import pyDes
# 定義金鑰,長度必須為8個字元(64位)
key = 'hfpwalletXXXpayf92b7651-0007-45f8-95b8-4517e7cdb47d'
# 定義初始化向量,長度為8個字元(64位)
iv = b'chinapnr'
# 建立加密器
k = pyDes.triple_des('hfpwalletXXX', pyDes.CBC, iv, pad=None, padmode=pyDes.PAD_PKCS5)
# 要加密的明文
data = '111111'
# 加密
cipher_text = k.encrypt(data)
# 解密
plain_text = k.decrypt(cipher_text)
import base64
print('加密前的明文:', data)
print('加密後的密文:', base64.b64encode(cipher_text).decode() )
print('解密後的明文:', plain_text)
參考:https://www.jianshu.com/p/1a0dde3f1b57
2):check_value
步驟如下:
透過開發者工具進行搜尋很明顯地發現check_value引數由aes加密演算法得到,key為固定的chinapnr
在該步驟打上斷點之後,再次手動進行支付,以支付密碼檢驗請求為例
發現明文為{password: 'oO4Ruxbd1X8='},其中oO4Ruxbd1X8=為明文支付密碼111111的加密密文,假定明文為請求載荷,即post請求中所攜帶的data資料
跟棧進入,發現所採用的為hmac_sha256演算法加密,透過控制檯輸出本次的加密結果進行後續驗證
透過python進行演算法模擬
import hmac
import hashlib
def hmac_sha256(key, message):
return hmac.new(key.encode('utf-8'), message.encode('utf-8'),
hashlib.sha256).hexdigest()
print(hmac_sha256("chinapnr","password=oO4Ruxbd1X8="))
#e24378626f029eb351656b637d7991fc5fb853db749c6b9c9e69ecc0a1ab2087
透過對後續請求呼叫該函式時傳入的引數進行對照確定,明文為請求載荷。
至此,加密引數分析完畢,整個需求也實現完畢。