本文将会逆向京东的 JS
,分析京东请求内的 h5st 加密参数生成规则及部分 Cookie
生成
1 h5st
1.1 加密定位
进入网址:手机 - 商品搜索 - 京东 (jd.com) (opens in a new tab) 后打开浏览器 F12 进行抓包
可以看到请求都有一个 h5st
的加密参数,通过全文搜索,关键字可以是 .h5st
可以查看调用的地方,定位到加密的地方
1.2 算法分析
打上断点开始逐步分析发现 window.PSign.sign(e)
是一个 Promise
,也就是一个异步的处理
而 e
是 body
进行加密后的结果,加密方式为 sha256
加密原文是之前的 body
所以可以还原 genBody
函数
def gen_body(functionid, ts, body):
"""
:param body: 请求参数内的 body
:param functionid: 函数 id
:param ts: 时间戳, 秒级
:return:
"""
return {
"appid": "search-pc-java",
"functionId": functionid,
"client": "pc",
"clientVersion": "1.0.0",
"t": ts,
"body": hashlib.sha256(body.encode()).hexdigest()
}
根据断点步进查看 window.PSign.sign
函数
查看 window.PSign
这个对象,因为加密都是调用的这个对象的方法,看看有没有可以的方法!
点开 Prototype
查看发现里面有几个疑似函数
分析一下 window.PSign.__genSign
发现 t
是一串加密的字符串,r
是上一步加密好的 body
让我们来看一下代码是啥
代码很简单,可以看出来:t
就是一个加密的 key
, 而 u
是之前的 body
进行的格式化,加密方式为 hamc
这边等于清楚了第一步,那么接下来要分析 t
是怎么来的,我们需要在堆栈这里找到上一步,然后打断点继续分析
断点下来我们可以发现是在这里调用的 __genSign
方法,主要分析 C
是怎么来的, 断点调试发现:
this[u(0, Qe, 0, $e)] ? C = this[u(0, to, 0, ro)](m, w, s, A, eI).toString() || "" : b ? C = this[a(0, 0, -no, -eo) + a(0, 0, 30, oo)](b, w, s, A) : (this._defaultToken = kf[u(0, io, 0, ao)](II, this[u(0, co, 0, uo) + "nt"]),
C = this["__genDefau" + u(0, fo, 0, so)](this[u(0, ee, 0, ho) + a(0, 0, -lo, -vo)], w, s, A));
浏览器自助解一下混淆,然后看解完混淆之后的代码:
this['_isNormal'] ? C = this['__genKey'](m, w, s, A, eI).toString() || "" : b ? C = this['__genDefaultKey'](b, w, s, A) : (this._defaultToken = II(this['_fingerprint']), C = this['__genDefaultKey'](this['_defaultToken'], w, s, A));
因为 this['_isNormal']
为 true
,所以 C = this['__genKey'](m, w, s, A, eI).toString() || ""
而 this['__genKey']
这个函数又是来源于:
因此接下来要分析 m, w, s, A, eI
都是哪里来的
m
:this._token
w
:this._fingerprint
s
:s = XC(h, 'yyyyMMddhhmmssSSS')
,跟时间相关的,后面的是他的格式A
:this['_appId']
eI
:是一个加密工具,分析this['__genKey']
的代码发现并没有使用
所以可以还原出 genKey
的加密算法了:
def gen_key(ts):
"""
生成hmac256 盐值
"""
rd = '6vRABalFEPcI'
str_ = f'{T["env"]["tk"]}{T["env"]["fingerprint"]}{ts}{appid}{rd}'
return hashlib.md5(str_.encode()).hexdigest()
接着还原 genSign
的代码:
def gen_sign(data: dict, key):
"""
生成加密参数
:param data: hash 之后的body
:param key: 加密的 key
:return:
"""
message = ""
for k in sorted(data.keys()):
message += f'{k}:{data[k]}'
return hmac.new(key.encode(), message.encode('utf-8'), digestmod=sha256).hexdigest()
这个 data
是需要加密的 body
,而那个 key
是调用 genKey
生成的 key
最后分析完了这三个关键函数,我们就要分析 h5st
了,继续分析刚刚的代码:
发现又调用了 __genSignParams
方法
这不就是我们需要的 h5st
吗?那我们追进去看看
l
:是上面gen_key
生成的结果h
:是一个时间戳s
:是时间戳的字符串形式r
:是函数传进来的参数,先不着急分析
先分析一下 __genSignParams
方法,发现就是进行拼接
解一下混淆并去掉没用的代码:
[n, this['_fingerprint'], this['_appId'], this['_token'], t, this['_version'], r, e]['join'](";")
用 python
还原一下
def gen_h5st(sign, ts, tstr):
return ";".join([tstr, T["env"]["fingerprint"], appid, T["env"]["tk"], sign, version, ts, T["env"]["e"]])
通过上述分析,我们还有几个参数不知道怎么来的:
this['_fingerprint']
this['_token']
e
接下来我们来分析这个 e
是怎么来的,首先在刚刚那个加密的地方打上断点,然后刷新页面
可以看到 r
就是我们需要分析的参数
点到上一层堆栈查看可以知道 r
来源与这个 q
, 而 q = t[i(m, 0, 718)]
解开混淆发现:q = t["sent"]
,所以我们需要去查看这个的来源,那么这个 t
又是怎么来的呢?网上查看代码发现 t
也是传入进来的,所以我们要分析一下,传入来的 t
是不是就包含了这个 sent
,还是说一开始没有这个,后面走了一段代码后就有了呢?所以我们可以使用 Chrome
自带的 js
文件替换进行插桩输出看看!
插桩之后查看日志可以知道,有时候有,有时候没有
我们继续在上层堆栈中插桩,输出所有的 n
刷新页面后查看日志
发现这个加密参数出现的时候有一个这样的日志
也就是当 sent
为 {sua: 'Macintosh; Intel Mac OS X 10_15_7', pp: {…}, random: 'GFfcGJW9IV', referer: 'https://www.jd.com/', v: 'v3.2.1', …}
的时候,他的 rval
就是我们需要的值,其实熟知 JavaScript
的应该知道,这是 js
中常见的异步手段,通过 promise
实现的,sent
就是参数,rval
就是返回值!所以这个参数肯定和这条日志是有关系的!
我们接着插桩,当 sent
为 {sua: 'Macintosh; Intel Mac OS X 10_15_7', pp: {…}, random: 'GFfcGJW9IV', referer: 'https://www.jd.com/', v: 'v3.2.1', …}
的时候,我们就断下来,然后逐步分析
断下来了,这一步还没有生成我们需要的加密参数,所以我们先在下一行打一个断点,放过这个断点后,再分析下一步的执行流程
此时放过去之后即将调用 t
函数,那么这个 t
函数是哪个呢?我们可以在 console
上输出一下
发现他调用的是这个函数,所以我们可以进入到这个函数里面,分析一下,进去发现是一段分支语句
for (; ; ){
switch (j.prev = j[N(0, t, 0, r)]) {
case 0:
return j[N(0, 460, 0, 444)] = 2,
I[N(0, 662, 0, n)](KI, 1);
case 2:
return (M = j.sent).fp = this[N(0, e, 0, o) + "nt"],
O = JSON.stringify(M, null, 2),
this[N(0, 478, 0, i)](I.aDGtR[N(0, a, 0, c)](O)),
L = eI[P(-19, 0, 0, -u)].encrypt(O, eI.enc[P(f, 0, 0, s)][P(h, 0, 0, 184)](["wm", I[N(0, l, 0, 326)], "w_", I[N(0, v, 0, p)], I[P(d, 0, 0, 46)], "o("][P(-y, 0, 0, -d)]("")), {
iv: eI[N(0, 379, 0, g)][P(f, 0, 0, -x)][N(0, 526, 0, _)](["01", "02", "03", "04", "05", "06", "07", "08"].join("")),
mode: eI[P(-m, 0, 0, b)][N(0, w, 0, A)],
padding: eI[N(0, C, 0, S)][P(-10, 0, 0, -B)]
}),
j[N(0, 339, 0, z)](I.KWfsh, L.ciphertext[P(D, 0, 0, k)]());
case 8:
case I[N(0, E, 0, 425)]:
return j[P(-99, 0, 0, -T)]()
}
}
我们先解一下混淆
for (; ; ){
switch (j.prev = j['next']) {
case 0:
return j['next'] = 2,
I['fMHMe'](KI, 1);
case 2:
return (M = j.sent).fp = this['_fingerprint'],
O = JSON.stringify(M, null, 2),
this['_log']('__collect envCollect='['concat'](O)),
L = eI['AES'].encrypt(O, eI.enc['Utf8']['parse']('wm0!@w_s#ll1flo('), {
iv: eI['enc']['Utf8']['parse']('0102030405060708'),
mode: eI['mode']['CBC'],
padding: eI['pad']['Pkcs7']
}),
j['abrupt'](I.KWfsh, L.ciphertext['toString']());
case 8:
case 'end':
return j['stop']()
}
}
就可以很清晰的看到一个 AES
加密了,我们可以手动再 console
上调用一下,看看是不是我们需要的结果,发现断点不好打,就直接插桩输出看看吧,插桩代码如下:
(M = j.sent).fp = this[N(0, e, 0, o) + "nt"];
O = JSON.stringify(M, null, 2)
L = eI[P(-19, 0, 0, -u)].encrypt(O, eI.enc[P(f, 0, 0, s)][P(h, 0, 0, 184)](["wm", I[N(0, l, 0, 326)], "w_", I[N(0, v, 0, p)], I[P(d, 0, 0, 46)], "o("][P(-y, 0, 0, -d)]("")), {
iv: eI[N(0, 379, 0, g)][P(f, 0, 0, -x)][N(0, 526, 0, _)](["01", "02", "03", "04", "05", "06", "07", "08"].join("")),
mode: eI[P(-m, 0, 0, b)][N(0, w, 0, A)],
padding: eI[N(0, C, 0, S)][P(-10, 0, 0, -B)]
})
console.log("key: ", ["wm", I[N(0, l, 0, 326)], "w_", I[N(0, v, 0, p)], I[P(d, 0, 0, 46)], "o("][P(-y, 0, 0, -d)](""))
console.log("iv: ", ["01", "02", "03", "04", "05", "06", "07", "08"].join(""))
console.log("mode: ", eI[P(-m, 0, 0, b)][N(0, w, 0, A)])
console.log("padding: ", eI[N(0, C, 0, S)][P(-10, 0, 0, -B)])
console.log("msg: ", O)
console.log("result: ", L.ciphertext[P(D, 0, 0, k)]())
return j[N(0, 339, 0, z)](I.KWfsh, L.ciphertext[P(D, 0, 0, k)]())
因此就可以得到我们需要的参数 e
了,从上述分析步骤我们可以大概看出来加密方式是 aes CBC Pkcs7
,我们使用现成的工具验证一下
没有问题,所以这个加密参数已经还原了!接下来还有两个参数:_fingerprint
和 token
,这两个参数我们通过分析请求可以看到其实是来源于请求里的:
但是这个请求却又有一段加密参数
因此我们分析完 expandParams
之后基本所有的参数就全部分析完毕了,我们直接全局搜索 expandParams
,找到了这么一个地方
所以继续查看代码分析一下这个参数的来源,发现其来源于:$ = t[K(0, 970, 0, B)]
,打上断点继续分析
解混淆发现其来自于 t['env']
,那么这个 t
又是怎么来的呢 ?根据堆栈往上层找
t
就是 this['_onRequestTokenRemotely']
,而 t['env']
来源于 h
,h
又来源于 s.ciphertext.toString()
而
s
来源于下面这一行代码
s = eI[e(M, 366)][Xt(484, 370)](f, eI[Xt(594, O)].Utf8[Xt(L, R)](["wm", n[Xt(P, N)], "w-", n[Xt(504, W)], n[e(H, q)], "o("].join("")), {
iv: eI[Xt(F, U)][e(G, K)][e(V, Y)](["01", "02", "03", "04", "05", "06", "07", "08"][e(A, J)]("")),
mode: eI[Xt(X, Z)][e(567, $)],
padding: eI[e(285, tt)][Xt(rt, nt)]
})
解一下混淆
s = eI['AES']['encrypt'](f, eI['enc'].Utf8['parse']('wm0!@w-s#ll1flo('), {
iv: eI['enc']['Utf8']['parse']('0102030405060708'),
mode: eI['mode']['CBC'],
padding: eI['pad']['Pkcs7']
})
很简单就可以看出来又是一个 AES
加密,我们可以看看加密的 f
是什么
发现是一些浏览器的信息!使用通用的工具验证一下,发现也没有问题
到目前为止,我们需要的所有参数全部分析完毕,可以愉快的生成
h5st
了,读者可以总结归纳一下加密流程,这边就不做太多的介绍了,附上完整的 Python
还原代码
# -*- coding: utf-8 -*-
# @Time : 2023-05-31 10:38
# @Author : Kem
# @Desc : 生成加密参数 h5st
# -*- encoding: utf-8 -*-
import datetime
import hashlib
import hmac
import json
import time
from hashlib import sha256
import requests
from lego.state import T
from lego.utils import fake
from lego.utils.crypto import AES
from loguru import logger
appid = 'f06cc'
version = '3.1'
key1 = "wm0!@w-s#ll1flo("
key2 = "wm0!@w_s#ll1flo("
iv = "0102030405060708"
def gen_key(ts):
"""
生成hmac256 盐值
"""
rd = '6vRABalFEPcI'
str_ = f'{T["env"]["tk"]}{T["env"]["fingerprint"]}{ts}{appid}{rd}'
return hashlib.md5(str_.encode()).hexdigest()
def gen_sign(data: dict, key):
"""
生成加密参数
:param data: hash 之后的body
:param key: 加密的 key
:return:
"""
message = ""
for k in sorted(data.keys()):
message += f'{k}:{data[k]}'
return hmac.new(key.encode(), message.encode('utf-8'), digestmod=sha256).hexdigest()
def gen_body(functionid, ts, body):
"""
:param body: 请求参数内的 body
:param functionid: 函数 id
:param ts: 时间戳, 秒级
:return:
"""
return {
"appid": "search-pc-java",
"functionId": functionid,
"client": "pc",
"clientVersion": "1.0.0",
"t": ts,
"body": hashlib.sha256(body.encode()).hexdigest()
}
def gen_h5st(sign, ts, tstr):
return ";".join([tstr, T["env"]["fingerprint"], appid, T["env"]["tk"], sign, version, ts, T["env"]["e"]])
def init_env():
while True:
# 0102030405060708
fake_fp = fake.gen_random_str(16, "0123456789")
local_env = {
"wc": 0,
"wd": 0,
"l": "zh-CN",
"ls": "zh-CN",
"ml": 2,
"pl": 5,
"av": "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35",
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35",
"sua": "Macintosh; Intel Mac OS X 10_15_7",
"pp": {},
"pp1": "jsavif=0; jsavif=0; __jda=122270672.1685523235636810059383.1685523236.1685523236.1685523236.1; __jdc=122270672; __jdv=122270672|direct|-|none|-|1685523235637; shshshfpa=1fba7094-cf40-400d-972a-11138fdc6ca9-1685523235; shshshfpx=1fba7094-cf40-400d-972a-11138fdc6ca9-1685523235; shshshfpb=pAiDkfHR84jPVp-gmv-FY8g; areaId=18; ipLoc-djd=18-1482-0-0; rkv=1.0; qrsc=3; __jdb=122270672.11.1685523235636810059383|1.1685523236",
"pm": {
"ps": "prompt",
"np": "default"
},
"w": 1920,
"h": 1080,
"ow": 1920,
"oh": 1055,
"url": "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&wq=%E6%89%8B%E6%9C%BA&pvid=8858151673f941e9b1a4d2c7214b2b52&page=5&s=116&click=0",
"og": "https://search.jd.com",
"pr": 1,
"re": "",
"random": "bf35MB70a6",
"referer": "",
"v": "v3.2.1",
"ai": appid,
"fp": fake_fp
}
expand_params = AES.CBC.encrypt(
msg=json.dumps(
local_env,
indent=2,
ensure_ascii=False
),
key=key1,
iv=iv,
_output="hex"
)
url = "https://cactus.jd.com/request_algo?g_ty=ajax"
payload = json.dumps({
"version": "3.1",
"fp": fake_fp,
"appId": appid,
"timestamp": int(time.time() * 1000),
"platform": "web",
"expandParams": expand_params.ciphertext,
"fv": "v3.2.1"
})
headers = {
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'DNT': '1',
'Origin': 'https://search.jd.com',
'Pragma': 'no-cache',
'Referer': 'https://search.jd.com/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35',
'accept': 'application/json',
'content-type': 'application/json',
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'Cookie': 'ipLoc-djd=18-1482-48937-0'
}
response = requests.request("POST", url, headers=headers, data=payload)
data = response.json()
if data.get('status') == 200:
logger.info(f"初始化环境成功, 新的 fingerprint 为: {data['data']['result']['fp']}")
env = {
"sua": "Macintosh; Intel Mac OS X 10_15_7",
"pp": {},
"random": fake.gen_random_str(10),
"referer": "https://www.jd.com/",
"v": "v3.2.1",
"fp": data['data']['result']['fp']
}
e = AES.CBC.encrypt(
msg=json.dumps(
local_env,
indent=2,
ensure_ascii=False
),
key=key2,
iv=iv,
_output="hex"
)
# e = crypto.call("AES.encrypt", json.dumps(env, ensure_ascii=False, indent=2), key, iv)
T["env"] = {
"fingerprint": data['data']["result"]['fp'],
"tk": data['data']["result"]['tk'],
"e": e.ciphertext,
}
return
else:
logger.warning(f"初始化环境失败, 等待重试")
time.sleep(60)
def encrypt(functionid, body, ts=None):
"""
获取 h5st
:param functionid:
:param body:
:param ts:
:return:
"""
if not T["env"]: init_env()
ts = ts or time.time()
if len(str(ts)) == 13: ts = int(ts) / 1000
# ts = 1685500202335 / 1000
tstr = datetime.datetime.fromtimestamp(ts).strftime('%Y%m%d%H%M%S%f')[:-3]
# tstr = "20230531092616337"
body = gen_body(functionid, int(ts * 1000), body)
key = gen_key(tstr)
sign = gen_sign(body, key)
return {
"h5st": gen_h5st(sign, str(int(ts * 1000)), tstr),
"ts": str(int(ts * 1000))
}
2 Cookie
分析完 h5st
的算法,下面我们继续分析一下京东的 Cookie
生成规则,因为京东接口请求成功率还是和 Cookie
有一点点关系的,分析的 Cookie
都选取的算法生成的,至于请求生成的,那就自己抓包分析吧,本文分析的 Cookie
如下:
__jda
__jdb
__jdc
__jdv
shshshfpx
shshshfpa
mba_muid
mba_sid
分析 Cookie
生成规则可以借助一个油猴脚本,可以很快速的定位位置,地址如下:JS Cookie Monitor/Debugger Hook (greasyfork.org) (opens in a new tab)
装完这个脚本之后,你可以明确的在控制台看到所有 Cookie
操作了,真的很 nice
!
下面我们拿
___jda
举例,看看是如何分析的, 首先我们先把 Cookie
全部清空,然后刷新浏览器,就可以看到新增了的(绿色的就是新增的)
这边还能指向是在哪里执行的,我们点进去后打上断点,清除
Cookie
了重来一次
找前面的堆栈
看到了设置的地方,可以看到
__jda
来源于下面这一行代码
[(e = ce).get(f), e.get(h) || "-", e.get(A) || "-", e.get(L) || "-", e.get(D) || "-", e.get(W) || 1].join(".")
好像是从一个对象里面取的值,我们看看是哪个对象
发现其实就是
e
这个对象,因此我们需要知道 e
是怎么设置的值
[(e = ce).get('2'), e.get('1') || "-", e.get('22') || "-", e.get('23') || "-", e.get('24') || "-", e.get('25') || 1].join(".")
我们可以在他的 set
方法插桩,看一下设置流程
清除
Cookie
, 刷新页面,查看日志
运行打上断点,开始查看
0
:this.set(g, "UA-J2011-1");
, 所以0
一直都是"UA-J2011-1"
3
:this.set(p, t)
,t
来源于window.document.domain.indexOf("360buy") >= 0 ? "360buy.com" : "jd.com";
2
:this.set(f, Q())
,来源与Q()
re = function(e) {
var t, r = 1, n = 0;
if (!te(e))
for (r = 0,
t = e[a] - 1; t >= 0; t--)
n = e.charCodeAt(t),
r = (r << 6 & 268435455) + n + (n << 14),
n = 266338304 & r,
r = 0 != n ? r ^ n >> 21 : r;
return r
}
function() {
return re(r.domain)
}
4
:this.set(m, Math.round((new Date).getTime() / 1e3))
, 时间戳5
:this.set(v, 15552e6)
, 常量,15552000000
6
:this.set(w, 1296e6)
,常量,1296000000
7
:this.set(_, 18e5)
,常量,1800000
13
:this.set(C, ee());
,代码一堆,但是实际就是返回'-'
14
:this.set(x, r.name)
,浏览器名称15
:this.set(N, r.version)
,浏览器版本16
:this.set(O, ie());
,系统名称17
:this.set(R, n.D)
,分辨率18
:this.set(M, n.C)
,位数19
:this.set(T, n.language)
, 语言20
:this.set(I, n.javaEnabled)
, 是否运行JavaScript
21
:his.set(J, n.characterSet)
,characterSet
27
:this.set(H, X)
,X
是浏览器中定义的一个数组32
:this.set(B, (new Date).getTime())
, 时间戳35
:this.set(Y, i[a] ? i[0] : "-")
, 其实就是-
36
:this.set(F, s || "-")
,其实就是-
8
:e.set(b, r.location.hostname)
,当前网站的hostname
9
:e.set(j, r.title.replace(/\$/g, ""))
,当前网址标题10
:e.set(y, r.location.pathname)
,网址path
11
:e.set(k, r.referrer.replace(/\$/g, ""))
,referrer
地址12
:e.set(S, r.location.href)
,当前网址1
:K()
,时间戳 + 随机数22
:取之前的4
23
:取之前的4
24
:取之前的4
25
:恒为1
28
:direct
29
:-
30
:none
31
:-
26
:0
33
:-
34
:0
分析完这个对象怎么生成的就很容易分析出我们需要的 Cookie
是怎么生成的了!这边不分析了,附上 Python
版本算法
# -*- coding: utf-8 -*-
# @Time : 2023-06-05 17:54
# @Author : Kem
# @Desc : 生成京东一些根据 js 生成的 Cookie
import json
import random
import time
from urllib.parse import urlparse
import requests
from lego.utils import fake, crypto
def ct(t):
return t is None or t == "-" or t == ""
def ut(t):
n = 1
if not ct(t):
for e in range(len(t) - 1, -1, -1):
char_code = ord(t[e])
n = (n << 6 & 268435455) + char_code + (char_code << 14)
r = 266338304 & n
n = n ^ r >> 21 if r != 0 else n
return n
def parse_domain(url: str):
# 解析URL,获取域名和子域名
parsed_url = urlparse(url)
domain_parts = parsed_url.hostname.split('.')
if len(domain_parts) >= 2:
return '.'.join(domain_parts[-2:])
else:
return parsed_url.hostname
def gen_cookie(url: str):
domain = parse_domain(url)
timestamp = int(time.time() * 1000)
t1, t2, t3 = ut(domain), f'{timestamp}{fake.gen_random_str(10, base_str="0123456789")}', str(timestamp // 1000)
jda = ".".join([str(t1), t2, t3, t3, t3, "1"])
jdb = ".".join([str(t1), "1", f'{t2}|1', t3])
jdc = ".".join([str(t1)])
jdv = "|".join([str(t1), "direct", "-", "none", "-", str(timestamp)])
shshshfpx = generate_uuid()
shshshfpa = generate_uuid()
mba_muid = f'{timestamp}{fake.gen_random_str(10, base_str="0123456789")}'
mba_sid = f'{timestamp}{fake.gen_random_str(16, base_str="0123456789")}.0'
# ; shshshfpb={get_shshshfpb()}
return "; ".join([
f'__jda={jda}',
f'__jdb={jdb}',
f'__jdc={jdc}',
f'__jdv={jdv}',
f'shshshfpx={shshshfpx}',
f'shshshfpa={shshshfpa}',
f'mba_muid={mba_muid}',
f'mba_sid={mba_sid}',
])
def generate_uuid():
uuid = ''
for i in range(1, 33):
random_num = random.randint(0, 15)
uuid += hex(random_num)[2:]
if i in [8, 12, 16, 20]:
uuid += '-'
now = time.time()
uuid += '-' + str(int(now * 1000 / 1000))
return uuid
def get_shshshfpb(shshshfpa=None):
"""
这个获取 Cookie 的暂时用不上
:param shshshfpa:
:return:
"""
shshshfpa = shshshfpa or generate_uuid()
body = {
"appname": "jdwebm_hf",
"jdkey": "",
"isJdApp": False,
"jmafinger": "",
"whwswswws": "",
"businness": "jshopx",
"body": {
"browser_info": crypto.Hash.md5(shshshfpa),
"client_time": int(time.time() * 1000),
"period": 24,
"shshshfpa": shshshfpa,
"ecflag": "n",
"whwswswws": "",
"jdkey": "",
"isJdApp": False,
"jmafinger": "",
"cookie_pin": "",
"jdu": "",
"mba_muid": "",
"visitkey": "",
"msdk_version": "2.5.0",
"wid": "",
"lan": "zh-CN",
"scrh": 1080,
"scrah": 1055,
"scrw": 1920,
"scaw": 1920,
"oscpu": "",
"platf": "MacIntel",
"pros": "20030107",
"temp": 33,
"hll": False,
"hlr": False,
"hlo": False,
"hlb": False,
"color_depth": 24,
"pixel_ratio": 1,
"resolution": "1920;1080",
"available_resolution": "1920;1055",
"session_storage": 1,
"local_storage": 1,
"indexed_db": 1,
"open_database": 1,
"cpu_class": "unknown",
"navigator_platform": "MacIntel",
"regular_plugins": "PDF Viewer::Portable Document Format::application/pdf~pdf,text/pdf~pdf;Chrome PDF Viewer::Portable Document Format::application/pdf~pdf,text/pdf~pdf;Chromium PDF Viewer::Portable Document Format::application/pdf~pdf,text/pdf~pdf;Microsoft Edge PDF Viewer::Portable Document Format::application/pdf~pdf,text/pdf~pdf;WebKit built-in PDF::Portable Document Format::application/pdf~pdf,text/pdf~pdf",
"adblock": False,
"touch_support": 0,
"app_code_name": "Mozilla",
"app_name": "Netscape",
"app_version": "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35",
"cookie_enabled": True,
"regular_mimetypes": "::Portable Document Format::;::Portable Document Format::",
"online": "unknown",
"hardwareConcurrency": 8,
"product": "Gecko",
"vendor": "Google Inc.",
"vendorSub": "unknown",
"devicePixelRatio": 1,
"updateInterval": "unknown",
"orientationType": "landscape-primary",
"doNotTrack": "1",
"canvas": "canvas winding:yes~canvas fp:518a43b5a0f974add394ce72abb7b18b",
"webgl": "fp:02ffb1efaaefd7e35acf063246d0e907~extensions:ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_disjoint_timer_query;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw~aliased line width range:[1, 1]~aliased point size range:[1, 64]~alpha bits:8~antialiasing:yes~blue bits:8~depth bits:24~green bits:8~max anisotropy:16~max combined texture image units:32~max cube map texture size:16384~max fragment uniform vectors:1024~max render buffer size:16384~max texture image units:16~max texture size:16384~max varying vectors:31~max vertex attribs:16~max vertex texture image units:16~max vertex uniform vectors:1024~max viewport dims:[16384, 16384]~red bits:8~renderer:WebKit WebGL~shading language version:WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)~stencil bits:0~vendor:WebKit~version:WebGL 1.0 (OpenGL ES 2.0 Chromium)~unmasked vendor:Google Inc. (Apple)~unmasked renderer:ANGLE (Apple, Apple M1, OpenGL 4.1)~vertex high float:23(127,127)~vertex medium float:23(127,127)~vertex low float:23(127,127)~fragment high float:23(127,127)~fragment medium float:23(127,127)~fragment low float:23(127,127)~vertex high int:0(31,30)~vertex medium int:0(31,30)~vertex low int:0(31,30)~fragment high int:0(31,30)~fragment medium int:0(31,30)~fragment low int:0(31,30)",
"device_memory": 8,
"is_headless_browser": 0
}
}
url = "https://blackhole-m.m.jd.com/getinfo"
with requests.post(url=url, data={"body": json.dumps(body, ensure_ascii=False)}) as res:
return "; ".join([f'{k}={v}' for k, v in res.cookies.items()])
def get_shshshs_id(shshshfpa):
return f'{crypto.Hash.md5(shshshfpa)}_1_{int(time.time() * 1000)}'
if __name__ == '__main__':
print(gen_cookie("https://mall.jd.com/view_search-472889-0-0-1-24-3.html"))
# print(get_shshshs_id(generate_uuid()))