Browse Source

initial

master
steve.gao 7 months ago
commit
8441f09ae5
  1. 18
      README.md
  2. 0
      dianping/__init__.py
  3. 431
      dianping/mtgsig.py
  4. 0
      douyin/__init__.py
  5. 227
      douyin/dy_abogus.py
  6. 152
      douyin/dy_ttwid.py
  7. 0
      hnw/__init__.py
  8. 114
      hnw/hnw_hook.py
  9. 27
      kuaishou/README.md
  10. 0
      kuaishou/__init__.py
  11. 161
      kuaishou/ks_http.py
  12. 58
      kuaishou/ks_make_trace.py
  13. 336
      kuaishou/ks_slide.py
  14. 16
      main.py
  15. 40
      requirements.txt
  16. 266
      server.py
  17. 205
      utils/ImageHelper.py
  18. 112
      utils/Logger.py
  19. 234
      utils/MysqlData.py
  20. 0
      utils/__init__.py
  21. 123
      utils/rotate_captcha.py
  22. BIN
      utils/tmp/bg_2.png
  23. BIN
      utils/tmp/fg_2.png
  24. 143
      utils/tool.py
  25. 11
      uwsgi.ini
  26. 0
      wx/__init__.py
  27. 0
      wx/video/__init__.py
  28. 59
      wx/video/video_decode.py
  29. 0
      xhs/__init__.py
  30. 132
      xhs/shield.py
  31. 200
      xhs/shield_aes.py
  32. 274
      xhs/shield_const.py
  33. 250
      xhs/shield_md5.py
  34. 36
      xhs/shield_rc4.py
  35. 28
      xhs/xhs_aes.py
  36. 208
      xhs/xhs_captcha.py
  37. 226
      xhs/xhs_des.py
  38. 199
      xhs/xhs_param.py

18
README.md

@ -0,0 +1,18 @@
# api-py
python版--提供API
- kuaishou: 协议过快手web端滑块,激活cookie["did"]字段
- 需要配合node服务使用,当前以定时任务执行
- hnw: 处理惠农网的请求头加密
- 部分参数需要node服务生成,当前以python接口实现
- douyin: 处理ttwid生成问题
- 需要配合node服务使用,当前以定时任务执行
- xhs: 处理web端的x-s、x-t生成逻辑
- wx: 处理微信视频号的视频加密解析
- 需要配合node服务使用,目前node服务部署在172.16.10.39设备上
- 注:该模块接口主要在172.18.1.145设备上对外开放使用

0
dianping/__init__.py

431
dianping/mtgsig.py

@ -0,0 +1,431 @@
import base64
import binascii
import ctypes
import gzip
import hashlib
import json
import time
from urllib.parse import quote, urlparse, parse_qs, quote_plus
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
def encrypt_aes_cbc(plaintext):
key = "z7Jut6Ywr2Pe5Nhx".encode("utf-8")
iv = "0807060504030201".encode("utf-8")
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(bytes.fromhex(plaintext), AES.block_size))
return base64.b64encode(ciphertext).decode()
def decrypt_aes_128_cbc(ciphertext):
"""
"""
key = "z7Jut6Ywr2Pe5Nhx".encode("utf-8")
iv = "0807060504030201".encode("utf-8")
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(base64.b64decode(ciphertext))
strs = binascii.b2a_hex(plaintext).decode()
print(strs, len(strs))
return binascii.b2a_hex(unpad(plaintext, AES.block_size))
def md5_(bytearray):
md5 = hashlib.md5()
md5.update(bytearray)
result = md5.hexdigest()
# print(result)
return result
def hex_2_str(v: int):
v = v & 0xffffffff # 将结果转换为32位有符号整数
return str(hex(v))[2:]
def get_md5Array(hex_md5):
md5_list = [hex_md5[i: i + 8] for i in range(0, len(hex_md5), 8)]
for j in range(len(md5_list)):
m = [md5_list[j][i: i + 2] for i in range(0, len(md5_list[j]), 2)]
md5_list[j] = "".join(m[::-1])
# print(md5_list)
md5_int_list = []
for q in md5_list:
decimal_num = int(q, 16)
# decimal_num = is_overflow(decimal_num)
md5_int_list.append(decimal_num)
# print(md5_int_list)
return md5_int_list
def hex_reserve(i):
st1 = hex_2_str(i)
st1 = '0' * (8 - len(st1)) + st1 if len(st1) < 8 else st1
return "".join([st1[i: i + 2] for i in range(0, len(st1), 2)][::-1])
def logical_left_shift(n, bits):
# 有符号左移
return ctypes.c_int(n << bits).value
def logical_right_shift(n, bits):
# 无符号右移
return (n % 0x100000000) >> bits
def python_2_js(value, size):
# python转为和js一样 ^
return (value ^ size) % (2**32)
def iu(jo):
jp = []
for jq in range(0, len(jo), 2):
jr = jo[jq:jq + 2]
js = int(jr, 16)
jp.append(js)
# print(jp)
return jp
def ix(jo):
if jo is None:
jo = []
jp = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]
result = "".join([jp[jo_item >> 4 & 15] + jp[15 & jo_item] for jo_item in jo])
return result
def is_(jo):
jp = quote(jo)
# print(jp)
jq = []
jr = 0
while jr < len(jp):
js = jp[jr]
if "%" == js:
jt = jp[jr + 1] + jp[jr + 2]
ju = int(jt, 16)
jq.append(ju)
jr += 2
else:
jq.append(ord(js[0]))
jr += 1
# print(len(jq), jq)
return jq
def _iD(jo):
raw_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
new_table = 'ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5='
dictionary_decode = str.maketrans(new_table, raw_table) # 创建字符映射关系 用于base64decode
dictionary_encode = dict(zip(dictionary_decode.values(), dictionary_decode.keys())) # 创建一个与上面反向的映射关系用于base64encode
result_b64 = base64.b64encode(bytearray(jo)).decode()
new_result_b64 = result_b64.translate(dictionary_encode)
# print(new_result_b64)
return new_result_b64
def get_k2(jo, jp):
"""
iO(jo, jp)
jo: url相关的数组
jp:
"""
m = len(jo) % 4
jj = jp ^ len(jo)
ju = 1540483477 # 固定
# 每四个为一组
for i in range(0, len(jo) - m, 4):
tmp1 = (jo[i] & 255) | logical_left_shift((jo[i + 1] & 255), 8)
tmp2 = logical_left_shift((jo[i + 2] & 255), 16)
tmp3 = tmp1 | tmp2
tmp4 = tmp3 | logical_left_shift(jo[i + 3] & 255, 24)
tmp5 = (tmp4 & 65535) * ju
tmp6 = logical_left_shift((((logical_right_shift(tmp4, 16)) * ju) & 65535), 16)
tmp7 = tmp5 + tmp6
tmp8 = python_2_js(tmp7, logical_right_shift(tmp7, 24))
tmp9 = (tmp8 & 65535) * ju
tmp10 = logical_left_shift((((logical_right_shift(tmp8, 16)) * ju) & 65535), 16)
tmp11 = tmp9 + tmp10
tmp12 = (jj & 65535) * ju
tmp13 = logical_left_shift((((logical_right_shift(jj, 16)) * ju) & 65535), 16)
tmp14 = tmp12 + tmp13
jj = python_2_js(tmp11, tmp14)
i = i + 4
if m == 3:
jj = python_2_js(jj , logical_left_shift(255 & jo[i + 2], 16))
jj = python_2_js(jj , logical_left_shift(255 & jo[i + 1], 8))
jj = python_2_js(jj, (255 & jo[i]))
jj = (65535 & jj) * ju + logical_left_shift(logical_right_shift(jj, 16) * ju & 65535, 16)
elif m == 2:
jj = python_2_js(jj , logical_left_shift(255 & jo[i + 1], 8))
jj = python_2_js(jj, (255 & jo[i]))
jj = (65535 & jj) * ju + logical_left_shift(logical_right_shift(jj, 16) * ju & 65535, 16)
elif m == 1:
jj = python_2_js(jj, (255 & jo[i]))
jj = (65535 & jj) * ju + logical_left_shift(logical_right_shift(jj, 16) * ju & 65535, 16)
tmp0 = python_2_js(jj, logical_right_shift(jj, 13))
tmp1 = (tmp0 & 65535) * ju
tmp2 = logical_left_shift((logical_right_shift(tmp0, 16) * ju) & 65535, 16)
tmp3 = tmp1 + tmp2
tmp4 = logical_right_shift(python_2_js(tmp3, logical_right_shift(tmp3, 15)), 0)
res = ctypes.c_int(tmp4 ^ ju).value
# print(res)
return res
def iy(jo, jq):
jr = 0
js = 0
jt = []
ju = len(jq)
# print(f"{jq} 长度为 {ju}")
for jv in range(ju):
jr = (jr + 1) % 256
js = (js + jo[jr]) % 256
jp = jo[jr]
jo[jr] = jo[js]
jo[js] = jp
jt.append(ord(jq[jv]) ^ jo[(jo[jr] + jo[js]) % 256])
return jt
def get_k1(jZ, jp):
array1 = [i for i in range(256)]
array2 = jZ
jr = 0
for i in range(256):
jt = i % 256
n = i % 16
js = array1[jt]
jr = (jr + js + array2[n] + 31) % 256
array1[jt] = array1[jr]
array1[jr] = js
# print(f"ararry1 => {array1}")
jt_ = iy(array1, jp)
# print(f"_arrary1 => {array1}")
jZ.extend(jt_)
# print(f" jZ => {len(jZ)} => {jZ}")
k1 = _iD(jZ) # a5
return k1
def get_kh(jS, kb, kf, x0=4):
m = [0] * 2
m[0] = jS << x0
m[1] = jS << (32 - x0)
m[0] = m[0] | m[1]
kg = m[0]
kf[0] = kf[0] ^ kg
kf[1] = kf[1] ^ kb
kf[2] = kf[2] ^ kb ^ kg
kf[3] = kf[3] ^ kf[0]
kh_list = []
for i in kf:
kh_list.append(hex_reserve(i))
kh = "".join(kh_list)
return kh
def iv(jo):
jp = []
jp.append((jo >> 24) & 255)
jp.append((jo >> 16) & 255)
jp.append((jo >> 8) & 255)
jp.append(jo & 255)
return jp
def bc(p):
t = 256
f = [None] * t
while t:
c = 8
t -= 1
a = t
while c:
if a & 1:
a = python_2_js(logical_right_shift(a, 1), 3988292384)
else:
a = logical_right_shift(a, 1)
c -= 1
f[t] = logical_right_shift(a, 0)
c = 0
t = -1
while c < len(p):
t = f[python_2_js(255 & t, p[c])] ^ logical_right_shift(t, 8)
c += 1
return python_2_js(t, 306674911) - 2 ** 32
def get_a6(a6):
"""
a6
a6的部分内容
:return:
"""
env = from_a6(a6[4:])
A = gzip.compress(env.encode("utf-8"), compresslevel=1)
C = encrypt_aes_cbc(A.hex())
C = "w1.3" + C
return C
def get_JP(url):
"""
url
"""
parse_result = urlparse(url)
path = parse_result.path
param_dict = parse_qs(parse_result.query)
param_list = list(param_dict.keys())
param_list.sort() # 排序从小到大
params = "&".join([f'{i}={quote_plus(param_dict[i][0])}' for i in param_list])
target_ = f"GET {path} {params}"
JP = is_(target_)
return JP
def get_a5(i, y, qa):
"""
a5
"""
d = i & 4294967295
p = iv(d)
g = []
g.extend(is_(y))
g.extend(p)
v = md5_(bytearray(g))
m = iu(v[:15])
f = 0 # env 环境值
m[7] = 255 & (f ^ bc(p))
m.extend(p)
l = iv(4294967295 & bc(m))
m.extend(l)
a5 = get_k1(m, qa)
return a5
def get_main(i, a6, a3, a7, url, jq):
# i = 1723618362188 # 时间戳 TODO:需要二次处理
o = i & 4294967295
a5 = get_a5(i, a6, jq)
tmp = is_(a6)
l = iv(o)
tmp.extend(l)
g = bytearray(tmp)
v = md5_(g)
w = get_JP(url)
S = get_k2(w, i)
C = iv(S)
A = get_k2(bytearray(is_(a5)), i)
I = iv(A)
c = [S, A, S ^ o, S ^ A ^ o]
O = iu("".join([hex_reserve(i) for i in c]))
tmp = []
tmp.extend(C)
tmp.extend(I)
tmp.extend(O)
D = ix(tmp) # a4
d = logical_right_shift(A, 0)
T = f"1.2{i}{a3}{D}{d}{v}{a7}"
tmp = md5_(bytearray(is_(T)))
j = get_md5Array(tmp)
d1 = get_kh(o, d, j, x0=3) # 到这一步就是 小程序的 d1
mtgsig = {
"a1": "1.2",
"a2": i, # 时间戳 估计有校验
"a3": a3, # 服务器下发
"a4": D, # 计算
"a5": a5, # 计算 暂时不清楚入参
"a6": a6, # 计算环境 需要确定环境信息
"a7": a7, # wxcode 固定
"x0": 3, # 固定
"d1": d1 # 计算 比h5少一步
}
return mtgsig
def from_a6(a6):
"""
a6
:param a6:
:return:
"""
decode_res = decrypt_aes_128_cbc(a6)
plain_text = gzip.decompress(bytes.fromhex(decode_res.decode()))
p = json.loads(plain_text.decode())
p[-3] = int(time.time()) # 修改时间戳
p[-4][-6] = "3.9.12" # 修改版本
p[-4][-9] = "3.6.3" # 修改另外一个版本 **目前没有看到其他的需要修改的地方
res = json.dumps(p, ensure_ascii=False, separators=(',', ':'))
return res
def mtgsig_run(url, b8, a3, a6):
"""
mtgsig
:param url:
:param b8:
:param a3: a3服务器下发
:param a6:
:return:
"""
a6 = get_a6(a6)
a7 = "wx734c1ad7b3562129"
# m = 1723688535465
i = int(time.time() * 1000)
# diff = i - m
# i = i - diff # 处理时间 这块暂时先写死
_jq = {"b7": int(time.time()) - 200,
"b1": {"miniProgram": {"appId": "wx734c1ad7b3562129", "envVersion": "release", "version": "9.37.232"}},
"b6": "", "b8": b8, "b2": "pages/poi/poi"}
jq = json.dumps(_jq, separators=(',', ':'))
mtgsig = json.dumps(get_main(i, a6, a3, a7, url, jq), ensure_ascii=False, separators=(',', ':'))
return mtgsig
if __name__ == '__main__':
url = "https://mapi.dianping.com/mapi/wechat/weshop.bin?yodaReady=wx&csecappid=wx734c1ad7b3562129&csecplatform=3&csecversionname=9.64.0&csecversion=1.4.0&optimus_uuid=18b229261a3c8-c53ac1e606702-0-0-18b229261a3c8&optimus_platform=13&optimus_partner=203&optimus_risk_level=71&optimus_code=10&redirect_from=pages%252Fdetail%252Fdetail&shopUuid=k9Kcg8I6w4GBcuGF&shopType=10&shopStyle=bigpic&online=1&shopuuid=k9Kcg8I6w4GBcuGF&pageName=shop&lat=40.055335628194634&lng=116.35128357647478&mtsiReferrer=pages%252Fdetail%252Fdetail%253Fredirect_from%253Dpages%25252Fdetail%25252Fdetail%2526shopUuid%253Dk9Kcg8I6w4GBcuGF%2526shopType%253D10%2526shopStyle%253Dbigpic%2526online%253D1%2526shopuuid%253Dk9Kcg8I6w4GBcuGF%2526shopId%253Dk9Kcg8I6w4GBcuGF%2526pageName%253Dshop&cookieid=-4TzrChF1yPgyQBn5D7NrKC2_vuFu9oL8DiAq28T-Kk&device_system=MAC&wxmp_version=9.64.0"
# print(mtgsig_run(url))
a6 = "w1.3grvNcRFZkE6QoEQ31ALPuc415J6/VKamSdeIXCVWX/DGsGA+taQlBpn7QF0z/ffrSmLkkQW0dqtDnWECJyGr9iFEhTKHYlyKRbYWwffNlvlDZ9tmFet/PyCSfKJw7xVZe8/4QYaAcbk1eJ7tWMUQllVcQDKlxku5Smml4r566JVAOGi48LCGAHxImhjIuI2KomDqOtCQb7Ii2cbGd2Y7vmWjypGGJbTtrztH/1weiEULAxadX72+RdjpXDa8UMgGbVmEUu5LLIwoyNEi6leK6f/QvV9/EvVfb5vtKyL5IAQND5B9g5qndP4GOuTftOM2ylQKb4rguR4TEQsR3uyruBJusZlID67k1d8jPCM+ol2mbFJP88EfBwnrQ+BSDWfVQjjfIj2mWpEnI+oA72gkPxuHteESJaXzZz94udFusf1tUtdf0q15QiQPSBX7JjK2P/lLHMfe+867ZiGMyoivxisrw1d9bbal27BjMTm32IrAN+ywQFoP9bHXNThdxmai"
print(get_a6(a6))
print(from_a6(a6[4:]))

0
douyin/__init__.py

227
douyin/dy_abogus.py

@ -0,0 +1,227 @@
import hashlib, time, random
from functools import reduce
def hash_sm3(data):
sm3 = hashlib.new("sm3")
if isinstance(data, str):
sm3.update(data.encode("utf-8"))
else:
sm3.update(data)
return sm3.hexdigest()
def get_array(timestamp):
array1 = [0] * 4
array1[0] = (timestamp >> 24) & 255
array1[1] = (timestamp >> 16) & 255
array1[2] = (timestamp >> 8) & 255
array1[3] = (timestamp >> 0) & 255
return array1
def deal(orginalString, target, step=20):
"""
"""
result = ""
for i in range(0, len(orginalString), 3):
if i > step:
break
M0 = orginalString[i]
if i >= len(orginalString) - 1:
M1 = M2 = 0
else:
M1 = orginalString[i + 1]
M2 = orginalString[i + 2]
baseNum = (M0 << 16) | (M1 << 8) | M2
num1 = target[(16515072 & baseNum) >> 18]
num2 = target[(258048 & baseNum) >> 12]
if i >= len(orginalString) - 1:
num3 = num4 = target[-1]
else:
num3 = target[(4032 & baseNum) >> 6]
num4 = target[63 & baseNum]
tmpNum = num1 + num2 + num3 + num4
result += tmpNum
return result
def get_fn(code):
fn = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255]
j = 0
for i in range(len(fn)):
j = (j + fn[i] + ord(code[i % 3])) % 256
tmp = fn[i]
fn[i], fn[j] = fn[j], tmp # 替换
return fn
def deal1(orginalString, fn):
"""
"""
tmp1 = 0
F_array = []
for i in range(len(orginalString)):
j = (tmp1 + fn[i + 1]) % 256
tmp2 = fn[j]
tmp1 = fn[i + 1]
fn[i + 1], fn[j] = tmp2, tmp1
# print(f"ua_code: {ua_code[i]} ^ {fn[(tmp1 + tmp2) % 256]}")
F_array.append(orginalString[i] ^ fn[(tmp1 + tmp2) % 256])
tmp1 = j
return F_array
def get_ua_code(o_string, ua, code):
"""
o_string: '\u0000\u0001\u000e'
"""
fn = get_fn(o_string) # 获得256变换后的数组
ua_code = [ord(i) for i in ua] # 需要配合计算的数组
F_array = deal1(ua_code, fn)
ua_base = deal(F_array, code, 117)
return ua_base
def get_head(shift_list):
"""
"""
random_code = int(random.random() * 10000)
tmp_num1 = random_code & 255
tmp_num2 = (random_code >> 8) & 255
tmp = shift_list[0] & 85
num1 = (tmp_num1 & 170) | tmp
tmp2 = shift_list[0] & 170
num2 = (tmp_num1 & 85) | tmp2
tmp3 = shift_list[1] & 85
num3 = (tmp_num2 & 170) | tmp3
tmp4 = shift_list[1] & 170
num4 = (tmp_num2 & 85) | tmp4
return [num1, num2, num3, num4]
def get_original(o_string, originalString, code):
fn = get_fn(o_string)
# print(f"fn => {len(fn)} {str(fn).replace(' ', '')}")
original_string1 = deal1(originalString, fn)
# print(f"original_string1 {len(original_string1)}=> {len(original_string1)} {str(original_string1).replace(' ', '')}")
original_string = []
# 这下面仨字符固定了 TODO: 可能是随机的 每次都不一样
a1 = get_head([3, 45]) # header1
a2 = get_head([1, 0]) # header2
a3 = get_head([1, 5]) # header3
original_string.extend(a1)
original_string.extend(a2)
original_string.extend(a3)
# print(f"original_string => {original_string}")
# code = "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe="
#
original_string.extend(original_string1)
a_bogus = deal(original_string, code, 124)
return a_bogus
def get_h2(env="1440|150|1440|900|0|0|0|0|1440|900|1440|900|1440|150|24|24|MacIntel"):
"""
"""
return [ord(i) for i in env]
def exchange_array(array1, array2, array3, array4, array5, array6, array7):
h1 = [44, array1[0], array2[0], 0, 0, 0, array3[1], array4[21], array5[21], array2[1], array6[23], array1[1],
0, array2[2], array2[3], 1, 0, array3[0], array4[22], array5[22], array6[24], array1[2], 0, 0, array3[2],
0, array1[3], 0, 0, 14, array7[0], array7[1], array3[2], array7[2], array7[3], 3, 400, 1, 400, 1, 67,
0, 0, 0]
return h1
def main(params, ua, timestamp1, timestamp2, base_key):
P = lambda x: [int(x[i:i + 2], 16) for i in range(0, len(x), 2)]
tags = "cus"
pageId = 6241
aid = 6383
array1 = get_array(timestamp1) # 后面计算时候的时间戳
# print(f"array1 => {str(array1).replace(' ', '')}")
array2 = get_array(pageId) # pageId
# print(f"array2 => {str(array2).replace(' ', '')}")
array3 = get_array(aid)[::-1] # aid
# print(f"array3 => {str(array3).replace(' ', '')}")
array4 = P(hash_sm3(bytes.fromhex(hash_sm3(params + tags)))) # 参数加上 "cus"进行二次加密
# print(f"array4 => {str(array4).replace(' ', '')}")
array5 = P(hash_sm3(bytes.fromhex(hash_sm3(tags)))) # cus两次加密结果数组
# print(f"array5 => {str(array5).replace(' ', '')}")
# code = "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe"
array6 = P(hash_sm3(get_ua_code("\u0000\u0001\u000e", ua, base_key["a1"]))) # 计算请求头的数组
# print(f"array6 => {str(array6).replace(' ', '')}")
array7 = get_array(timestamp2) # 加载时间戳
# print(f"array7 => {str(array7).replace(' ', '')}")
h1 = exchange_array(array1, array2, array3, array4, array5, array6, array7) # 整合所有数组
# print(f"h1 => {str(h1).replace(' ', '')}")
h2 = get_h2() # 获取上半段数据
# print(f"h2 => {str(h2).replace(' ', '')}")
h3 = reduce(lambda x, y: int(x) ^ int(y), h1)
# print(f"h3 => {str(h3).replace(' ', '')}")
h = []
h.extend(h1)
h.extend(h2)
h.append(h3)
# print(f"h => {len(h)} {str(h).replace(' ', '')}")
a_bogus = get_original("yyy", h, base_key["a2"])
# print(f" a_bogus {len(a_bogus)}=> {a_bogus}")
return a_bogus
def run(params, ua):
base_key = {
"a1": "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe",
"a2": "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe="
}
timestamp1 = 1718786088339 # 后者
timestamp2 = 1718786087904 # js加载时间
timestamp2 = int(time.time() * 1000)
tmp = random.randint(300, 600)
timestamp1 = timestamp2 + tmp
return main(params, ua, timestamp1, timestamp2, base_key)
if __name__ == '__main__':
params = "device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=7123849705431272712&update_version_code=170400&pc_client_type=1&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1440&screen_height=900&browser_language=zh-CN&browser_platform=MacIntel&browser_name=Chrome&browser_version=125.0.0.0&browser_online=true&engine_name=Blink&engine_version=125.0.0.0&os_name=Mac+OS&os_version=10.15.7&cpu_core_num=2&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50&webid=7382044487177143862&msToken=Ru8XaSvg7YcHk135aj68vgAK247SND6YxUW8KgdHWeRJHk_On01S3Acja3fqH4INQjtIwnpz-FDy9BtVQ_qO_MeIkErjRima9r6t461khRCmTXZcHs7NMRrj7pC43w%3D%3D"
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
a_bogus = run(params, ua)

152
douyin/dy_ttwid.py

@ -0,0 +1,152 @@
# coding:utf-8
import argparse
import os, sys
# 相对路径补充
root_path = os.path.abspath(os.path.dirname(__file__)).split('api-py')[0] + "api-py"
sys.path.append(root_path)
from concurrent.futures.thread import ThreadPoolExecutor
from utils.MysqlData import MysqlPoolClient, CRAWLER_DB_CONF_DY
from utils.tool import download_q
from loguru import logger
class DouyinTtwid():
"""
ttwid
signature
"""
def __init__(self, is_proxy: bool):
self.is_proxy = is_proxy
self.sql_list = []
def get_cookie(self, ck=None):
"""
noncettwid)
:param ck:
:return:
"""
headers = {
"authority": "www.douyin.com",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"pragma": "no-cache",
"sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
}
url = "https://www.douyin.com/"
if ck:
status, response = download_q(url, headers, cookies=ck, is_proxy=self.is_proxy)
key = response.cookies["ttwid"]
logger.info(f"获得 ttwid 成功:{key}")
self.ttwid = key
else:
status, response = download_q(url, headers, cookies={}, is_proxy=self.is_proxy)
key = response.cookies["__ac_nonce"]
logger.info(f"获得 __ac_nonce 成功:{key}")
self.nonce = key
return key
def get_signature(self, nonce):
"""
node signature
:param nonce:
:return:
"""
url = f"http://127.0.0.1:3000/douyin/get_signature?nonce={nonce}"
status, response = download_q(url, {}, {})
signature = response.text
logger.info(f"成功获取signature:{signature}; nonce: {nonce}")
return signature
def run(self):
"""
:return:
"""
try:
__ac_nonce = self.get_cookie()
if __ac_nonce:
__ac_signature = self.get_signature(__ac_nonce)
ck = {"__ac_nonce": __ac_nonce, "__ac_signature": __ac_signature, "__ac_referer": "__ac_blank"}
ttwid = self.get_cookie(ck)
if ttwid:
sql_ttwid = f"ttwid={ttwid};"
sql = "INSERT INTO `douyin_cookie_video_ly_copy2_test`(`cookie`, `status`, `source`, `time`) " \
"VALUES('%s', '%s', '%s', NOW());" % (sql_ttwid, 2, 0)
self.sql_list.append(sql)
return ttwid
else:
logger.error("获得 ttwid 失败 ")
else:
logger.error("获得 nonce 失败 ")
except Exception as e:
# traceback.print_exc()
logger.error(e)
def insert_data(sql_list):
"""
:param sql_list:
:return:
"""
client = MysqlPoolClient(CRAWLER_DB_CONF_DY)
for sql in sql_list:
try:
logger.success(f"insert cookie -> {sql}")
client.getOne(sql)
except Exception as e:
logger.error(f"insert cookie -> {sql}")
def write_file(l):
"""
:param l:
:return:
"""
with open("ttwid.txt", "w") as f:
f.write("\n".join(l))
f.close()
logger.info("文件保存成功")
def create_by_thread(douyin, count):
"""
:param slid:
:param count:
:return:
"""
with ThreadPoolExecutor(max_workers=3) as t:
obj_list = []
for i in range(count * 2):
obj = t.submit(douyin.run)
obj_list.append(obj)
insert_data(douyin.sql_list)
# write_file(douyin.sql_list)
logger.info(f"[sum] 并发任务 需要生成数量 {count}, 实际抓取数量 {count*2}, 实际生成数量 {len(douyin.sql_list)}, 成功率 {len(douyin.sql_list)/(count*2)}")
if __name__ == '__main__':
dy = DouyinTtwid(is_proxy=True)
parser = argparse.ArgumentParser(description='get douyin.com cookie')
parser.add_argument('-c', type=int, default=100, help="needed cookie count;default count=100;")
args = parser.parse_args()
args_count = args.c
create_by_thread(dy, args_count)

0
hnw/__init__.py

114
hnw/hnw_hook.py

@ -0,0 +1,114 @@
from hashlib import md5, sha1, sha384
import math, random, time,requests
def MD5(s):
m = md5()
m.update(str.encode(s, encoding="utf-8"))
return m.hexdigest()
def SHA1(s):
sha = sha1()
sha.update(str.encode(s, encoding="utf-8"))
return sha.hexdigest()
def SHA384(s):
sha = sha384()
sha.update(str.encode(s, encoding="utf-8"))
return sha.hexdigest()
def I(nonce="03423d9c06bd4bbb45b5fb9059a9ed4f"):
return MD5(nonce)
def j(timeStamp):
return SHA1(timeStamp)
def P(nonce, client_id):
return MD5(nonce + client_id)
def S(time):
s = "EOi^0N5sWWHhkrF2A0gekY9U20BgnAcr"
res = SHA1(s + str(time))
return res[len(res)-16:len(res) - 1]
def o(e, f):
b = e
t = f
n = ""
r1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
while b:
o = b % 36
l = b / 36
n = r1[o] + n
b = round(math.floor(l))
return ("0000000000000000" + n)[-t:]
def get_trace_id(e):
return o(e, 9) + o(random.randint(0, 78364164095), 7)
def get_uuid(t):
e = int(time.time() * 1000)
# e = 1700013194061
template = "xxxxxxxxxxxxxyxxxxyxxxxxxxxxxxxx" if t else "xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxx"
uuid = ""
i = -1
uuids = []
while i < len(template) - 1:
i += 1
if template[i] == "-":
uuids.append(uuid)
uuid = ""
continue
elif template[i] == "4":
uuid += template[i]
continue
n = (e + 16 * random.random()) % 16
e = e // 16
char = format(int(n) if template[i] == 'x' else 3 & int(n) | 8, "x")
uuid += char
uuids.append(uuid)
return "-".join(uuids)
def get_header_nonce():
return get_uuid(True)
def server(key):
url = f"http://127.0.0.1:3000/hn/get_signature?nonce={key}"
res = requests.get(url)
return res.text
def build_headers():
times = int(time.time() * 1000)
nonce = get_header_nonce()
sid = f"S_{get_trace_id(times - 300)}"
traceid = get_trace_id(times)
client_id = get_uuid(False)
s = S(times)
M = f'{I(nonce)}!{j(str(times))}!{P(nonce, client_id)}!{server(s)}'
h = {
"x-b3-traceid": traceid,
"x-client-id": client_id,
"x-client-nonce": nonce,
"x-client-sign": SHA384(M),
"x-client-time": str(times),
"x-client-sid": sid
}
# print(h)
return h
if __name__ == '__main__':
build_headers()

27
kuaishou/README.md

@ -0,0 +1,27 @@
# 快手
协议过滑块,激活cookie did字段,主要分三个部分
- 获取未激活的did
- 携带did,通过接口获取验证码信息
- 协议过验证码,激活did
- 获取图片,进行缺口识别
- 通过识别位置进行参数拼接,轨迹模拟
- 调用node服务将参数加密,获得最终验证参数
最终数据写入sql
代码位置:
`
172.16.10.39: /data1/api-py/kuaishou
`
执行:
```shell
conda activate py3.8
python ks_slide.py -h
```
文件说明
- `ks_http.py`: 公共变量
- `ks_slide.py`: 主要逻辑
- `ks_make_trace.py`: 模拟轨迹生成算法

0
kuaishou/__init__.py

161
kuaishou/ks_http.py

@ -0,0 +1,161 @@
import json
import random
from enum import Enum
import requests
def get_gpuInfo():
"""
:return:
"""
random_list = ["Intel(R) Iris(TM) Pro Graphics 5200", "Intel(R) Iris(TM) Graphics 5100",
"Intel(R) Iris(TM) Graphics 5000", "Intel(R) Iris(TM) Graphics 4200",
"Intel(R) Iris(TM) Graphics 4400", "Intel(R) Iris(TM) Graphics 4600",
"Intel(R) Iris(TM) Pro Graphics 6200", "Intel(R) Iris(TM) Pro Graphics 6100",
"Intel(R) Iris(TM) Graphics 6000", "Intel(R) Iris(TM) Graphics 5500",
"Intel(R) Iris(TM) Graphics 5300", f"Intel(R) Iris(TM) Graphics {random.randint(100, 999)}"]
random_list = [f"Intel(R) Iris(TM) Graphics {random.randint(100, 999)}"]
gpuInfo = {"glRenderer":"WebKit WebGL","glVendor":"WebKit","unmaskRenderer":f"ANGLE (Intel, ANGLE Metal Renderer: {random.choice(random_list)} Unspecified Version)","unmaskVendor":"Google Inc. (Intel)"}
return json.dumps(gpuInfo, separators=(',', ':'))
class BaseHeaders(Enum):
"""
"""
# 获取快手session(快手api接口) header
HEADERS = {
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Origin": "https://www.kuaishou.com",
"Pragma": "no-cache",
# "Referer": "https://www.kuaishou.com/profile/3xsdu49r65skedk",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
"accept": "*/*",
"content-type": "application/json",
"sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
# 获取快手过验证码请求headers
VERIFY_HEADERS = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
"Origin": "https://captcha.zt.kuaishou.com",
"Pragma": "no-cache",
# "Referer": Referer,
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
"sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
PIC_HEADERS = {
"Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Pragma": "no-cache",
# "Referer": referer,
"Sec-Fetch-Dest": "image",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
"sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
DOC_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
"sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
class BaseParam(Enum):
VERIFY_PARAM = {
"captchaSn": "",
"bgDisWidth": 316, # 固定
"bgDisHeight": 184, # 固定
"cutDisWidth": 56, # 固定
"cutDisHeight": 56, # 固定
"relativeX": "", # 滑动距离 应该是缩放过了
"relativeY": "", # config接口里的y 缩放 136 * 56/122 ()
"trajectory": "",
# "gpuInfo": "{\"glRenderer\":\"WebKit WebGL\",\"glVendor\":\"WebKit\",\"unmaskRenderer\":\"ANGLE (Apple, ANGLE Metal Renderer: Apple M1 Pro, Unspecified Version)\",\"unmaskVendor\":\"Google Inc. (Apple)\"}",
# "captchaExtraParam": "{\"ua\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\",\"timeZone\":\"UTC+8\",\"language\":\"zh\",\"cpuCoreCnt\":\"8\",\"platform\":\"MacIntel\",\"riskBrowser\":\"false\",\"webDriver\":\"false\",\"exactRiskBrowser\":\"false\",\"webDriverDeep\":\"false\",\"exactRiskBrowser2\":\"false\",\"webDriverDeep2\":\"false\",\"battery\":\"1\",\"plugins\":\"1a68ba429dd293b14e41a28b6535aa590\",\"resolution\":\"1512x982\",\"pixelDepth\":\"30\",\"colorDepth\":\"30\",\"canvasGraphFingerPrint\":\"10681dead230ee9ca8e67e8ddb0a96013\",\"canvasGraph\":\"10681dead230ee9ca8e67e8ddb0a96013\",\"canvasTextFingerPrintEn\":\"11825830fcdd552d4d3e81af588208d71\",\"canvasTextEn\":\"11825830fcdd552d4d3e81af588208d71\",\"canvasTextFingerPrintZh\":\"19257bc614b1b886224a819517f0715e3\",\"canvasTextZh\":\"19257bc614b1b886224a819517f0715e3\",\"webglGraphFingerPrint\":\"19e85c5151728b98f00fc5a3c6c8bd2dd\",\"webglGraph\":\"19e85c5151728b98f00fc5a3c6c8bd2dd\",\"webglGPUFingerPrint\":\"1777f4110dd337e62088858e6e8df0288\",\"webglGpu\":\"1777f4110dd337e62088858e6e8df0288\",\"cssFontFingerPrintEn\":\"1329bb845104882a7754a3bdf007ff6fc\",\"fontListEn\":\"1329bb845104882a7754a3bdf007ff6fc\",\"cssFontFingerPrintZh\":\"11997d7fc5c7f90fad6abcbadabebb249\",\"fontListZh\":\"11997d7fc5c7f90fad6abcbadabebb249\",\"voiceFingerPrint\":\"14c6f007f1166921565a8aa1c5cfac1c6\",\"audioTriangle\":\"14c6f007f1166921565a8aa1c5cfac1c6\",\"nativeFunc\":\"1973dcbb27a04c3a2ee240d9d2549e105\",\"key1\":\"web_84547d0256a5ac1f023cf5cb911e70a8\",\"key2\":1698917540365,\"key3\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\",\"key4\":\"20030107\",\"key5\":\"zh\",\"key6\":\"Gecko\",\"key7\":1512,\"key8\":982,\"key9\":1512,\"key10\":950,\"key11\":360,\"key12\":360,\"key13\":945,\"key14\":1512,\"key15\":\"00000111\",\"key16\":1,\"key17\":1,\"key18\":[],\"key19\":{},\"key20\":[],\"key21\":{},\"key22\":[],\"key23\":{},\"key24\":[],\"key25\":{},\"key26\":{\"key27\":[\"0,1,21255,58,359,prepare1\",\"1,1,21261,31,332,prepare1\",\"2,1,21268,22,324,prepare1\",\"3,1,21277,5,309,prepare1\",\"4,1,21284,1,306,prepare1\",\"5,1,21600,2,304,prepare1\",\"6,1,21608,6,305,prepare1\",\"7,1,21617,10,306,prepare1\",\"8,1,21627,14,307,prepare1\",\"9,1,21632,20,308,prepare1\",\"10,3,22111,39,307\",\"11,1,22226,39,307,prepare2\",\"12,1,22230,42,307,prepare2\",\"13,1,22239,47,307,prepare2\",\"14,1,22247,53,307,prepare2\",\"15,1,22255,62,307,prepare2\",\"16,1,22264,75,307,prepare2\",\"17,1,22272,88,307,prepare2\",\"18,1,22281,95,307,prepare2\",\"19,1,22289,99,307,prepare2\",\"20,1,22297,105,307,prepare2\",\"21,4,22356,116,307\",\"22,2,22455,116,307,prepare3\",\"23,1,22456,117,307,prepare3\"],\"key28\":[],\"key29\":[],\"key30\":[],\"key31\":{\"prepare1\":\"9,1,21632,20,308\",\"prepare2\":\"20,1,22297,105,307\",\"prepare3\":\"23,1,22456,117,307\"},\"key32\":{},\"key33\":{},\"key34\":{}},\"key35\":\"bf3957c9488bdb2a7a453824e9ee2d6a\",\"key36\":\"f22a94013fc94e90e2af2798023a1985\",\"key37\":2,\"key38\":\"not support\",\"key39\":8}"
"gpuInfo": get_gpuInfo(),
"captchaExtraParam": '{"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36","timeZone":"UTC+8","language":"zh-CN","cpuCoreCnt":"2","platform":"MacIntel","riskBrowser":"false","webDriver":"false","exactRiskBrowser":"false","webDriverDeep":"false","exactRiskBrowser2":"false","webDriverDeep2":"false","battery":"1","plugins":"1a68ba429dd293b14e41a28b6535aa590","resolution":"1440x900","pixelDepth":"24","colorDepth":"24","canvasGraphFingerPrint":"170a0a884fada8bac5f9e42250271aaf4","canvasGraph":"170a0a884fada8bac5f9e42250271aaf4","canvasTextFingerPrintEn":"1996477fa0627ebb63c728fa32cacce79","canvasTextEn":"1996477fa0627ebb63c728fa32cacce79","canvasTextFingerPrintZh":"1528ffe56c43c1c514d0de433838f8134","canvasTextZh":"1528ffe56c43c1c514d0de433838f8134","webglGraphFingerPrint":"11a9644c09ae3fd0a918f6f6f712f43c6","webglGraph":"11a9644c09ae3fd0a918f6f6f712f43c6","webglGPUFingerPrint":"1bd896c110303ed46f715b5ac4cbf8329","webglGpu":"1bd896c110303ed46f715b5ac4cbf8329","cssFontFingerPrintEn":"1329bb845104882a7754a3bdf007ff6fc","fontListEn":"1329bb845104882a7754a3bdf007ff6fc","cssFontFingerPrintZh":"11997d7fc5c7f90fad6abcbadabebb249","fontListZh":"11997d7fc5c7f90fad6abcbadabebb249","voiceFingerPrint":"129d646e8a0c62f5c09bead9ff4a0f29d","audioTriangle":"129d646e8a0c62f5c09bead9ff4a0f29d","nativeFunc":"1973dcbb27a04c3a2ee240d9d2549e105"}'
}
class BaseURL(Enum):
# nodejs服务
NodeURL = "http://127.0.0.1:3000/kuaishou/get_verifyParam"
def download_q(url, headers, cookies, data=None, is_proxy=False, timeout=10):
"""
:param url:
:param headers:
:param cookies:
:param data:
:param is_proxy:
:param timeout:
:return:
"""
proxy_list = [f"172.24.12.23:{random.randint(45001, 45250)}", f"172.18.128.225:{random.randint(45001, 45250)}",
"16HFBVJC:897944@u270.40.tp.16yun.cn:6448"]
# proxy = f"{random.choice(proxy_list)}:{random.randint(45001, 45250)}" # 修改代理
proxy = random.choice(proxy_list)
proxies = {'http': proxy, 'https': proxy} # 代理初始化
if data:
if is_proxy:
response = requests.post(url, headers=headers, data=data, proxies=proxies, cookies=cookies, timeout=timeout)
else:
response = requests.post(url, headers=headers, data=data, cookies=cookies, timeout=timeout)
else:
if is_proxy:
response = requests.get(url, headers=headers, proxies=proxies, cookies=cookies, timeout=timeout)
else:
response = requests.get(url, headers=headers, cookies=cookies, timeout=timeout)
return response
def download_pic(path, content):
with open(path, "wb") as f:
f.write(content)
f.close()
# logger.info(f"图片保存成功 》》》》{path}")
return path

58
kuaishou/ks_make_trace.py

@ -0,0 +1,58 @@
import random
class Generate_trajectory:
@staticmethod
def __ease_out_expo(sep):
if sep == 1:
return 1
else:
return 1 - pow(2, -10 * sep)
@staticmethod
def __generate_y():
init_y = random.randint(10, 30)
y = [init_y]
for i in range(20):
_ = random.randint(-1, 1)
y += [y[-1] + _] * random.randint(5, 10)
return y
@classmethod
def get_slide_track(cls, distance):
"""
:param distance:
"""
if not isinstance(distance, int) or distance < 0:
raise ValueError(f"distance类型必须是大于等于0的整数: distance: {distance}, type: {type(distance)}")
# 共记录count次滑块位置信息
count = 30 + int(distance / 20)
# 初始化滑动时间
t = random.randint(50, 100)
# 记录上一次滑动的距离
_x = random.randint(0, 5)
# _x = 0
_y = cls.__generate_y()
# 初始化轨迹列表
slide_track = [
'|'.join([str(random.randint(15, 30)), str(_y.pop(0)), str(0)])
]
for i in range(count):
# 已滑动的横向距离
x = round(cls.__ease_out_expo(i / count) * distance)
# 滑动过程消耗的时间
t += random.randint(30, 40)
if x == _x:
continue
slide_track.append('|'.join([str(x), str(_y[i]), str(t)]))
_x = x
slide_track.append(slide_track[-1])
return ','.join(slide_track)
if __name__ == '__main__':
print(Generate_trajectory().get_slide_track(500))

336
kuaishou/ks_slide.py

@ -0,0 +1,336 @@
# coding:utf-8
import json
import time
import os, sys
import argparse
# 相对路径补充
root_path = os.path.abspath(os.path.dirname(__file__)).split('api-py')[0] + "api-py"
sys.path.append(root_path)
from concurrent.futures._base import as_completed
from concurrent.futures.thread import ThreadPoolExecutor
from urllib.parse import urlparse, quote_plus, parse_qs
from utils.MysqlData import MysqlPoolClient, CRAWLER_DB_CONF_KS
from kuaishou.ks_http import download_q, BaseHeaders, BaseParam, download_pic, BaseURL
from utils.Logger import MyLogger
from loguru import logger
from kuaishou.ks_make_trace import Generate_trajectory
from utils.ImageHelper import recognize_gap
import traceback
class KSSlide(object):
"""
"""
def __init__(self, count=5, is_proxy=False):
self.is_proxy = is_proxy
self.count = count
self.did_url = "https://www.kuaishou.com/short-video/3xdhjtb9xs7xpaw?authorId=3xsdu49r65skedk&streamSource=profile&area=profilexxnull"
self.api_url = "https://www.kuaishou.com/graphql"
self.headers = BaseHeaders.HEADERS.value
self.doc_headers = BaseHeaders.DOC_HEADERS.value
self.pic_headers = BaseHeaders.PIC_HEADERS.value
self.verify_headers = BaseHeaders.VERIFY_HEADERS.value
self.did = ""
self.captchaSession = ""
self.captcha_refer = ""
self.sql_list = []
self.post_data = {
"search": {
"referer": "",
"data": {
"operationName": "visionSearchPhoto",
"variables": {
"keyword": "f1",
"pcursor": "",
"page": "search"
},
"query": "fragment photoContent on PhotoEntity {\n __typename\n id\n duration\n caption\n originCaption\n likeCount\n viewCount\n commentCount\n realLikeCount\n coverUrl\n photoUrl\n photoH265Url\n manifest\n manifestH265\n videoResource\n coverUrls {\n url\n __typename\n }\n timestamp\n expTag\n animatedCoverUrl\n distance\n videoRatio\n liked\n stereoType\n profileUserTopPhoto\n musicBlocked\n}\n\nfragment recoPhotoFragment on recoPhotoEntity {\n __typename\n id\n duration\n caption\n originCaption\n likeCount\n viewCount\n commentCount\n realLikeCount\n coverUrl\n photoUrl\n photoH265Url\n manifest\n manifestH265\n videoResource\n coverUrls {\n url\n __typename\n }\n timestamp\n expTag\n animatedCoverUrl\n distance\n videoRatio\n liked\n stereoType\n profileUserTopPhoto\n musicBlocked\n}\n\nfragment feedContent on Feed {\n type\n author {\n id\n name\n headerUrl\n following\n headerUrls {\n url\n __typename\n }\n __typename\n }\n photo {\n ...photoContent\n ...recoPhotoFragment\n __typename\n }\n canAddComment\n llsid\n status\n currentPcursor\n tags {\n type\n name\n __typename\n }\n __typename\n}\n\nquery visionSearchPhoto($keyword: String, $pcursor: String, $searchSessionId: String, $page: String, $webPageArea: String) {\n visionSearchPhoto(keyword: $keyword, pcursor: $pcursor, searchSessionId: $searchSessionId, page: $page, webPageArea: $webPageArea) {\n result\n llsid\n webPageArea\n feeds {\n ...feedContent\n __typename\n }\n searchSessionId\n pcursor\n aladdinBanner {\n imgUrl\n link\n __typename\n }\n __typename\n }\n}\n"
},
},
"video": {},
"user": {
"referer": "https://www.kuaishou.com/profile/3xsdu49r65skedk",
"data": {
"operationName": "visionProfilePhotoList",
"variables": {
"userId": "3xsdu49r65skedk",
"pcursor": "",
"page": "profile"
},
"query": "fragment photoContent on PhotoEntity {\n __typename\n id\n duration\n caption\n originCaption\n likeCount\n viewCount\n commentCount\n realLikeCount\n coverUrl\n photoUrl\n photoH265Url\n manifest\n manifestH265\n videoResource\n coverUrls {\n url\n __typename\n }\n timestamp\n expTag\n animatedCoverUrl\n distance\n videoRatio\n liked\n stereoType\n profileUserTopPhoto\n musicBlocked\n}\n\nfragment recoPhotoFragment on recoPhotoEntity {\n __typename\n id\n duration\n caption\n originCaption\n likeCount\n viewCount\n commentCount\n realLikeCount\n coverUrl\n photoUrl\n photoH265Url\n manifest\n manifestH265\n videoResource\n coverUrls {\n url\n __typename\n }\n timestamp\n expTag\n animatedCoverUrl\n distance\n videoRatio\n liked\n stereoType\n profileUserTopPhoto\n musicBlocked\n}\n\nfragment feedContent on Feed {\n type\n author {\n id\n name\n headerUrl\n following\n headerUrls {\n url\n __typename\n }\n __typename\n }\n photo {\n ...photoContent\n ...recoPhotoFragment\n __typename\n }\n canAddComment\n llsid\n status\n currentPcursor\n tags {\n type\n name\n __typename\n }\n __typename\n}\n\nquery visionProfilePhotoList($pcursor: String, $userId: String, $page: String, $webPageArea: String) {\n visionProfilePhotoList(pcursor: $pcursor, userId: $userId, page: $page, webPageArea: $webPageArea) {\n result\n llsid\n webPageArea\n feeds {\n ...feedContent\n __typename\n }\n hostName\n pcursor\n __typename\n }\n}\n"
}
},
"comment": {
"referer": "https://www.kuaishou.com/short-video/3xdhjtb9xs7xpaw?authorId=3xsdu49r65skedk&streamSource=profile&area=profilexxnull",
"data": {
"operationName": "commentListQuery",
"variables": {
"photoId": "3xdhjtb9xs7xpaw",
"pcursor": ""
},
"query": "query commentListQuery($photoId: String, $pcursor: String) {\n visionCommentList(photoId: $photoId, pcursor: $pcursor) {\n commentCount\n pcursor\n rootComments {\n commentId\n authorId\n authorName\n content\n headurl\n timestamp\n likedCount\n realLikedCount\n liked\n status\n authorLiked\n subCommentCount\n subCommentsPcursor\n subComments {\n commentId\n authorId\n authorName\n content\n headurl\n timestamp\n likedCount\n realLikedCount\n liked\n status\n authorLiked\n replyToUserName\n replyTo\n __typename\n }\n __typename\n }\n __typename\n }\n}\n"
}
}
}
pass
def get_did(self):
"""
did
"""
response = download_q(self.did_url, self.doc_headers, {}, is_proxy=self.is_proxy)
self.did = response.cookies["did"]
logger.info(f"获得did -> {self.did}")
return self.did
def get_session(self, did, token="", type_="user"):
"""
comment接口拿session
"""
if not did:
if not self.did:
return
self.did = did
headers = self.headers.copy()
headers["Referer"] = self.post_data[type_]["referer"]
if token:
headers["Identity-Verification-Token"] = token
headers["Identity-verification-type"] = "2"
data = json.dumps(self.post_data[type_]["data"], separators=(',', ':'))
cookies = {
"kpf": "PC_WEB",
"kpn": "KUAISHOU_VISION",
"clientid": "3",
"did": self.did
}
response = download_q(self.api_url, headers, cookies, data=data, is_proxy=self.is_proxy)
res = response.json()
status = res["data"].get("captcha")
if status: # 需要验证码 非 400002 暂时抛弃40002状态码
captchaSession_url = res["data"]["captcha"]["url"]
params = parse_qs(urlparse(captchaSession_url).query)
elif res["data"].get("result"):
logger.warning("验证码异常 !400002 由于评论不出验证码 暂时放行! ")
captchaSession_url = res["data"]["url"]
params = parse_qs(urlparse(captchaSession_url).query)
else: # 有数据或者为空 不是验证码问题
s = res.get("data", {}).get("visionProfilePhotoList", {}).get("result")
if s and s == 1: # 这里只对user生效
logger.info(f"did 有效; 携带 token {token}")
else:
logger.warning(f"did 无效 {res}; 携带 token {token}")
return None, None
self.captchaSession = params["captchaSession"][0]
self.captcha_refer = captchaSession_url
# logger.info(f"获得captchaSession 》》 {self.captchaSession}")
return self.captcha_refer, self.captchaSession
def get_config(self, did, Referer, captchaSession, is_save=False):
"""
"""
headers = self.verify_headers.copy()
headers["Referer"] = Referer
timeStamp = str(int(time.time()))
cookies = {
"did": did
}
url = "https://captcha.zt.kuaishou.com/rest/zt/captcha/sliding/config"
data = {
"captchaSession": captchaSession
}
response = download_q(url, headers, cookies, data=data, is_proxy=self.is_proxy)
data = response.json()
# logger.info(f"获得config -> {data}")
captchaSn = data.get("captchaSn")
bgPicUrl = data.get("bgPicUrl") + f"?captchaSn={captchaSn}"
cutPicUrl = data.get("cutPicUrl") + f"?captchaSn={captchaSn}"
self.pic_headers["Referer"] = Referer
bgContent = download_q(bgPicUrl, self.pic_headers, cookies).content
fgcontent = download_q(cutPicUrl, self.pic_headers, cookies).content
if is_save:
bg_path = f"tmp/{timeStamp}_bg_pic.jpg"
fg_path = f"tmp/{timeStamp}_fg_pic.jpg"
download_pic(bg_path, bgContent)
download_pic(fg_path, fgcontent)
else:
bg_path = bgContent
fg_path = fgcontent
disY = data.get("disY")
verifyParam = self.build_param(bg_path, fg_path, captchaSn, disY)
return verifyParam
def build_param(self, bg, fg, captchaSn, y):
"""
"""
distance = recognize_gap(bg, fg)
relativeX = int(distance * 0.46) # 缩放
trajectory = Generate_trajectory().get_slide_track(int(distance * 1.76)) # 1.76 1.764 暂时测定稳定
logger.info(f"缩放距离为 -> {relativeX}")
param = BaseParam.VERIFY_PARAM.value
param["captchaSn"] = captchaSn
param["relativeX"] = relativeX
param["relativeY"] = int(y * 0.46) # config接口里的y 缩放 136 * 56/122 ()
param["trajectory"] = trajectory
# param["gpuInfo"] = "" ## TODO: 需要随机替换
# param["captchaExtraParam"] = ""
def get_plaintext(t: dict):
concat_order = ["captchaSn", "bgDisWidth", "bgDisHeight", "cutDisWidth", "cutDisHeight",
"relativeX", "relativeY", "trajectory", "gpuInfo", "captchaExtraParam"]
return "&".join([k + "=" + quote_plus(str(t[k])).replace("+", "%20") for k in concat_order])
info = get_plaintext(param)
verifyParam = KSSlide.encrypt(info)
return verifyParam
@staticmethod
def encrypt(info):
"""
node-js
"""
url = BaseURL.NodeURL.value
data = {
"info": info
}
response = download_q(url, {}, {}, data=data)
# logger.info(f"node服务获取加密数据")
return response.text
def verify(self, verifyParam, did, refer):
headers = self.verify_headers.copy()
headers["Referer"] = refer
headers["Content-Type"] = "application/json"
cookies = {
"did": did
}
url = "https://captcha.zt.kuaishou.com/rest/zt/captcha/sliding/kSecretApiVerify"
data1 = {
"verifyParam": verifyParam
}
data = json.dumps(data1, separators=(',', ':'))
response = download_q(url, headers, cookies, data=data, is_proxy=self.is_proxy)
logger.info(f"verify 结果 -> {response.json()}")
res = response.json()
captcha_token = res.get("captchaToken", None)
# logger.info(f"获得captchaToken为: {captcha_token}")
return captcha_token
def run(self):
"""
"""
try:
did = self.get_did()
referer, session = self.get_session(did)
if session:
verifyParam = self.get_config(did, referer, session)
if verifyParam:
token = self.verify(verifyParam, did, referer)
if token:
logger.success(f"验证码成功: -> {did}")
sql_demo = "INSERT INTO `ksCookie`(`cookie`, `token`) VALUES('%s', '%s');" % (did, token)
self.sql_list.append(sql_demo)
# logger.info(f"sql_demo {sql_demo}")
self.get_session(did=did, token=token, type_="user")
else:
logger.error("verify 失败")
else:
logger.error("node 生成 参数失败")
else:
logger.error("获得 session 失败")
except Exception as e:
logger.error(e)
traceback.print_exc()
# def write_file(l):
# with open("webdid.txt", "w") as f:
# f.write("\n".join(l))
# f.close()
# logger.info("文件保存成功")
def insert_data(sql_list):
"""
:param sql_list:
:return:
"""
client = MysqlPoolClient(CRAWLER_DB_CONF_KS)
for sql in sql_list:
try:
logger.success(f"insert cookie -> {sql}")
client.getOne(sql)
except Exception as e:
logger.error(f"insert cookie -> {sql}")
def create_by_thread(slid, count):
"""
:param slid:
:param count:
:return:
"""
with ThreadPoolExecutor(max_workers=2) as t:
obj_list = []
for i in range(count * 2):
obj = t.submit(slid.run)
obj_list.append(obj)
insert_data(slide.sql_list)
logger.info(f"[sum] 并发任务 需要生成数量 {count}, 实际抓取数量 {count*2}, 实际生成数量 {len(slide.sql_list)}, 成功率 {len(slid.sql_list)/(count*2)}")
def create_by_for(slid, count):
"""
for循环执行
:param slid:
:param count:
:return:
"""
num = 100
i = 0
while num > i:
if len(slid.sql_list) >= count: # 超出目标数量结束
break
slid.run()
i += 1
insert_data(slide.sql_list)
logger.info(f"[sum] 循环任务 需要生成数量 {count}, 实际抓取数量 {i}, 实际生成数量 {len(slide.sql_list)}, 成功率 {len(slide.sql_list)/i}")
if __name__ == '__main__':
slide = KSSlide(is_proxy=True)
log = MyLogger()
parser = argparse.ArgumentParser(description='pass kuaishou cookie slider')
parser.add_argument('-c', type=int, default=10, help="needed cookie count;default count=10;")
parser.add_argument('-m', type=str, default="0", help="method: {0:for, 1:thread}; default method=0;")
args = parser.parse_args()
method = {
"0": create_by_for,
"1": create_by_thread
}
args_count = args.c
args_method = args.m
method[args_method](slide, args_count) # 执行函数

16
main.py

@ -0,0 +1,16 @@
# This is a sample Python script.
# Press ⌃R to execute it or replace it with your code.
# Press Double ⇧ to search everywhere for classes, files, tool windows, actions, and settings.
def print_hi(name):
# Use a breakpoint in the code line below to debug your script.
print(f'Hi, {name}') # Press ⌘F8 to toggle the breakpoint.
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
print_hi('PyCharm')
# See PyCharm help at https://www.jetbrains.com/help/pycharm/

40
requirements.txt

@ -0,0 +1,40 @@
aiohttp==3.8.3
aiosignal==1.3.1
asgiref==3.8.1
async-timeout==4.0.3
attrs==23.2.0
blinker==1.7.0
bottle==0.12.25
certifi==2023.7.22
charset-normalizer==2.0.12
click==8.1.7
DBUtils==3.0.3
Flask==3.0.2
frozenlist==1.4.1
furl==2.1.3
idna==3.4
importlib-metadata==7.0.1
itsdangerous==2.1.2
Jinja2==3.1.3
kafka==1.3.5
kafka-python==2.0.2
loguru==0.7.2
MarkupSafe==2.1.5
multidict==6.0.5
numpy==1.24.4
opencv-python==4.5.5.64
orderedmultidict==1.0.1
Paste==3.7.1
Pillow==10.1.0
pycryptodome==3.19.0
PyExecJS==1.5.1
PyMySQL==1.1.0
requests==2.27.1
scipy==1.10.1
six==1.16.0
typing_extensions==4.12.1
urllib3==1.26.16
uWSGI==2.0.23
Werkzeug==3.0.1
yarl==1.9.4
zipp==3.17.0

266
server.py

@ -0,0 +1,266 @@
import time
import json
from dianping.mtgsig import mtgsig_run
from douyin.dy_abogus import run
from hnw.hnw_hook import build_headers
from flask import Flask, request
import traceback
from utils.Logger import MyLogger
from wx.video.video_decode import main
from xhs.shield import shield_run
from xhs.shield_aes import get_key
from xhs.xhs_captcha import SignalCaptcha
from xhs.xhs_param import get_xs, get_a1, get_comment
app = Flask(__name__)
log = MyLogger()
"""
nohup /root/anaconda3/envs/py3.8/bin/python server.py > /dev/null 2>&1 &
uwsgi -d --ini uwsgi.ini
uwsgi --stop
uwsgi --reload
"""
@app.route("/getData", methods=['GET'])
def get_review_list():
# shop_id = request.query.shopId
# page_num = request.query.pageNum
# page_num = int(page_num) * 14
# review_list = main(shop_id, str(page_num))
# ret = {"desc": "success", "result": review_list}
ret = ""
return json.dumps(ret, ensure_ascii=False)
@app.route("/hnw/get_header", methods=['GET'])
def get_hnw_headers():
ret = {"code": 200, "data": {}}
try:
headers = build_headers()
ret["data"] = headers
except Exception as e:
ret["code"] = 500
ret["data"] = f"{e}"
log.info(f"[hnw]: {ret}")
return json.dumps(ret, ensure_ascii=False)
@app.route("/xhs/get_header", methods=['POST'])
def get_xhs_headers():
"""
x-s参数接口
/a1/timeStamp
cookie信息如果不传则本地生成一个
:return:
"""
ret = {"code": 200, "data": {}}
try:
api_type = request.args.get("api")
a1 = request.args.get("a1") if request.args.get("a1") else get_a1("Mac OS")[0]
times = request.args.get("timStamp") if request.args.get("timStamp") else str(int(time.time() * 1000))
data = request.get_data()
params = json.dumps(json.loads(data), ensure_ascii=False, separators=(',', ':'))
res = get_xs(a1, api_type, params, times)
ret["data"] = res
except Exception as e:
log.error(e)
ret = {"code": 500, "data": str(e)}
log.info(f"[xhs]: {ret}")
return json.dumps(ret, ensure_ascii=False)
@app.route("/xhs/get_header_all", methods=['POST'])
def get_xhs_headers_all():
"""
x-sx-comment参数接口
/a1/timeStamp
:return:
"""
ret = {"code": 200, "data": {}}
try:
api_type = request.args.get("api")
a1 = request.args.get("a1")
times = str(int(time.time() * 1000))
data = request.get_data()
params = json.dumps(json.loads(data), ensure_ascii=False, separators=(',', ':'))
res = get_xs(a1, api_type, params, times)
res["x-s-common"] = get_comment(a1, res['x-s'], res['x-t'])
ret["data"] = res
except Exception as e:
log.error(e)
ret = {"code": 500, "data": str(e)}
log.info(f"[xhs]: {ret}")
return json.dumps(ret, ensure_ascii=False)
@app.route("/xhs/pass_captcha", methods=['POST'])
def verify_captcha():
"""
xhs验证码
461
:return:
"""
ret = {"code": 200, "data": {}}
try:
data = request.get_data()
data = json.loads(data)
log.info(data)
web_session = data["web_session"]
verify_uuid = data["verify_uuid"]
source = data["source"]
a1 = data["a1"]
webId = data["webId"]
is_proxy = data.get("is_proxy") if data.get("is_proxy") else False
A = SignalCaptcha(web_session, verify_uuid, a1, webId, source=source, is_proxy=is_proxy)
res = A.run()
ret["data"] = res
except Exception as e:
log.error(e)
ret = {"code": 500, "data": str(e)}
log.info(f"[xhs]: {ret}")
return json.dumps(ret, ensure_ascii=False)
@app.route("/xhs/get_cookie", methods=['GET'])
def get_xhs_cookies():
"""
platform cookie
Android/iOS/Mac OS/Linux/Windows
:return:
"""
ret = {"code": 200, "data": {}}
try:
platform = request.args.get("platform") if request.args.get("platform") else "Mac OS"
a1, web_id = get_a1(platform)
ret["data"] = {
"a1": a1,
"webId": web_id
}
except Exception as e:
ret["code"] = 500
ret["data"] = f"{e}"
log.info(f"[xhs]: {ret}")
return json.dumps(ret, ensure_ascii=False)
@app.route("/wx/decode", methods=["POST"])
async def get_upload_path():
"""
:return:
"""
if 'file' not in request.files:
return json.dumps({"code": 500, "msg": "视频流文件不存在"}, ensure_ascii=False)
file = request.files["file"]
decode = request.args.get("decodekey")
log.info(f"[wx]: 获取的decode {decode}")
upload_path = await main(file, decode)
log.info(f"[wx]: {upload_path}")
return json.dumps(upload_path, ensure_ascii=False)
@app.route("/wx/decode", methods=["GET"])
async def get_upload_path1():
"""
:return:
"""
decode = request.args.get("decodekey")
localpath = request.args.get("localpath")
try:
files = {'file': open(localpath, 'rb')}
except Exception as e:
log.error(f"[wx] error {e}")
return json.dumps({"code":500, "msg": "视频读取失败"}, ensure_ascii=False)
log.info(f"[wx]: 获取的decode {decode}")
upload_path = await main(files["file"], decode)
log.info(f"[wx]: {upload_path}")
return json.dumps(upload_path, ensure_ascii=False)
@app.route("/dy/get_abogus", methods=["POST"])
async def get_abogus():
"""
:return:
"""
try:
data = json.loads(request.get_data())
ua = data.get("ua")
param = data.get("param")
a_bogus = run(param, ua)
res = {"code": 200, "data": a_bogus}
log.info(f"[dy]: {len(a_bogus)} => {res}")
return json.dumps(res, ensure_ascii=False)
except Exception as e:
log.error(f"[dy] error {e}")
return json.dumps({"code":500, "msg": "算法生成失败"}, ensure_ascii=False)
@app.route("/dp/get_mtgsig", methods=["POST"])
async def get_mtgsig():
"""
mtgsig1.2
:return:
"""
try:
data = json.loads(request.get_data())
url = data.get("url")
a3 = data.get("a3")
b8 = data.get("b8") # 循环的次数
a6 = data.get("a6")
if not b8 or not a6 or not a3:
raise Exception("check b8/a6/a3 params!!!")
mtgsig = mtgsig_run(url, b8, a3, a6)
res = {"code": 200, "data": mtgsig}
log.info(f"[mtgsig]: => {res}")
return json.dumps(res, ensure_ascii=False)
except Exception as e:
log.error(f"[mtgsig] error {e}")
return json.dumps({"code":500, "msg": "算法生成失败"}, ensure_ascii=False)
@app.route("/xhs/get_shield", methods=['POST'])
async def get_app_xhs_headers():
"""
app端
shield本地生成
:return:
"""
ret = {"code": 200, "data": {}}
try:
data = request.get_data()
data_json = json.loads(data)
hmac_main = data_json["hmac_main"]
url = data_json["url"]
xy_common_params = data_json["xy_common_params"]
deviceId = data_json["deviceId"]
api = data_json["api"] # /api/sns/v1/note/feed 默认
keys = get_key(deviceId, hmac_main) # 获得keys
res = shield_run(url, keys, xy_common_params, deviceId, api) # 加密流程
ret["data"] = res
except Exception as e:
log.error(e)
traceback.print_exc()
ret = {"code": 500, "data": str(e)}
log.info(f"[xhs]: {ret}")
return json.dumps(ret, ensure_ascii=False)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8088)

205
utils/ImageHelper.py

@ -0,0 +1,205 @@
import io
import random
import time
import cv2 as cv
import numpy as np
from PIL import Image
from scipy import signal
# format_list = [0, 10, 16, 6, 13, 3, 9, 15, 11, 19, 14, 18, 4, 12, 2, 1, 8, 17, 7, 5]
# this.canvasCtx.drawImage(this.img,
# 30 * i, 开始剪切的 x 坐标位置。
# 0, 开始剪切的 y 坐标位置。
# 30, 被剪切图像的宽度。
# 400, 被剪切图像的高度。
# 30 * keylist[i] / 1.5, 在画布上放置图像的 x 坐标位置
# 0, 在画布上放置图像的 y 坐标位置。
# offset / 1.5, 要使用的图像的宽度。(伸展或缩小图像)
# 200) 要使用的图像的高度。(伸展或缩小图像)
def format_slide_img(raw_img: bytes, format_list: list) -> bytes:
fp = io.BytesIO(raw_img)
img = Image.open(fp)
image_dict = {}
offset = 30
for i in range(len(format_list)):
box = (i * offset, 0, offset + (i * offset), 400) # 左(起始),上(不变),右(宽),下(不变)
image_dict[format_list[i]] = img.crop(box)
image_list = []
for i in sorted(image_dict):
image_list.append(image_dict[i])
image_num = len(image_list)
image_size = image_list[0].size
height = image_size[1]
width = image_size[0]
new_img = Image.new('RGB', (image_num * width, height), 255)
x = y = 0
for img in image_list:
new_img.paste(img, (x, y))
x += width
box = (0, 0, 600, 400)
new_img = new_img.crop(box)
# 保存图片
processClickImgIoFlow = io.BytesIO()
new_img.save(processClickImgIoFlow, format="JPEG")
return processClickImgIoFlow.getvalue()
# with open("test.jpg", "wb") as f:
# f.write(processClickImgIoFlow.getvalue())
# 1
def discern_gap(gapImage: bytes, sliderImage: bytes, show=False):
def edge_detection(rawimg):
def tracebar(x):
threshold1 = cv.getTrackbarPos('threshold1', 'Test')
threshold2 = cv.getTrackbarPos('threshold2', 'Test')
edged_img = cv.Canny(img_Gaussian, threshold1, threshold2)
cv.imshow("edged_img", edged_img)
image = np.asarray(bytearray(rawimg), dtype="uint8")
img = cv.imdecode(image, cv.IMREAD_COLOR)
grep_img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 高斯滤波 高斯滤波是通过对输入数组的每个点与输入的高斯滤波模板执行卷积计算然后将这些结果一块组成了滤波后的输出数组,
# 通俗的讲就是高斯滤波是对整幅图像进行加权平均的过程,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到。
# 高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。
img_Gaussian = cv.GaussianBlur(grep_img, (5, 5), 0)
# 用于对图像的边缘检测
edged_img = cv.Canny(img_Gaussian, 25, 45)
if show:
cv.namedWindow("Test")
# cv.imshow('raw_img', img)
# cv.imshow('grep_img', grep_img)
cv.imshow('img_Gaussian', img_Gaussian)
cv.createTrackbar("threshold1", "Test", 0, 255, tracebar)
cv.createTrackbar("threshold2", "Test", 0, 255, tracebar)
cv.imshow('edged_img', edged_img)
cv.waitKey(3000)
cv.destroyAllWindows()
return edged_img
def similarity_calculation(background, slider):
result = cv.matchTemplate(background, slider, cv.TM_CCOEFF_NORMED)
# 获取一个/组int类型的索引值在一个多维数组中的位置。
# x, y = np.unravel_index(result.argmax(), result.shape)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result)
return max_loc
"""计算滑动距离方法"""
gap = edge_detection(gapImage)
slider = edge_detection(sliderImage)
x, y = similarity_calculation(gap, slider)
# print('需要滑动距离', x, y)
# todo 返回的距离
return x
def discern_gap2(gap_path, slider_path, save=False):
def pic2grep(pic_path, type) -> np.ndarray:
pic_path_rgb = cv.imread(pic_path)
pic_path_gray = cv.cvtColor(pic_path_rgb, cv.COLOR_BGR2GRAY)
if save:
cv.imwrite(f"./{type}.jpg", pic_path_gray)
return pic_path_gray
def canny_edge(image_array: np.ndarray, show=False) -> np.ndarray:
can = cv.Canny(image_array, threshold1=200, threshold2=300)
if show:
cv.imshow('candy', can)
cv.waitKey()
cv.destroyAllWindows()
return can
def clear_white(img: str, show=False) -> np.ndarray:
img = cv.imread(img)
rows, cols, channel = img.shape
min_x = 255
min_y = 255
max_x = 0
max_y = 0
for x in range(1, rows):
for y in range(1, cols):
t = set(img[x, y])
if len(t) >= 2:
if x <= min_x:
min_x = x
elif x >= max_x:
max_x = x
if y <= min_y:
min_y = y
elif y >= max_y:
max_y = y
img1 = img[min_x:max_x, min_y:max_y]
if show:
cv.imshow('img1', img1)
cv.waitKey()
cv.destroyAllWindows()
return img1
def convolve2d(bg_array: np.ndarray, fillter: np.ndarray) -> np.ndarray:
bg_h, bg_w = bg_array.shape[:2]
fillter = fillter[::-1,::-1]
fillter_h, fillter_w = fillter.shape[:2]
c_full = signal.convolve2d(bg_array, fillter, mode="full")
kr, kc = fillter_h // 2, fillter_w // 2
c_same = c_full[
fillter_h - kr - 1: bg_h + fillter_h - kr - 1,
fillter_w - kc - 1: bg_w + fillter_w - kc - 1,
]
return c_same
def find_max_point(arrays: np.ndarray, search_on_horizontal_center=False) -> tuple:
max_point = 0
max_point_pos = None
array_rows, array_cols = arrays.shape
if search_on_horizontal_center:
for col in range(array_cols):
if arrays[array_rows // 2, col] > max_point:
max_point = arrays[array_rows // 2, col]
max_point_pos = col, array_rows // 2
else:
for row in range(array_rows):
for col in range(array_cols):
if arrays[row, col] > max_point:
max_point = arrays[row, col]
max_point_pos = col, row
return max_point_pos
gap_grep = pic2grep(gap_path, "gap")
gap_can = canny_edge(gap_grep, False)
clear_slider = cv.imread(slider_path) # clear_white(slider_path, False)
slider_can = canny_edge(clear_slider, False)
convolve2d_result = convolve2d(gap_can, slider_can)
result = find_max_point(convolve2d_result, True)
print(result)
def recognize_gap(bg, fg, is_show=False):
if isinstance(bg, str):
with open(fg, "rb") as f:
sliderImage = f.read()
# print(sliderImage)
with open(bg, "rb") as f:
gapImage = f.read()
else:
sliderImage = fg
gapImage = bg
res = discern_gap(gapImage,sliderImage, is_show)
return res
if __name__ == '__main__':
with open("../kuaishou/tmp/img.png", "rb") as f:
sliderImage = f.read()
# print(sliderImage)
with open("../kuaishou/tmp/img_1.png", "rb") as f:
gapImage = f.read()
res = discern_gap(gapImage,sliderImage, True)
print(res * 56/122)
# print(random.randint(100, 200))
# 256

112
utils/Logger.py

@ -0,0 +1,112 @@
import os
from functools import wraps
from time import perf_counter
from loguru import logger
# from loguru._logger import Logger
class MyLogger:
"""
"""
def __init__(self, log_dir='logs', max_size=20, retention='7 days'):
self.log_dir = log_dir
self.max_size = max_size
self.retention = retention
self.logger = self.configure_logger()
def configure_logger(self):
"""
Returns:
"""
# 创建日志目录
os.makedirs(self.log_dir, exist_ok=True)
shared_config = {
"level": "DEBUG",
"enqueue": True,
"backtrace": True,
"format": "{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
}
# 添加按照日期和大小切割的文件 handler
logger.add(
sink=f"{self.log_dir}/{{time:YYYY-MM-DD}}.log",
rotation=f"{self.max_size} MB",
retention=self.retention,
**shared_config
)
# 配置按照等级划分的文件 handler 和控制台输出
logger.add(sink=self.get_log_path, **shared_config)
return logger
def get_log_path(self, message: str) -> str:
"""
Args:
message:
Returns:
"""
log_level = message.record["level"].name.lower()
log_file = f"{log_level}.log"
log_path = os.path.join(self.log_dir, log_file)
return log_path
def __getattr__(self, level: str):
return getattr(self.logger, level)
def log_decorator(self, msg="快看,异常了,别唧唧哇哇,快排查"):
"""
Args:
logger:
Returns:
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
self.logger.info(f'-----------分割线-----------')
self.logger.info(f'调用 {func.__name__} args: {args}; kwargs:{kwargs}')
start = perf_counter() # 开始时间
try:
result = func(*args, **kwargs)
end = perf_counter() # 结束时间
duration = end - start
self.logger.info(f"{func.__name__} 返回结果:{result}, 耗时:{duration:4f}s")
return result
except Exception as e:
self.logger.exception(f"{func.__name__}: {msg}")
self.logger.info(f"-----------分割线-----------")
# raise e
return wrapper
return decorator
if __name__ == '__main__':
log = MyLogger()
for i in range(1000):
log.error('错误信息')
log.critical('严重错误信息')
log.debug('调试信息')
log.info('普通信息')
log.success('成功信息')
log.warning('警告信息')

234
utils/MysqlData.py

@ -0,0 +1,234 @@
import threading
import pymysql
from pymysql.cursors import DictCursor
from dbutils.pooled_db import PooledDB
class CRAWLER_DB_CONF(object):
"""
"""
DBHOST = '172.18.1.181'
DBPORT = 3306
DBUSER = 'crawl'
DBPWD = 'crawl'
DBNAME = 'dianpinggfy'
DBCHAR = 'utf8'
DB_FULL_NAME = "dianpinggfy"
class CRAWLER_DB_CONF_KS(object):
"""
"""
DBHOST = '172.18.1.134'
DBPORT = 3306
DBUSER = 'crawl666'
DBPWD = 'lx2a4jN1xFT96kj20LU='
DBNAME = 'KS_storage'
DBCHAR = 'utf8'
DB_FULL_NAME = "KS_storage"
class CRAWLER_DB_CONF_DY(object):
"""
"""
DBHOST = '172.18.1.181'
DBPORT = 3306
DBUSER = 'crawl'
DBPWD = 'crawl'
DBNAME = 'test'
DBCHAR = 'utf8'
DB_FULL_NAME = "test"
class MysqlPoolClient(object):
"""
MYSQL数据库对象 , conn = Mysql.getConn()
;conn.close()del conn
"""
# 连接池对象
__pool = {}
__lock = threading.Lock()
# TODO(YaoPeng): 反复加锁影响性能,但是爬虫场景下,可以暂时容忍
def __init__(self, db_conf):
MysqlPoolClient.__lock.acquire()
# 数据库构造函数,从连接池中取出连接,并生成操作游标
# pip install DBUtils
self._conn = MysqlPoolClient.__getConn(db_conf)
self._cursor = self._conn.cursor()
MysqlPoolClient.__lock.release()
def __del__(self):
self.dispose()
@staticmethod
def __getConn(db_conf):
pool_name = db_conf.DB_FULL_NAME
"""
@summary:
@return MySQLdb.connection
"""
if pool_name not in MysqlPoolClient.__pool:
MysqlPoolClient.__pool[pool_name] = PooledDB(creator=pymysql,
mincached=1,
maxcached=20,
host=db_conf.DBHOST,
port=db_conf.DBPORT,
user=db_conf.DBUSER,
passwd=db_conf.DBPWD,
db=db_conf.DBNAME,
use_unicode=True,
charset=db_conf.DBCHAR,
cursorclass=DictCursor)
return MysqlPoolClient.__pool[pool_name].connection()
def getAll(self, sql, param=None):
"""
@summary:
@param sql:使[param]
@param param: /
@return: result list()/boolean
"""
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
if count > 0:
query_result = self._cursor.fetchall()
else:
query_result = False
return query_result
def getOne(self, sql, param=None):
"""
@summary:
@param sql:使[param]
@param param: /
@return: result list/boolean
"""
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
if count > 0:
query_result = self._cursor.fetchone()
else:
query_result = False
return count,query_result
def getMany(self, sql, num, param=None):
"""
@summary: num条结果
@param sql:使[param]
@param num:
@param param: /
@return: result list/boolean
"""
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
if count > 0:
query_result = self._cursor.fetchmany(num)
else:
query_result = False
return query_result
def insertOne(self, sql, value=None):
"""
@summary:
@param sql:
@param value:tuple/list
@return: insertId
"""
self._cursor.execute(sql, value)
return self.__getInsertId()
def insertMany(self, sql, values):
"""
@summary:
@param sql:
@param values:tuple(tuple)/list[list]
@return: count
"""
count = self._cursor.executemany(sql, values)
return count
def updateMany(self, sql, values):
"""
@summary:
@param sql:
@param values:tuple(tuple)/list[list]
@return: count
"""
count = self._cursor.executemany(sql, values)
return count
def __getInsertId(self):
"""
id,
"""
self._cursor.execute("SELECT @@IDENTITY AS id")
result = self._cursor.fetchall()
return result[0]['id']
def __query(self, sql, param=None, commit=True):
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
if commit:
self._conn.commit()
return count
def update(self, sql, param=None):
"""
@summary:
@param sql: 使(%s,%s)
@param param: tuple/list
@return: count
"""
return self.__query(sql, param)
def delete(self, sql, param=None):
"""
@summary:
@param sql: 使(%s,%s)
@param param: tuple/list
@return: count
"""
return self.__query(sql, param)
def begin(self):
"""
@summary:
"""
self._conn.autocommit(0)
def end(self, option='commit'):
"""
@summary:
"""
if option == 'commit':
self._conn.commit()
else:
self._conn.rollback()
def dispose(self, is_end=1):
"""
@summary:
"""
MysqlPoolClient.__lock.acquire()
if is_end == 1:
self.end('commit')
else:
self.end('rollback')
self._cursor.close()
self._conn.close()
MysqlPoolClient.__lock.release()

0
utils/__init__.py

123
utils/rotate_captcha.py

@ -0,0 +1,123 @@
#!/usr/bin/env/ python
# -*- coding: utf-8 -*-
"""
@Time : 2023/2/12 11:20
@Author :
@File : rotate_captcha.py
@Software: PyCharm
"""
import cv2
import math
import numpy as np
from loguru import logger
import time
from utils.tool import cost_time
def circle_point_px(img, accuracy_angle, r=None):
rows, cols, _ = img.shape
assert 360 % accuracy_angle == 0
x0, y0 = r0, _ = (rows // 2, cols // 2)
if r: r0 = r
angles = np.arange(0, 360, accuracy_angle)
cos_angles = np.cos(np.deg2rad(angles))
sin_angles = np.sin(np.deg2rad(angles))
x = x0 + r0 * cos_angles
y = y0 + r0 * sin_angles
x = np.round(x).astype(int)
y = np.round(y).astype(int)
circle_px_list = img[x, y]
return circle_px_list
def rotate(image, angle, center=None, scale=1.0): # 1
(h, w) = image.shape[:2] # 2
if center is None: # 3
center = (w // 2, h // 2) # 4
M = cv2.getRotationMatrix2D(center, angle, scale) # 5
rotated = cv2.warpAffine(image, M, (w, h)) # 6
return rotated
def HSVDistance(c1, c2):
"""
HSVHue, Saturation, Value
:param c1:
:param c2:
:return:
"""
y1 = 0.299 * c1[0] + 0.587 * c1[1] + 0.114 * c1[2]
u1 = -0.14713 * c1[0] - 0.28886 * c1[1] + 0.436 * c1[2]
v1 = 0.615 * c1[0] - 0.51498 * c1[1] - 0.10001 * c1[2]
y2 = 0.299 * c2[0] + 0.587 * c2[1] + 0.114 * c2[2]
u2 = -0.14713 * c2[0] - 0.28886 * c2[1] + 0.436 * c2[2]
v2 = 0.615 * c2[0] - 0.51498 * c2[1] - 0.10001 * c2[2]
rlt = math.sqrt((y1 - y2) * (y1 - y2) + (u1 - u2) * (u1 - u2) + (v1 - v2) * (v1 - v2))
return rlt
def crop_to_square(image):
height, width = image.shape[:2]
size = min(height, width)
start_y = (height - size) // 2
start_x = (width - size) // 2
cropped = image[start_y:start_y+size, start_x:start_x+size]
return cropped
@cost_time
def single_discern(inner_image_brg:bytes, outer_image_brg:bytes):
inner_image = cv2.cvtColor(inner_image_brg, cv2.COLOR_BGR2HSV) # 颜色转换
outer_image = cv2.cvtColor(outer_image_brg, cv2.COLOR_BGR2HSV)
outer_image = crop_to_square(outer_image)
all_deviation = []
for result in range(0, 360):
inner = rotate(inner_image, -result) # 顺时针
outer = rotate(outer_image, 0)
inner_circle_point_px = circle_point_px(inner, 1, 95)
outer_circle_point_px = circle_point_px(outer, 1, 105)
total_deviation = 0
for i in range(len(inner_circle_point_px)):
in_px = inner_circle_point_px[i]
out_px = outer_circle_point_px[i]
deviation = HSVDistance(in_px, out_px)
total_deviation += deviation
all_deviation.append(total_deviation)
result = all_deviation.index(min(all_deviation))
return result
def export_single_discern(fg_path, bg_path):
"""
content处理
:param fg_path:
:param bg_path:
:return:
"""
if isinstance(fg_path, str):
with open(fg_path, "rb") as f:
inner_image_brg = f.read()
with open(bg_path, "rb") as f:
outer_image_brg = f.read()
else:
inner_image_brg = fg_path
outer_image_brg = bg_path
image = np.asarray(bytearray(inner_image_brg), dtype="uint8")
inner_image_brg = cv2.imdecode(image, cv2.IMREAD_COLOR)
image = np.asarray(bytearray(outer_image_brg), dtype="uint8")
outer_image_brg = cv2.imdecode(image, cv2.IMREAD_COLOR)
result = single_discern(inner_image_brg, outer_image_brg)
return result
if __name__ == '__main__':
export_single_discern('./tmp/fg_2.png', './tmp/bg_2.png') # 310

BIN
utils/tmp/bg_2.png

After

Width: 600  |  Height: 400  |  Size: 290 KiB

BIN
utils/tmp/fg_2.png

After

Width: 400  |  Height: 400  |  Size: 82 KiB

143
utils/tool.py

@ -0,0 +1,143 @@
import json
import random
import aiohttp
import requests
from aiohttp import FormData
from loguru import logger
import functools, time
def retry(exceptions: (BaseException, tuple, list)=BaseException,
max_retries: int = 2,
delay: int = 1,
sleep=time.sleep,
if_result=None):
"""
:param exceptions:
:param max_retries:
:param delay:
:param sleep: time.sleep
:param if_result:
:return:
"""
if not isinstance(exceptions, (tuple, list)):
new_exceptions = [exceptions]
else:
new_exceptions = exceptions
def init_retry(func, count, *args, **kwargs):
if count > max_retries:
return
try:
if if_result:
# print(f"重拾次数 is {count}")
return call(func, if_result, *args, **kwargs)
return func(*args, **kwargs)
except tuple(new_exceptions) as e:
# print("重试")
# traceback.print_exc()
if count < max_retries:
sleep(delay)
return init_retry(func, count+1, *args, **kwargs)
else:
raise e
def call(func, if_result, *args, **kwargs):
val = func(*args, **kwargs)
if if_result(*val):
return val
raise Exception("The result is not as expected!")
def decorator(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
return init_retry(func, 0, *args, **kwargs)
return wrapped
return decorator
def cost_time(func):
"""
:param func:
:return:
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
logger.info(f"{func.__name__} 请求耗时:{time.time() - start_time}")
return result
return wrapper
def retry_if_result_none(status, content):
if status == 200:
return True
return False
@retry(max_retries=3, delay=1, if_result=retry_if_result_none)
def download_q(url, headers, cookies, data=None, is_proxy=False, timeout=10):
"""
:param url:
:param headers:
:param cookies:
:param data:
:param is_proxy:
:param timeout:
:return:
"""
proxy = "16ABDDQU:153219@u1590.20.tp.16yun.cn:6447"
proxy = f"172.24.12.23:{random.randint(45001, 45100)}" # 修改代理
proxies = {'http': proxy, 'https': proxy} # 代理初始化
try:
if data:
if is_proxy:
response = requests.post(url, headers=headers, data=data, proxies=proxies, cookies=cookies, timeout=timeout)
else:
response = requests.post(url, headers=headers, data=data, cookies=cookies, timeout=timeout)
else:
if is_proxy:
response = requests.get(url, headers=headers, proxies=proxies, cookies=cookies, timeout=timeout)
else:
response = requests.get(url, headers=headers, cookies=cookies, timeout=timeout)
status = response.status_code
except Exception as e:
response = None
status = 0
logger.error(f"download {e}")
# print(response)
return status, response
async def upload_file(file_path, content, url="http://172.18.1.113:8080/upload"):
"""
:param file_path:
:param url:
:return:
"""
if file_path:
# timeout = aiohttp.ClientTimeout(total=30)
async with aiohttp.ClientSession() as session:
data = FormData()
data.add_field("file", content, filename=file_path, content_type='multipart/form-data;charset=utf-8"')
data.add_field("output", "json")
async with session.post(url, data=data) as response:
result = await response.text() # 返回结果为json字符串
result = json.loads(result)
logger.info(f"upload file {result}")
if 'url' in result.keys():
video_path = result['url']
return video_path
else:
raise Exception
else:
raise Exception

11
uwsgi.ini

@ -0,0 +1,11 @@
[uwsgi]
chdir=/data1/api-py
http=0.0.0.0:8088
stats=%(chdir)/uwsgi/uwsgi.status
pidfile=%(chdir)/uwsgi/uwsgi.pid
wsgi-file=%(chdir)/server.py
daemonize=%(chdir)/uwsgi/uwsgi.log
callable=app
processes=2
threads=2
buffer-size=65536

0
wx/__init__.py

0
wx/video/__init__.py

59
wx/video/video_decode.py

@ -0,0 +1,59 @@
import requests, os
from utils.tool import upload_file
import traceback
def decode(content, decodekey):
"""
:param content:
:param decodekey:
:return:
"""
url = "http://127.0.0.1:3000/wx/decode1"
files = {'file': content}
param = {
"decodekey": decodekey
}
response = requests.post(url, files=files, params=param)
return response.content
def save_video(content, filename):
"""
tmp下
:param content:
:param filename:
:return:
"""
tmp_file = f"{os.getcwd()}/wx/tmp/{filename}"
with open(tmp_file, 'wb') as f:
f.write(content)
f.close()
return filename
async def main(content, decodekey):
"""
-
-
:param content:
:param decodekey:
:return:
"""
file_name = f"{decodekey}.mp4"
res = {"code": 200, "msg": "上传成功", "filePath": ""}
try:
content = decode(content, decodekey)
upload_file_name = await upload_file(file_name, content)
except Exception as e: # 失败则保存本地
traceback.print_exc()
upload_file_name = save_video(content, file_name)
res["code"] = 203
res["msg"] = "上传失败,保存到本地"
res["filePath"] = upload_file_name
return res

0
xhs/__init__.py

132
xhs/shield.py

@ -0,0 +1,132 @@
import base64
from xhs.shield_aes import get_key
from xhs.shield_md5 import md5sum
from xhs.shield_rc4 import RC4
def hex_string(num, step=2):
"""
step
"""
tmp_string = hex(num)[2:]
if len(tmp_string) < step:
tmp_string = '0' + tmp_string
return tmp_string
def expand_keys(keys, m):
md5_str1_list = [hex_string(int(keys[i] + keys[i + 1], 16) ^ m) for i in range(0, len(keys), 2)]
md5_str1 = "".join(md5_str1_list)
return md5_str1
def char2hex(char):
return hex_string(ord(char))
def get_md5(keys, params):
"""
hmac
:param keys:
:param params:
:return:
"""
# 拓展key1 0x36
md5_str1 = expand_keys(keys, 54)
# 拓展key2 0x5c
md5_str2 = expand_keys(keys, 92)
data = bytes.fromhex(md5_str1) + params.encode("utf-8")
md1 = md5sum(data)
data1 = bytes.fromhex(md5_str2 + md1)
md2 = md5sum(data1) # 获得最终结果
return md2
def get_rc4(params):
"""
rc4运算
:param params:
:return:
"""
key = "std::abort();"
result = RC4(key.encode("utf-8"), bytes.fromhex(params))
return result.hex()
def get_shield(keys, params, deviceId):
"""
shield生成核心部分
app_id
:param keys:
:param params:
:param deviceId:
:return:
"""
version = "6970181"
app_id = "ecfaaf01"
p7 = "".join([char2hex(m) for m in version])
p8 = "".join([char2hex(m) for m in deviceId])
p9 = get_md5(keys, params) # 魔改md5 16位md5
rc4_plaintext = f"00000001{app_id}00000002000000{hex_string(len(version))}000000{hex_string(len(deviceId))}000000{hex_string(len(p9) // 2)}{p7}{p8}{p9}"
result = get_rc4(rc4_plaintext)
_tmp = len(version) + len(deviceId) + len(p9) // 2 + 24
tmp = f"0000000100000001000000{hex_string(_tmp)}000000{hex_string(_tmp)}" # 固定 0x53是上述 几个固定的值算出来的
_shield = "XY" + base64.b64encode(bytes.fromhex(tmp + result)).decode()
return _shield
def shield(node_id, xy_common_params, xy_platform_info):
"""
"""
# 设备一
deviceId = "2fe75062-a528-3340-bed3-220a67f7f240"
keys = "aa82da57410dddd5b2860e534f7c0602f589c20ec8e8830baa239360c89cce62bdc304d8a1aa988d620917dbefc2a1154692fad24294f4419ea19c7dc069897b"
api = "/api/sns/v1/note/feed"
param = f"note_id={node_id}&page=1&num=5&fetch_mode=1&source=&ads_track_id="
plaintext = api + param + xy_common_params + xy_platform_info
return get_shield(keys, plaintext, deviceId)
def shield_run(url, keys, xy_common_params, deviceId, api="/api/sns/v1/note/feed"):
"""
:param url:
:param keys: hmac_main
:param xy_common_params: header中字段
:param deviceId:
:param api: feed流接口
:return:
"""
param = url[url.index("?") + 1:]
xy_platform_info = f"platform=android&build=6970181&deviceId={deviceId}"
plaintext = api + param + xy_common_params + xy_platform_info
# print(plaintext)
return get_shield(keys, plaintext, deviceId)
if __name__ == '__main__':
# 先获得 main_hmac 处理后的key
# XYAAAAAQAAAAEAAABTAAAAUzUWEe0xG1IbD9/c+qCLOlKGmTtFa+lG43AHe+FXTKxDxI2yn7IxH534qbVaz8N7icV+2KNmRAwcQDSAZrqn3SpjhOCLuaGTuDRgbpA0sNhU/xUP 结果
# XYAAAAAQAAAAEAAABTAAAAUzUWEe0xG1IbD9/c+qCLOlKGmTtFa+lG43AHe+FXTKxDxI2yn7IxH534qbVaz8N7icV+2KNmRAwcQDSAZrqn3SpjhOCLuaGTuDRgbpA0sNhU/xUP
# keys = "aa82da57410dddd5b2860e534f7c0602f589c20ec8e8830baa239360c89cce62bdc304d8a1aa988d620917dbefc2a1154692fad24294f4419ea19c7dc069897b"
# get_shield(keys)
url = "https://edith.xiaohongshu.com/api/sns/v1/note/feed?note_id=66ceeabe000000001d03b546&page=1&num=5&fetch_mode=1&source=&ads_track_id="
hmac = "NqLx0YFKNb4KraYq524SgzVpepYQ0SwhZLRs7eyxe6A26c/b1b+d6OU2LfAPwh8zpt3fkR/jsR5yzVzIqXe66EWhGJ8iWV36KKSIz0mVt436sTqt3eUYUZwb5TzpSYDa"
deviceId = "119214fc-0fe5-3ae8-91dd-baa821c11324"
xy_platform_info = "platform=android&build=6970181&deviceId=119214fc-0fe5-3ae8-91dd-baa821c11324"
xy_common_params = "fid=1721639154103483c0daaace2ca9266cba37ac9fe114&device_fingerprint=202407221758192809006e7e334e46628620f6768bcf3b0153b1977b9f6cd6&device_fingerprint1=202407221758192809006e7e334e46628620f6768bcf3b0153b1977b9f6cd6&cpu_name=Qualcomm+Technologies%2C+Inc+SM8150&device_model=phone&launch_id=1727578036&tz=Asia%2FShanghai&channel=CPA-3DSP-N3-ZSKJ&versionName=6.97.0.1&overseas_channel=0&deviceId=119214fc-0fe5-3ae8-91dd-baa821c11324&platform=android&sid=session.1721639201142076381131&identifier_flag=4&t=1727590998&project_id=ECFAAF&build=6970181&lang=zh-Hans&app_id=ECFAAF01&uis=dark&teenager=0"
keys = get_key(deviceId, hmac)
result = sheild_run(url, keys, xy_common_params, deviceId)
print(result)

200
xhs/shield_aes.py

@ -0,0 +1,200 @@
from functools import reduce
import base64
from xhs.shield_const import *
"""
hmac_main aes处理
aes
6.97.0.1
"""
class AES:
def __init__(self, key: bytes):
self.aes_type = len(key) * 8
self._key_r = self._generate_key(key)
def _sort_key(self, w_list):
"""
key
"""
def change_char(w):
w_4 = self._split_int(w) # 拆分成 4x8bit
s_w_4 = [SBox[w_4[0]], SBox[w_4[1]], SBox[w_4[2]], SBox[w_4[3]]]
s_s_w_4 = [dword_7F550[s_w_4[0]], dword_7F950[s_w_4[1]], dword_7FD50[s_w_4[2]], dword_80150[s_w_4[3]]]
key4 = reduce(lambda x, y: x ^ y, s_s_w_4)
return key4
# 交换位置
for i in range(len(w_list) // 2):
tmp = w_list[i]
if i > 3: # 需要交换位置
tmp = change_char(tmp)
w_list[len(w_list) - i - 1] = change_char(w_list[len(w_list) - i -1])
w_list[i] = w_list[len(w_list) - i -1]
w_list[len(w_list) - i -1] = tmp
# print(i, len(w_list) - i -1)
return w_list
@staticmethod
def hex_string(num, step=2):
"""
step
"""
tmp_string = hex(num)[2:]
if len(tmp_string) < step:
tmp_string = '0' * (step - len(tmp_string)) + tmp_string
return tmp_string
def encrypt(self, plaintext:bytes):
"""
:param plaintext:
:return:
"""
state = [[plaintext[i + j] for j in range(16)] for i in range(0, len(plaintext), 16)] # 先分组
result = []
# 进行加密流程
for p in range(len(state)):
_plaintext_block = [self._joint_int(state[p][i:i+4]) for i in range(0, 16, 4)]
state[p] = _plaintext_block.copy() # 合并为每四个字节的数组
_encrypt_block = self.encrypt_block(_plaintext_block) # 处理第一块
if p > 0:
extra_block = [self.hex_string(_encrypt_block[i] ^ state[p - 1][i], 8) for i in range(4)] # 异或处理
result.append("".join(extra_block))
return "".join(result[:4])
def encrypt_block(self, plaintext_block):
"""
"""
keys = self._key_r
# 第一轮由于明文和那个key是反的 单独计算
plaintext_block[0] = plaintext_block[0] ^ keys[3]
plaintext_block[1] = plaintext_block[1] ^ keys[2]
plaintext_block[2] = plaintext_block[2] ^ keys[1]
plaintext_block[3] = plaintext_block[3] ^ keys[0]
for i in range(0, 9):
num = (i + 1) * 4
a1 = self._split_int(plaintext_block[0]) # 这块是按顺序的
a2 = self._split_int(plaintext_block[1])
a3 = self._split_int(plaintext_block[2])
a4 = self._split_int(plaintext_block[3])
# 查表法进行替换
a1_1_box = [dword_7F550[a1[0]], dword_7F950[a1[1]], dword_7FD50[a1[2]], dword_80150[a1[3]]]
a2_1_box = [dword_7F550[a2[0]], dword_7F950[a2[1]], dword_7FD50[a2[2]], dword_80150[a2[3]]]
a3_1_box = [dword_7F550[a3[0]], dword_7F950[a3[1]], dword_7FD50[a3[2]], dword_80150[a3[3]]]
a4_1_box = [dword_7F550[a4[0]], dword_7F950[a4[1]], dword_7FD50[a4[2]], dword_80150[a4[3]]]
if i == 0:
# 矩阵运算 这块不太一样
d1 = [a4_1_box[0], a3_1_box[1], a2_1_box[2], a1_1_box[3]]
d2 = [a3_1_box[0], a2_1_box[1], a1_1_box[2], a4_1_box[3]]
d3 = [a2_1_box[0], a1_1_box[1], a4_1_box[2], a3_1_box[3]]
d4 = [a1_1_box[0], a4_1_box[1], a3_1_box[2], a2_1_box[3]]
else:
# 矩阵运算
d1 = [a1_1_box[0], a2_1_box[1], a3_1_box[2], a4_1_box[3]]
d2 = [a2_1_box[0], a3_1_box[1], a4_1_box[2], a1_1_box[3]]
d3 = [a3_1_box[0], a4_1_box[1], a1_1_box[2], a2_1_box[3]]
d4 = [a4_1_box[0], a1_1_box[1], a2_1_box[2], a3_1_box[3]]
# 拼接
plaintext_block[0] = reduce(lambda x, y: x ^ y, d1) ^ keys[num]
plaintext_block[1] = reduce(lambda x, y: x ^ y, d2) ^ keys[num + 1]
plaintext_block[2] = reduce(lambda x, y: x ^ y, d3) ^ keys[num + 2]
plaintext_block[3] = reduce(lambda x, y: x ^ y, d4) ^ keys[num + 3]
# 后四位
a1 = self._split_int(plaintext_block[0]) # 这块是按顺序的
a2 = self._split_int(plaintext_block[1])
a3 = self._split_int(plaintext_block[2])
a4 = self._split_int(plaintext_block[3])
a1_sum_box = [SBoxIV[a4[0]], SBoxIV[a1[1]], SBoxIV[a2[2]], SBoxIV[a3[3]]] # 合并后
a2_sum_box = [SBoxIV[a3[0]], SBoxIV[a4[1]], SBoxIV[a1[2]], SBoxIV[a2[3]]] # 合并后
a3_sum_box = [SBoxIV[a2[0]], SBoxIV[a3[1]], SBoxIV[a4[2]], SBoxIV[a1[3]]] # 合并后
a4_sum_box = [SBoxIV[a1[0]], SBoxIV[a2[1]], SBoxIV[a3[2]], SBoxIV[a4[3]]] # 合并后
plaintext_block[0] = self._joint_int(a1_sum_box) ^ keys[-1]
plaintext_block[1] = self._joint_int(a2_sum_box) ^ keys[-2]
plaintext_block[2] = self._joint_int(a3_sum_box) ^ keys[-3]
plaintext_block[3] = self._joint_int(a4_sum_box) ^ keys[-4]
return plaintext_block
def _generate_key(self, key: bytes) -> list:
"""密钥扩展"""
Rcon = [0x12310000, 0x2000100, 0x4020000, 0x8020200, 0x10102000, 0x30020400, 0x40002000, 0x80002000, 0x1B002000, 0x36200200] # 轮常数
Nr, Nk = 10 + (self.aes_type - 128) // 32, self.aes_type // 32 # Nr:轮数,Nk:密钥长度
w = [0 for _ in range(4 * (Nr + 1))] # 轮密钥
p = [4052295985, 4278194467, 4043314006, 4045621392] # 额外处理
for i in range(Nk): # 初始化是对的
w[i] = int.from_bytes(key[4 * i:4 * i + 4], 'big') ^ p[i]
# print(f"w{i} => {hex(w[i])}")
for i in range(Nk, 4 * (Nr + 1)):
temp = w[i - 1]
# print("******************************")
# print(f"开始", hex(temp), i % Nk)
if i % Nk == 0:
temp = self._split_int(temp) # 拆分成 4x8bit
temp = [SBox[temp[1]], SBox[temp[2]], SBox[temp[3]], SBox[temp[0]]] # sbox 的轮数发生变化了
temp = self._joint_int(temp) ^ Rcon[i // Nk - 1] # 合并回 32bit
elif Nk > 6 and i % Nk == 4:
temp = self._split_int(temp) # 拆分成 4x8bit
temp = [SBox[temp[0]], SBox[temp[1]],
SBox[temp[2]], SBox[temp[3]]]
temp = self._joint_int(temp) # 合并回 32bit
w[i] = w[i - Nk] ^ temp
w = self._sort_key(w) # 替换处理key
return w
@staticmethod
def _split_int(n: int) -> list:
"""拆分 32bit 成 4x8bit"""
return [(n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF]
@staticmethod
def _joint_int(b: list) -> int:
"""合并 4x8bit 成 32bit"""
return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]
def get_key(deviceId, hmac_main):
"""
:param deviceId: id
:param hmac_main: s.xml
:return:
"""
key = deviceId[:16]
plaintext = base64.b64decode(hmac_main.encode("utf-8"))
A = AES(key.encode("utf-8"))
result = A.encrypt(plaintext)
return result
if __name__ == '__main__':
key = "2fe75062-a528-33"
m = AES(key.encode("utf-8"))
hmac_main = "cDdMxUWZy3e2szBCUB04rZMxTdf6tVKcpCIRrDQGa/NS8Agki6U5MGN6c6QCT3t6amTAYBbcDwFlPndCV3AfaerPd36GS9sdmTeKzBU45YsIBsGAdBXyy2GnkRlDaVCO"
plaintext = base64.b64decode(hmac_main.encode("utf-8"))
pp = m.encrypt(plaintext)
print(get_key(key, hmac_main))

274
xhs/shield_const.py

@ -0,0 +1,274 @@
# AES 初始化化参数
# s盒和逆s盒是原始的 其他都是自定的数组
SBox = [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B,
0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,
0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26,
0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,
0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0,
0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED,
0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F,
0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC,
0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14,
0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,
0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,
0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F,
0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E,
0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11,
0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F,
0xB0, 0x54, 0xBB, 0x16]
# aes 轮密钥
Rcon = [
0x12310000, 0x2000100, 0x4020000, 0x8020200, 0x10102000, 0x30020400,
0x40002000, 0x80002000, 0x1B002000, 0x36200200
]
SBoxIV = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e,
0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32,
0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50,
0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05,
0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41,
0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8,
0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59,
0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d,
0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63,
0x55, 0x21, 0x0c, 0x7d]
dword_7F550 = [
0x51F4A750, 0x7E416553, 0x1A17A4C3, 0x3A275E96, 0x3BAB6BCB
,0x1F9D45F1, 0xACFA58AB, 0x4BE30393, 0x2030FA55, 0xAD766DF6
,0x88CC7691, 0xF5024C25, 0x4FE5D7FC, 0xC52ACBD7, 0x26354480
,0xB562A38F, 0xDEB15A49, 0x25BA1B67, 0x45EA0E98, 0x5DFEC0E1
,0xC32F7502, 0x814CF012, 0x8D4697A3, 0x6BD3F9C6, 0x38F5FE7
,0x15929C95, 0xBF6D7AEB, 0x955259DA, 0xD4BE832D, 0x587421D3
,0x49E06929, 0x8EC9C844, 0x75C2896A, 0xF48E7978, 0x99583E6B
,0x27B971DD, 0xBEE14FB6, 0xF088AD17, 0xC920AC66, 0x7DCE3AB4
,0x63DF4A18, 0xE51A3182, 0x97513360, 0x62537F45, 0xB16477E0
,0xBB6BAE84, 0xFE81A01C, 0xF9082B94, 0x70486858, 0x8F45FD19
,0x94DE6C87, 0x527BF8B7, 0xAB73D323, 0x724B02E2, 0xE31F8F57
,0x6655AB2A, 0xB2EB2807, 0x2FB5C203, 0x86C57B9A, 0xD33708A5
,0x302887F2, 0x23BFA5B2, 0x2036ABA, 0xED16825C, 0x8ACF1C2B
,0xA779B492, 0xF307F2F0, 0x4E69E2A1, 0x65DAF4CD, 0x605BED5
,0xD134621F, 0xC4A6FE8A, 0x342E539D, 0xA2F355A0, 0x58AE132
,0xA4F6EB75, 0xB83EC39, 0x4060EFAA, 0x5E719F06, 0xBD6E1051
,0x3E218AF9, 0x96DD063D, 0xDD3E05AE, 0x4DE6BD46, 0x91548DB5
,0x71C45D05, 0x406D46F, 0x605015FF, 0x1998FB24, 0xD6BDE997
,0x894043CC, 0x67D99E77, 0xB0E842BD, 0x7898B88, 0xE7195B38
,0x79C8EEDB, 0xA17C0A47, 0x7C420FE9, 0xF8841EC9, 0
,0x9808683, 0x322BED48, 0x1E1170AC, 0x6C5A724E, 0xFD0EFFFB
,0xF853856, 0x3DAED51E, 0x362D3927, 0xA0FD964, 0x685CA621
,0x9B5B54D1, 0x24362E3A, 0xC0A67B1, 0x9357E70F, 0xB4EE96D2
,0x1B9B919E, 0x80C0C54F, 0x61DC20A2, 0x5A774B69, 0x1C121A16
,0xE293BA0A, 0xC0A02AE5, 0x3C22E043, 0x121B171D, 0xE090D0B
,0xF28BC7AD, 0x2DB6A8B9, 0x141EA9C8, 0x57F11985, 0xAF75074C
,0xEE99DDBB, 0xA37F60FD, 0xF701269F, 0x5C72F5BC, 0x44663BC5
,0x5BFB7E34, 0x8B432976, 0xCB23C6DC, 0xB6EDFC68, 0xB8E4F163
,0xD731DCCA, 0x42638510, 0x13972240, 0x84C61120, 0x854A247D
,0xD2BB3DF8, 0xAEF93211, 0xC729A16D, 0x1D9E2F4B, 0xDCB230F3
,0xD8652EC, 0x77C1E3D0, 0x2BB3166C, 0xA970B999, 0x119448FA
,0x47E96422, 0xA8FC8CC4, 0xA0F03F1A, 0x567D2CD8, 0x223390EF
,0x87494EC7, 0xD938D1C1, 0x8CCAA2FE, 0x98D40B36, 0xA6F581CF
,0xA57ADE28, 0xDAB78E26, 0x3FADBFA4, 0x2C3A9DE4, 0x5078920D
,0x6A5FCC9B, 0x547E4662, 0xF68D13C2, 0x90D8B8E8, 0x2E39F75E
,0x82C3AFF5, 0x9F5D80BE, 0x69D0937C, 0x6FD52DA9, 0xCF2512B3
,0xC8AC993B, 0x10187DA7, 0xE89C636E, 0xDB3BBB7B, 0xCD267809
,0x6E5918F4, 0xEC9AB701, 0x834F9AA8, 0xE6956E65, 0xAAFFE67E
,0x21BCCF08, 0xEF15E8E6, 0xBAE79BD9, 0x4A6F36CE, 0xEA9F09D4
,0x29B07CD6, 0x31A4B2AF, 0x2A3F2331, 0xC6A59430, 0x35A266C0
,0x744EBC37, 0xFC82CAA6, 0xE090D0B0, 0x33A7D815, 0xF104984A
,0x41ECDAF7, 0x7FCD500E, 0x1791F62F, 0x764DD68D, 0x43EFB04D
,0xCCAA4D54, 0xE49604DF, 0x9ED1B5E3, 0x4C6A881B, 0xC12C1FB8
,0x4665517F, 0x9D5EEA04, 0x18C355D, 0xFA877473, 0xFB0B412E
,0xB3671D5A, 0x92DBD252, 0xE9105633, 0x6DD64713, 0x9AD7618C
, 0x37A10C7A, 0x59F8148E, 0xEB133C89, 0xCEA927EE, 0xB761C935
,0xE11CE5ED, 0x7A47B13C, 0x9CD2DF59, 0x55F2733F, 0x1814CE79
,0x73C737BF, 0x53F7CDEA, 0x5FFDAA5B, 0xDF3D6F14, 0x7844DB86
,0xCAAFF381, 0xB968C43E, 0x3824342C, 0xC2A3405F, 0x161DC372
,0xBCE2250C, 0x283C498B, 0xFF0D9541, 0x39A80171, 0x80CB3DE
,0xD8B4E49C, 0x6456C190, 0x7BCB8461, 0xD532B670, 0x486C5C74
, 0xD0B85742
]
dword_7F950 = [
0x5051F4A7, 0x537E4165, 0xC31A17A4, 0x963A275E, 0xCB3BAB6B
,0xF11F9D45, 0xABACFA58, 0x934BE303, 0x552030FA, 0xF6AD766D
,0x9188CC76, 0x25F5024C, 0xFC4FE5D7, 0xD7C52ACB, 0x80263544
,0x8FB562A3, 0x49DEB15A, 0x6725BA1B, 0x9845EA0E, 0xE15DFEC0
,0x2C32F75, 0x12814CF0, 0xA38D4697, 0xC66BD3F9, 0xE7038F5F
,0x9515929C, 0xEBBF6D7A, 0xDA955259, 0x2DD4BE83, 0xD3587421
,0x2949E069, 0x448EC9C8, 0x6A75C289, 0x78F48E79, 0x6B99583E
,0xDD27B971, 0xB6BEE14F, 0x17F088AD, 0x66C920AC, 0xB47DCE3A
,0x1863DF4A, 0x82E51A31, 0x60975133, 0x4562537F, 0xE0B16477
,0x84BB6BAE, 0x1CFE81A0, 0x94F9082B, 0x58704868, 0x198F45FD
,0x8794DE6C, 0xB7527BF8, 0x23AB73D3, 0xE2724B02, 0x57E31F8F
,0x2A6655AB, 0x7B2EB28, 0x32FB5C2, 0x9A86C57B, 0xA5D33708
,0xF2302887, 0xB223BFA5, 0xBA02036A, 0x5CED1682, 0x2B8ACF1C
,0x92A779B4, 0xF0F307F2, 0xA14E69E2, 0xCD65DAF4, 0xD50605BE
,0x1FD13462, 0x8AC4A6FE, 0x9D342E53, 0xA0A2F355, 0x32058AE1
,0x75A4F6EB, 0x390B83EC, 0xAA4060EF, 0x65E719F, 0x51BD6E10
,0xF93E218A, 0x3D96DD06, 0xAEDD3E05, 0x464DE6BD, 0xB591548D
, 0x571C45D, 0x6F0406D4, 0xFF605015, 0x241998FB, 0x97D6BDE9
,0xCC894043, 0x7767D99E, 0xBDB0E842, 0x8807898B, 0x38E7195B
,0xDB79C8EE, 0x47A17C0A, 0xE97C420F, 0xC9F8841E, 0
,0x83098086, 0x48322BED, 0xAC1E1170, 0x4E6C5A72, 0xFBFD0EFF
,0x560F8538, 0x1E3DAED5, 0x27362D39, 0x640A0FD9, 0x21685CA6
,0xD19B5B54, 0x3A24362E, 0xB10C0A67, 0xF9357E7, 0xD2B4EE96
,0x9E1B9B91, 0x4F80C0C5, 0xA261DC20, 0x695A774B, 0x161C121A
,0xAE293BA, 0xE5C0A02A, 0x433C22E0, 0x1D121B17, 0xB0E090D
,0xADF28BC7, 0xB92DB6A8, 0xC8141EA9, 0x8557F119, 0x4CAF7507
,0xBBEE99DD, 0xFDA37F60, 0x9FF70126, 0xBC5C72F5, 0xC544663B
,0x345BFB7E, 0x768B4329, 0xDCCB23C6, 0x68B6EDFC, 0x63B8E4F1
, 0xCAD731DC, 0x10426385, 0x40139722, 0x2084C611, 0x7D854A24
,0xF8D2BB3D, 0x11AEF932, 0x6DC729A1, 0x4B1D9E2F, 0xF3DCB230
,0xEC0D8652, 0xD077C1E3, 0x6C2BB316, 0x99A970B9, 0xFA119448
,0x2247E964, 0xC4A8FC8C, 0x1AA0F03F, 0xD8567D2C, 0xEF223390
,0xC787494E, 0xC1D938D1, 0xFE8CCAA2, 0x3698D40B, 0xCFA6F581
,0x28A57ADE, 0x26DAB78E, 0xA43FADBF, 0xE42C3A9D, 0xD507892
,0x9B6A5FCC, 0x62547E46, 0xC2F68D13, 0xE890D8B8, 0x5E2E39F7
,0xF582C3AF, 0xBE9F5D80, 0x7C69D093, 0xA96FD52D, 0xB3CF2512
,0x3BC8AC99, 0xA710187D, 0x6EE89C63, 0x7BDB3BBB, 0x9CD2678
,0xF46E5918, 0x1EC9AB7, 0xA8834F9A, 0x65E6956E, 0x7EAAFFE6
,0x821BCCF, 0xE6EF15E8, 0xD9BAE79B, 0xCE4A6F36, 0xD4EA9F09
,0xD629B07C, 0xAF31A4B2, 0x312A3F23, 0x30C6A594, 0xC035A266
,0x37744EBC, 0xA6FC82CA, 0xB0E090D0, 0x1533A7D8, 0x4AF10498
,0xF741ECDA, 0xE7FCD50, 0x2F1791F6, 0x8D764DD6, 0x4D43EFB0
,0x54CCAA4D, 0xDFE49604, 0xE39ED1B5, 0x1B4C6A88, 0xB8C12C1F
,0x7F466551, 0x49D5EEA, 0x5D018C35, 0x73FA8774, 0x2EFB0B41
,0x5AB3671D, 0x5292DBD2, 0x33E91056, 0x136DD647, 0x8C9AD761
,0x7A37A10C, 0x8E59F814, 0x89EB133C, 0xEECEA927, 0x35B761C9
,0xEDE11CE5, 0x3C7A47B1, 0x599CD2DF, 0x3F55F273, 0x791814CE
,0xBF73C737, 0xEA53F7CD, 0x5B5FFDAA, 0x14DF3D6F, 0x867844DB
,0x81CAAFF3, 0x3EB968C4, 0x2C382434, 0x5FC2A340, 0x72161DC3
,0xCBCE225, 0x8B283C49, 0x41FF0D95, 0x7139A801, 0xDE080CB3
,0x9CD8B4E4, 0x906456C1, 0x617BCB84, 0x70D532B6, 0x74486C5C
,0x42D0B857
]
# ??不知道 少一个
dword_7FD50 = [
0xA75051F4, 0x65537E41, 0xA4C31A17, 0x5E963A27, 0x6BCB3BAB
,0x45F11F9D, 0x58ABACFA, 0x3934BE3, 0xFA552030, 0x6DF6AD76
,0x769188CC, 0x4C25F502, 0xD7FC4FE5, 0xCBD7C52A, 0x44802635
,0xA38FB562, 0x5A49DEB1, 0x1B6725BA, 0xE9845EA, 0xC0E15DFE
,0x7502C32F, 0xF012814C, 0x97A38D46, 0xF9C66BD3, 0x5FE7038F
, 0x9C951592, 0x7AEBBF6D, 0x59DA9552, 0x832DD4BE, 0x21D35874
,0x692949E0, 0xC8448EC9, 0x896A75C2, 0x7978F48E, 0x3E6B9958
,0x71DD27B9, 0x4FB6BEE1, 0xAD17F088, 0xAC66C920, 0x3AB47DCE
,0x4A1863DF, 0x3182E51A, 0x33609751, 0x7F456253, 0x77E0B164
,0xAE84BB6B, 0xA01CFE81, 0x2B94F908, 0x68587048, 0xFD198F45
,0x6C8794DE, 0xF8B7527B, 0xD323AB73, 0x2E2724B, 0x8F57E31F
,0xAB2A6655, 0x2807B2EB, 0xC2032FB5, 0x7B9A86C5, 0x8A5D337
,0x87F23028, 0xA5B223BF, 0x6ABA0203, 0x825CED16, 0x1C2B8ACF
,0xB492A779, 0xF2F0F307, 0xE2A14E69, 0xF4CD65DA, 0xBED50605
,0x621FD134, 0xFE8AC4A6, 0x539D342E, 0x55A0A2F3, 0xE132058A
,0xEB75A4F6, 0xEC390B83, 0xEFAA4060, 0x9F065E71, 0x1051BD6E
,0x8AF93E21, 0x63D96DD, 0x5AEDD3E, 0xBD464DE6, 0x8DB59154
,0x5D0571C4, 0xD46F0406, 0x15FF6050, 0xFB241998, 0xE997D6BD
,0x43CC8940, 0x9E7767D9, 0x42BDB0E8, 0x8B880789, 0x5B38E719
,0xEEDB79C8, 0xA47A17C, 0xFE97C42, 0x1EC9F884, 0, 0x86830980
,0xED48322B, 0x70AC1E11, 0x724E6C5A, 0xFFFBFD0E, 0x38560F85
,0xD51E3DAE, 0x3927362D, 0xD9640A0F, 0xA621685C, 0x54D19B5B
,0x2E3A2436, 0x67B10C0A, 0xE70F9357, 0x96D2B4EE, 0x919E1B9B
,0xC54F80C0, 0x20A261DC, 0x4B695A77, 0x1A161C12, 0xBA0AE293
,0x2AE5C0A0, 0xE0433C22, 0x171D121B, 0xD0B0E09, 0xC7ADF28B
,0xA8B92DB6, 0xA9C8141E, 0x198557F1, 0x74CAF75, 0xDDBBEE99
,0x60FDA37F, 0x269FF701, 0xF5BC5C72, 0x3BC54466, 0x7E345BFB
,0x29768B43, 0xC6DCCB23, 0xFC68B6ED, 0xF163B8E4, 0xDCCAD731
,0x85104263, 0x22401397, 0x112084C6, 0x247D854A, 0x3DF8D2BB
,0x3211AEF9, 0xA16DC729, 0x2F4B1D9E, 0x30F3DCB2, 0x52EC0D86
,0xE3D077C1, 0x166C2BB3, 0xB999A970, 0x48FA1194, 0x642247E9
,0x8CC4A8FC, 0x3F1AA0F0, 0x2CD8567D, 0x90EF2233, 0x4EC78749
,0xD1C1D938, 0xA2FE8CCA, 0xB3698D4, 0x81CFA6F5, 0xDE28A57A
,0x8E26DAB7, 0xBFA43FAD, 0x9DE42C3A, 0x920D5078, 0xCC9B6A5F
,0x4662547E, 0x13C2F68D, 0xB8E890D8, 0xF75E2E39, 0xAFF582C3
,0x80BE9F5D, 0x937C69D0, 0x2DA96FD5, 0x12B3CF25, 0x993BC8AC
,0x7DA71018, 0x636EE89C, 0xBB7BDB3B, 0x7809CD26, 0x18F46E59
,0xB701EC9A, 0x9AA8834F, 0x6E65E695, 0xE67EAAFF, 0xCF0821BC
,0xE8E6EF15, 0x9BD9BAE7, 0x36CE4A6F, 0x9D4EA9F, 0x7CD629B0
,0xB2AF31A4, 0x23312A3F, 0x9430C6A5, 0x66C035A2, 0xBC37744E
,0xCAA6FC82, 0xD0B0E090, 0xD81533A7, 0x984AF104, 0xDAF741EC
,0x500E7FCD, 0xF62F1791, 0xD68D764D, 0xB04D43EF, 0x4D54CCAA
,0x4DFE496, 0xB5E39ED1, 0x881B4C6A, 0x1FB8C12C, 0x517F4665
,0xEA049D5E, 0x355D018C, 0x7473FA87, 0x412EFB0B, 0x1D5AB367
,0xD25292DB, 0x5633E910, 0x47136DD6, 0x618C9AD7, 0xC7A37A1
,0x148E59F8, 0x3C89EB13, 0x27EECEA9, 0xC935B761, 0xE5EDE11C
,0xB13C7A47, 0xDF599CD2, 0x733F55F2, 0xCE791814, 0x37BF73C7
,0xCDEA53F7, 0xAA5B5FFD, 0x6F14DF3D, 0xDB867844, 0xF381CAAF
,0xC43EB968, 0x342C3824, 0x405FC2A3, 0xC372161D, 0x250CBCE2
,0x498B283C, 0x9541FF0D, 0x17139A8, 0xB3DE080C, 0xE49CD8B4
,0xC1906456, 0x84617BCB, 0xB670D532, 0x5C74486C, 0x5742D0B8
]
dword_80150 = [
0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B
,0x9D45F11F, 0xFA58ABAC, 0xE303934B, 0x30FA5520, 0x766DF6AD
,0xCC769188, 0x24C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026
,0x62A38FB5, 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D
,0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, 0x8F5FE703
,0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358
,0xE0692949, 0xC9C8448E, 0xC2896A75, 0x8E7978F4, 0x583E6B99
,0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D
,0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1
,0x6BAE84BB, 0x81A01CFE, 0x82B94F9, 0x48685870, 0x45FD198F
,0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3
,0x55AB2A66, 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3
,0x2887F230, 0xBFA5B223, 0x36ABA02, 0x16825CED, 0xCF1C2B8A
,0x79B492A7, 0x7F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x5BED506
,0x34621FD1, 0xA6FE8AC4, 0x2E539D34, 0xF355A0A2, 0x8AE13205
,0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD
,0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591
,0xC45D0571, 0x6D46F04, 0x5015FF60, 0x98FB2419, 0xBDE997D6
,0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7
,0xC8EEDB79, 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0
,0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C, 0xEFFFBFD
,0x8538560F, 0xAED51E3D, 0x2D392736, 0xFD9640A, 0x5CA62168
,0x5B54D19B, 0x362E3A24, 0xA67B10C, 0x57E70F93, 0xEE96D2B4
,0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C
,0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x90D0B0E
,0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814, 0xF1198557, 0x75074CAF
,0x99DDBBEE, 0x7F60FDA3, 0x1269FF7, 0x72F5BC5C, 0x663BC544
,0xFB7E345B, 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8
,0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084, 0x4A247D85
,0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC
,0x8652EC0D, 0xC1E3D077, 0xB3166C2B, 0x70B999A9, 0x9448FA11
,0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22
,0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6
,0x7ADE28A5, 0xB78E26DA, 0xADBFA43F, 0x3A9DE42C, 0x78920D50
,0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E
,0xC3AFF582, 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF
,0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB, 0x267809CD
,0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA
,0xBCCF0821, 0x15E8E6EF, 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA
,0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035
,0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x4984AF1
,0xECDAF741, 0xCD500E7F, 0x91F62F17, 0x4DD68D76, 0xEFB04D43
,0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1
,0x65517F46, 0x5EEA049D, 0x8C355D01, 0x877473FA, 0xB412EFB
,0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D, 0xD7618C9A
,0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7
,0x1CE5EDE1, 0x47B13C7A, 0xD2DF599C, 0xF2733F55, 0x14CE7918
,0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678
,0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216
,0xE2250CBC, 0x3C498B28, 0xD9541FF, 0xA8017139, 0xCB3DE08
,0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448
,0xB85742D0
]

250
xhs/shield_md5.py

@ -0,0 +1,250 @@
import binascii
"""
md5处理 s盒
shield hmachash值通过 shield_aes.py
6.97.0.1
"""
# md5的s盒 其中有几位 被处理了
SV = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf,
0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af,
0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e,
0x49b40821, 0xf61e2562&0xFF00FF00, 0xc040b340, 0x265e5a51, 0xe9b6c7aa& 0xFF0011FF,
0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6,
0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8 & 0xFF110011,
0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039,
0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97,
0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d,
0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391]
# 根据ascil编码把字符转成对应的二进制
def binvalue(val, bitsize):
binval = bin(val)[2:] if isinstance(val, int) else bin(ord(val))[2:]
if len(binval) > bitsize:
raise ("binary value larger than the expected size")
while len(binval) < bitsize:
binval = "0" + binval
return binval
def string_to_bit_array(text):
array = list()
for char in text:
binval = binvalue(char, 8)
array.extend([int(x) for x in list(binval)])
return array
# 循环左移
def leftCircularShift(k, bits):
bits = bits % 32
k = k % (2 ** 32)
upper = (k << bits) % (2 ** 32)
result = upper | (k >> (32 - (bits)))
return (result)
# 分块
def blockDivide(block, chunks):
result = []
size = len(block) // chunks
for i in range(0, chunks):
result.append(int.from_bytes(block[i * size:(i + 1) * size], byteorder="little"))
return result
# F函数作用于“比特位”上
# if x then y else z
def F(X, Y, Z):
compute = ((X & Y) | ((~X) & Z))
return compute
# if z then x else y
def G(X, Y, Z):
return ((X & Z) | (Y & (~Z)))
# if X = Y then Z else ~Z
def H(X, Y, Z):
return (X ^ Y ^ Z)
def I(X, Y, Z):
return (Y ^ (X | (~Z)))
# 四个F函数
def FF(a, b, c, d, M, s, t):
xhsTemp = leftCircularShift((a + F(b, c, d) + M + t), s)
result = b + xhsTemp
return (result)
def GG(a, b, c, d, M, s, t):
result = b + leftCircularShift((a + G(b, c, d) + M + t), s)
return (result)
def HH(a, b, c, d, M, s, t):
result = b + leftCircularShift((a + H(b, c, d) + M + t), s)
return (result)
def HH1(a, b, c, d, M, s, t):
result = b + leftCircularShift((a + H(b, c, d) + M + t), s)
return (result)
def II(a, b, c, d, M, s, t):
result = b + leftCircularShift((a + I(b, c, d) + M + t), s)
return (result)
# 数据转换
def fmt8(num):
bighex = "{0:08x}".format(num)
binver = binascii.unhexlify(bighex)
result = "{0:08x}".format(int.from_bytes(binver, byteorder='little'))
return (result)
# 计算比特长度
def bitlen(bitstring):
return len(bitstring) * 8
def md5sum(msg):
# 计算比特长度,如果内容过长,64个比特放不下。就取低64bit。
msgLen = bitlen(msg) % (2 ** 64)
# 先填充一个0x80,其实是先填充一个1,后面跟对应个数的0,因为一个明文的编码至少需要8比特,所以直接填充 0b10000000即0x80
msg = msg + b'\x80' # 0x80 = 1000 0000
zeroPad = (448 - (msgLen + 8) % 512) % 512
zeroPad //= 8
msg = msg + b'\x00' * zeroPad + msgLen.to_bytes(8, byteorder='little')
# 计算循环轮数,512个为一轮
msgLen = bitlen(msg)
iterations = msgLen // 512
# print(msgLen, iterations)
# 初始化变量
# 算法魔改的第一个点,也是最明显的点
D = 0x67452301
C = 0xefcdab89
B = 0x98badcfe
A = 0x10325476
# main loop 魔改点 位移数基本都改了
for i in range(0, iterations):
a = A
b = B
c = C
d = D
block = msg[i * 64:(i + 1) * 64]
M = blockDivide(block, 16)
# Rounds 16
a = FF(a, b, c, d, M[0], 6, SV[0])
d = FF(d, a, b, c, M[1], 13, SV[1])
c = FF(c, d, a, b, M[2], 17, SV[2])
b = FF(b, c, d, a, M[3], 21, SV[3])
a = FF(a, b, c, d, M[4], 7, SV[4])
d = FF(d, a, b, c, M[5], 12, SV[5])
c = FF(c, d, a, b, M[6], 17, SV[6])
b = FF(b, c, d, a, M[7], 20, SV[7])
a = FF(a, b, c, d, M[8], 7, SV[8])
d = FF(d, a, b, c, M[9], 12, SV[9])
c = FF(c, d, a, b, M[10], 16, SV[10])
b = FF(b, c, d, a, M[11], 22, SV[11])
a = FF(a, b, c, d, M[12], 7, SV[12])
d = FF(d, a, b, c, M[13], 13, SV[13])
c = FF(c, d, a, b, M[14], 17, SV[14])
b = FF(b, c, d, a, M[15], 22, SV[15])
a = GG(a, b, c, d, M[1], 5, SV[16])
d = GG(d, a, b, c, M[6], 9, SV[17])
c = GG(c, d, a, b, M[11], 14, SV[18])
b = GG(b, c, d, a, M[0], 20, SV[19])
a = GG(a, b, c, d, M[5], 5, SV[20]) # 21 step
d = GG(d, a, b, c, M[10], 9, SV[21]) # 22 step
c = GG(c, d, a, b, M[15], 14, SV[22]) # 23 step
b = GG(b, c, d, a, M[4], 20, SV[23])
a = GG(a, b, c, d, M[9], 5, SV[24])
d = GG(d, a, b, c, M[14], 9, SV[25])
c = GG(c, d, a, b, M[3], 14, SV[26]) # 27 step
b = GG(b, c, d, a, M[8], 20, SV[27])
a = GG(a, b, c, d, M[13], 5, SV[28]) # 29 step
d = GG(d, a, b, c, M[2], 9, SV[29]) # 30 step
c = GG(c, d, a, b, M[7], 14, SV[30])
b = GG(b, c, d, a, M[12], 20, SV[31])
# 16轮
a = HH(a, b, c, d, M[5], 4, SV[32]) # 33 step
d = HH(d, a, b, c, M[8], 11, SV[33])
c = HH(c, d, a, b, M[11], 16, SV[34])
b = HH(b, c, d, a, M[14], 23, SV[35]) # 36
a = HH(a, b, c, d, M[1], 4, SV[36]) # 37
d = HH(d, a, b, c, M[4], 11, SV[37]) # 38
c = HH(c, d, a, b, M[7], 16, SV[38]) # 39
# 正常的第40步
# b = HH(b, c, d, a, M[10], 23, SV[39])
a = HH(a, b, c, d, M[13], 4, SV[40]) # 第40步
b = HH(b, c, a, d, M[10], 23, SV[39]) # 第41步
c = HH(c, d, a, b, M[3], 16, SV[42]) # 第42步
d = HH(d, a, b, c, M[0], 11, SV[41]) # 43
b = HH(b, c, d, a, M[6], 23, SV[43]) # 44
a = HH(a, b, c, d, M[9], 4, SV[44]) # 45
d = HH(d, a, b, c, M[12], 11, SV[45]) # 46
c = HH(c, d, a, b, M[15], 16, SV[46]) # 47
b = HH(b, c, d, a, M[2], 23, SV[47]) # 48
a = II(a, b, c, d, M[0], 6, SV[48])
d = II(d, a, b, c, M[7], 10, SV[49])
c = II(c, d, a, b, M[14], 15, SV[50])
b = II(b, c, d, a, M[5], 21, SV[51]) # 52
a = II(a, b, c, d, M[12], 6, SV[52])
d = II(d, a, b, c, M[3], 10, SV[53])
c = II(c, d, a, b, M[10], 15, SV[54])
b = II(b, c, d, a, M[1], 21, SV[55]) # 56
a = II(a, b, c, d, M[8], 6, SV[56])
d = II(d, a, b, c, M[15], 10, SV[57])
c = II(c, d, a, b, M[6], 15, SV[58])
b = II(b, c, d, a, M[13], 21, SV[59]) # 60
a = II(a, b, c, d, M[4], 6, SV[60])
d = II(d, a, b, c, M[11], 10, SV[61])
c = II(c, d, a, b, M[2], 15, SV[62]) # 63
b = II(b, c, d, a, M[9], 21, SV[63])
A = (A + a) % (2 ** 32)
B = (B + b) % (2 ** 32)
C = (C + c) % (2 ** 32)
D = (D + d) % (2 ** 32)
result = fmt8(A) + fmt8(B) + fmt8(C) + fmt8(D)
return result
def reverse(m):
return "".join([m[i+1] + m[i] for i in range(0, len(m), 2)])
if __name__ == "__main__":
key_list = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
# 第一轮计算 获得结果 c4a02b20 e93108b2 d607639f 52569057 这个和f6填充 得到结果
data = bytes.fromhex("9cb4ec61773bebe384b03865794a3034c3bff438fedeb53d9c15a556feaaf8548bf532ee979caebb543f21edd9f4972370a4cce474a2c277a897aa4bf65fbf4d2f6170692f736e732f76312f6e6f74652f666565646e6f74655f69643d36366439323830333030303030303030316630316236383926706167653d31266e756d3d352666657463685f6d6f64653d3126736f757263653d266164735f747261636b5f69643d6669643d3137323538373037303531303636376565333737323431396563336236353064393436663763386632653137266465766963655f66696e6765727072696e743d3230323430393034313530303332663930373030323232316565343637393364323839346164323532366438383230316165626231666534646263626661266465766963655f66696e6765727072696e74313d3230323430393034313530303332663930373030323232316565343637393364323839346164323532366438383230316165626231666534646263626661266c61756e63685f69643d3137323539333737363626747a3d417369612532465368616e67686169266368616e6e656c3d504d67647431393933353733372676657273696f6e4e616d653d362e39372e302e312664657669636549643d32666537353036322d613532382d333334302d626564332d32323061363766376632343026706c6174666f726d3d616e64726f6964267369643d73657373696f6e2e31373235383730373132363338383830383638313238266964656e7469666965725f666c61673d3026743d313732353933373739372670726f6a6563745f69643d454346414146266275696c643d3639373031383126785f74726163655f706167655f63757272656e743d6578706c6f72655f66656564266c616e673d7a682d48616e73266170705f69643d4543464141463031267569733d6461726b706c6174666f726d3d616e64726f6964266275696c643d363937303138312664657669636549643d32666537353036322d613532382d333334302d626564332d323230613637663766323430")
# 2f6170692f736e732f76312f6e6f74652f666565646e6f74655f69643d36366365656162653030303030303030316430336235343626706167653d31266e756d3d352666657463685f6d6f64653d3126736f757263653d266164735f747261636b5f69643d6669643d3137323136333931353431303334383363306461616163653263613932363663626133376163396665313134266465766963655f66696e6765727072696e743d3230323430373232313735383139323830393030366537653333346534363632383632306636373638626366336230313533623139373762396636636436266465766963655f66696e6765727072696e74313d3230323430373232313735383139323830393030366537653333346534363632383632306636373638626366336230313533623139373762396636636436266370755f6e616d653d5175616c636f6d6d2b546563686e6f6c6f676965732532432b496e632b534d38313530266465766963655f6d6f64656c3d70686f6e65266c61756e63685f69643d3137323735373830333626747a3d417369612532465368616e67686169266368616e6e656c3d4350412d334453502d4e332d5a534b4a2676657273696f6e4e616d653d362e39372e302e31266f766572736561735f6368616e6e656c3d302664657669636549643d31313932313466632d306665352d336165382d393164642d62616138323163313133323426706c6174666f726d3d616e64726f6964267369643d73657373696f6e2e31373231363339323031313432303736333831313331266964656e7469666965725f666c61673d3426743d313732373539303939382670726f6a6563745f69643d454346414146266275696c643d36393730313831266c616e673d7a682d48616e73266170705f69643d4543464141463031267569733d6461726b267465656e616765723d30706c6174666f726d3d616e64726f6964266275696c643d363937303138312664657669636549643d31313932313466632d306665352d336165382d393164642d626161383231633131333234
# data = bytes.fromhex("868da458ff5a821fcff6f8bdafc009f56debf5290e0758a5d3ba4a4ae08b55764982003382b5907464bc90c34b877308474d16dec037edcfbf0ed45c2c045e2d2f6170692f736e732f76312f6e6f74652f666565646e6f74655f69643d36366365656162653030303030303030316430336235343626706167653d31266e756d3d352666657463685f6d6f64653d3126736f757263653d266164735f747261636b5f69643d6669643d3137323136333931353431303334383363306461616163653263613932363663626133376163396665313134266465766963655f66696e6765727072696e743d3230323430373232313735383139323830393030366537653333346534363632383632306636373638626366336230313533623139373762396636636436266465766963655f66696e6765727072696e74313d3230323430373232313735383139323830393030366537653333346534363632383632306636373638626366336230313533623139373762396636636436266370755f6e616d653d5175616c636f6d6d2b546563686e6f6c6f676965732532432b496e632b534d38313530266465766963655f6d6f64656c3d70686f6e65266c61756e63685f69643d3137323735373830333626747a3d417369612532465368616e67686169266368616e6e656c3d4350412d334453502d4e332d5a534b4a2676657273696f6e4e616d653d362e39372e302e31266f766572736561735f6368616e6e656c3d302664657669636549643d31313932313466632d306665352d336165382d393164642d62616138323163313133323426706c6174666f726d3d616e64726f6964267369643d73657373696f6e2e31373231363339323031313432303736333831313331266964656e7469666965725f666c61673d3426743d313732373539303939382670726f6a6563745f69643d454346414146266275696c643d36393730313831266c616e673d7a682d48616e73266170705f69643d4543464141463031267569733d6461726b267465656e616765723d30706c6174666f726d3d616e64726f6964266275696c643d363937303138312664657669636549643d32666537353036322d613532382d333334302d626564332d323230613637663766323430")
# 第二轮计算 获得key c4a02b20e93108b2d607639f52569057
# 7e424e3418de39d5ca9a0ba8d9dfde24
# data = bytes.fromhex("ece7ce329530e875a59c92d7c5aa639f07819f43646d32cfb9d020208ae13f1c23e86a59e8dffa1e0ed6faa921ed19622d277cb4aa5d87a5d564be36466e34477e424e3418de39d5ca9a0ba8d9dfde24")
print("plainText: ", data)
print("result: ", md5sum(data))

36
xhs/shield_rc4.py

@ -0,0 +1,36 @@
"""
rc4
"""
def KSA(key):
""" Key-Scheduling Algorithm (KSA) """
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
return S
def PRGA(S):
""" Pseudo-Random Generation Algorithm (PRGA) """
i, j = 0, 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
yield K
def RC4(key, text):
""" RC4 encryption/decryption """
S = KSA(key)
keystream = PRGA(S)
res = []
for char in text:
res.append(char ^ next(keystream))
return bytes(res)

28
xhs/xhs_aes.py

@ -0,0 +1,28 @@
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
"""
8.13 xhs-aes版本
"""
def encrypt_aes_cbc(plaintext):
"""
aes-cbc
:param plaintext:
:return:
"""
key = "3061396677336d3671666c6c3264656a"
iv = "6d686171686e6a6d723072736f6f336f"
cipher = AES.new(bytes.fromhex(key), AES.MODE_CBC, bytes.fromhex(iv))
ciphertext = cipher.encrypt(pad(bytes.fromhex(plaintext), AES.block_size))
return ciphertext.hex()
def aes_encrypt(plain_text):
"""
hex字符串便于加密操作
:param plain_text:
:return:
"""
array = "".join([hex(i)[2:] for i in plain_text])
payload = encrypt_aes_cbc(array)
return payload

208
xhs/xhs_captcha.py

@ -0,0 +1,208 @@
import math
import time
import json
import random
from kuaishou.ks_http import download_q
from utils.rotate_captcha import export_single_discern
from xhs.xhs_param import get_xs, encrypt_info, decrypt_info
from loguru import logger
class SignalCaptcha(object):
"""
"""
def __init__(self, web_session, verify_uuid,
a1, webId, source="", is_proxy=False):
self.is_proxy = is_proxy
self.source = source
self.verify_uuid = verify_uuid
self.register_url = "https://edith.xiaohongshu.com/api/redcaptcha/v2/captcha/register"
self.check_url = "https://edith.xiaohongshu.com/api/redcaptcha/v2/captcha/check"
self.headers = {
"authority": "edith.xiaohongshu.com",
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"content-type": "application/json;charset=UTF-8",
"origin": "https://www.xiaohongshu.com",
"pragma": "no-cache",
"referer": "https://www.xiaohongshu.com/",
"sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"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/120.0.0.0 Safari/537.36",
"x-s": "XYW_eyJzaWduU3ZuIjoiNTEiLCJzaWduVHlwZSI6IngxIiwiYXBwSWQiOiJsb2dpbiIsInNpZ25WZXJzaW9uIjoiMSIsInBheWxvYWQiOiJkNzkxNmQ0MDAyOTZmZDg4ZmIzYjcxOGY2MWRlOGU2MTEwYzZmMTBlMjZiOWI0NTQ1OTg0M2VjMTI2NmRlMGZiNDNkZGI3MmE5MWYyOTJkZTM4Yjk3MDlmZjcyMDBmMTRjOWUzYmZkYTFmYWExZWI5MGQ3NGFhMzFiNTRjNzJjZDBkNzRhYTMxYjU0YzcyY2RhYzQ4OWI5ZGE4Y2U1ZTQ4ZjRhZmI5YWNmYzNlYTI2ZmUwYjI2NmE2YjRjYzNjYjVhMDE2NzA1NzIyMWMxZjhkMzY4YzFkMjBjZGE4MGY5OWIxMWYyZWFhMGE5ZmQ3MWQ3NmZmZTgwMmI3MjM3NWQ2OWZiY2JjYzk1OTUxMmNhZDRmZWFjNWU1MjY1MDljYTliZDA3MzE0OGZjMDEzYzJlMzU4ZWE1ZDg5ZGQ4MzZkNWU2OTM4NGJkN2FmNjQ3MjY0ZmNjZWQxMmVkMWFiNmFjY2U1YTE1MWZmZDMzODNlZDE4OGMyMWE4OTlmY2E2Y2UifQ==",
"x-s-common": "2UQAPsHCPUIjqArjwjHjNsQhPsHCH0rjNsQhPaHCH0P1+jhhHjIj2eHjwjQ+GnPW/MPjNsQhPUHCHfl689S1HjIj2ecjwjHIN0L1+eGjNsQh+aHCH0rh8BpSGfp0G9cEP7pi+9p94gkT8fYh4gbx8opD+fMjJer9weG9JgbYPAZIPeZU+erh+0DjNsQh+jHCP/qIw/r7P/c9P0cM+jIj2eqjwjQGnp4K8gSt2fbg8oppPMkMank6yLELznSPcFkCGp4D4p8HJo4yLFD9anEd2rSk49S8nrQ7LM4zyLRkad+jPfzIGfSQqFS1/dmyP0pgnSYt2fbgwgpQyfRk/p+QqFS1cfYSp7Y9np4zyLRkafTw2fTh/fMzPrMrcgSOpbkTnDzd+bkTagk8yf+h/F48PDMgLflOzMLU/pzb4MSCnfM+prQV/nkyyLRga/mwpbrl/MzdPrFUpfk+prDU/fMaJrMonfSwzrE3nD4QPFMTz/p+pMSE/MztyMkL//z8yfVA/LzVJpkxG7S+zrQT/pzayDRgpgk8Jpk3npzBJpSgzgYypFDM/L4zPFEozfY+2D8k/SzayDECafkyzrQ3/dkaybSL/gY8ySLIngk02pDULfY82SrF/M4Q+pkoz/z8PSLlnSzz+bkxLfYyJLDMnp4wJpSC/fl8prDUnfMp4MSxa/QwJLLUnnkaySSC/fk8yS8i/LzbPDECc/bw2DSE/p4+2bkxyBT+2SDFngkByFMxcfkyzFLF/L48+LRgagY+pMSEnS4ByrMoz/pyJprA/p4zPFETnfMypB47/nMQPFMx//+wpMrU/M4yypDUafk82fVl/Mzp2rMT/fYyzbQTnS4ByDEgp/b+ySDl/LzVySSLJBSyzrrA/gkbPrRonfk+zrpCngktJrMCp/zypFLlnDzd+pkoL/z+2Sk3/S4pPDRL//zwzFk3/0QBJLExL/++ySDInfMwySkgLgY+Jp83/p4ByLETzfk8PSLlnpzbPLMgnfMyzrMC/FzwJbkrz/zOzFME/p4b+rRLJBM8PDLUn/Qpyn8zO/FjNsQhwsHCHDDAwoQH8B4AyfRI8FS98g+Dpd4daLP3JFSb/BMsn0pSPM87nrldzSzQ2bPAGdb7zgQB8nph8emSy9E0cgk+zSS1qgzianYt8p+DpoYlqg4Dag8mqM4sG9Y7LozF89FF+DTp2dYQyemAPrlNq9kl49EE+Fzyag86q7YjLBkEG7pmanYN8LzY+7+fySzLadbFLjTl4FbI8omwaL+aaMm/p94Aqg4La/P98/+l49E7qg4raLp+qFSi8Bph/bSDagY8PFS9+g+g4g4atA4ILok0/d+Dn/+S8dbFcLS3/fLApd41qgbFqomM4e+N2f4APp4C8LSepS4QysVINMmFLLTM4FbQPMiUJ9MD8nSl498QcFbSpb8FqDSbtUTQznM1G98D8nkdGApSqApSzobFnd+++np/Job/qrQN8n8T+o+Q2rRSzbmFnDSbPBpx4gzpq7knPLhE/r4Q2BRSpop7JBETcnph8rpcanYiJLSbybmzJF8xaL+aLoS0zUTQyFYQ8pm7LDS9zApoJ0mAyS4SqA8M4AbQ408S8fEt8pSSLf4QyApSygQT+LTl4M+QyLTApBR98/8+8oPApd4CJnRwqMSl4e+QPF8YJ9u98/ml49zCG/4ALMm78LShJobQ2bk9Gp8FJfpM4ozA8n+jLnQdqMDI/7+hqgzdanTw8LzA4d+nLFDlanT6q9kBP7+h8DlManTw8nSn4B8Qy9++Lbm7aozc4rbQP9pSPpmFnLS387Pl4g4kanWAq98c4rSFqg4d8gb7JrS9qLpQ2b4Cz98lyL4Qa9LApdzQanYHwrSk+9p/Lo4dGfMbwomx+g+DyfQ3anS0/7bc4ozyLo49agG78nTc4bScqDTSyn+wq9SQ2d+Qye4A+fu9qMzM4FkSpdzda/+NqAmc4FSQcFbA8SpjqFDAcg+/PrRS8S87cFDA+7+3qg4M/fza4FSiaomQc94APgHA8pz64fprLA8S8b8FcDS3yebQPMr9qS8FLLS9G9YQzLEAPF8lcg4P+9p3pdzYanTkOaHVHdWEH0iT+AcFPecU+AcUNsQhP/Zjw0H7+gF=",
"x-t": "1709171462456"
}
self.pic_headers = {
"authority": "picasso-static.xiaohongshu.com",
"accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"origin": "https://www.xiaohongshu.com",
"pragma": "no-cache",
"sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "image",
"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/120.0.0.0 Safari/537.36"
}
self.cookies = {
"a1": a1,
"webId": webId,
"web_session": web_session,
}
@staticmethod
def get_slide_track(distance):
"""
:param distance:
:return: <type 'list'>: [[x,y,t], ...]
x:
y: , , 0
t: , :
"""
t = 246
count = random.randint(4, 10)
_y_list = [0, 0, -1, 1, 1, 1, 1, 0]
y = 0
x = 0
slide_track = [[0, 0, 246]]
for i in range(count):
if x >= distance:
break
t += random.randint(50, 200)
y += _y_list[random.randint(0, 7)]
x += random.randint(30, 60)
slide_track.append([x, y, t])
if x != distance:
slide_track[-1][0] = distance
return slide_track, slide_track[-1][-1]
@staticmethod
def get_captcha_info(fg_path, bg_path):
"""
captcha_info加密参数的封装
:param fg_path:
:param bg_path:
:return:
"""
angle = export_single_discern(fg_path, bg_path)
distance = math.ceil(angle * 0.79)
logger.info(f"处理后的 距离为 : {distance}")
track, last_time = SignalCaptcha.get_slide_track(distance)
_time = last_time + random.randint(100, 200) # 处理字段
logger.info(f"_time: {_time}")
logger.info(f"track : {track}")
captcha_info = {
"mouseEnd": encrypt_info(str(distance), _type="mouseEnd"),
"time": encrypt_info(str(_time), _type="time"),
"track": encrypt_info(str(track).replace(" ", ""), _type="track"),
"width": encrypt_info("286", _type="width")
}
captcha_info = json.dumps(captcha_info, ensure_ascii=False, separators=(',', ':'))
return captcha_info
def check(self, rid, captcha_info):
"""
:param rid:
:param captcha_info:
:return:
"""
data = {
"rid": rid,
"verifyType": 102,
"verifyBiz": "461",
"verifyUuid": self.verify_uuid,
"biz": "sns_web",
"sourceSite": self.source,
"version": "1.0.1",
"captchaInfo": captcha_info
}
headers = self.headers.copy()
data = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
h = get_xs(self.cookies["a1"], "/api/redcaptcha/v2/captcha/check", data, str(int(time.time() * 1000)))
headers.update(h)
logger.info(data)
response = download_q(self.check_url, headers=headers, cookies=self.cookies,
data=data, is_proxy=self.is_proxy)
return response.json()
def register(self):
"""
:return:
"""
data = {
"secretId": "000",
"verifyType": "102",
"verifyUuid": self.verify_uuid,
"verifyBiz": "461",
"biz": "sns_web",
"sourceSite": self.source,
"version": "1.0.1"
}
data = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
h = get_xs(self.cookies["a1"], "/api/redcaptcha/v2/captcha/register", data, str(int(time.time() * 1000)))
headers = self.headers.copy()
headers["x-s-common"] = ""
headers.update(h)
response = download_q(self.register_url, headers=headers, cookies=self.cookies,
data=data, is_proxy=self.is_proxy)
return response.json()
def run(self):
"""
main:
:return:
"""
try:
register_data = self.register()
logger.info(f"初始化验证码信息:{register_data}")
data = register_data["data"]
rid = data["rid"]
captcha_info = data["captchaInfo"]
source = json.loads(decrypt_info(captcha_info)[:-2]) # 因为有补位则去掉补位结果
logger.info(f"解密验证码初始信息:{source}")
bg_url = source["backgroundUrl"]
fg_url = source["captchaUrl"]
bg_path = download_q(bg_url, headers=self.pic_headers, cookies={}).content
fg_path = download_q(fg_url, headers=self.pic_headers, cookies={}).content
captcha_info = SignalCaptcha.get_captcha_info(fg_path, bg_path)
logger.info(f"生成验证信息:{captcha_info}")
result = self.check(rid, captcha_info)
logger.info(f"验证结果:{result}")
except Exception as e:
result = {"msg": "失败", "data": {}}
logger.error(e)
return result
if __name__ == '__main__':
verify_uuid = "22318d30-daf4-4fc1-8148-c26d86b0a51a*CiZjkIoB"
web_session = "030037a2d377ba382693826598224a7950081e"
a1 = ""
webId = ""
source = "https://www.xiaohongshu.com/explore/65c02be2000000002c028b93"
A = SignalCaptcha(web_session, verify_uuid, a1, webId, source=source)
res = A.run()
print(res)

226
xhs/xhs_des.py

@ -0,0 +1,226 @@
import ctypes
from functools import reduce
from hashlib import md5
"""
deskey的utf-8
key还原标准des调用
"""
def MD5(strs):
m = md5()
m.update(strs.encode("utf-8"))
return m.hexdigest()
def hex_2_str(v: int):
v = v & 0xffffffff # 将结果转换为32位有符号整数
tmp = str(hex(v))[2:]
return "0" + tmp if len(tmp) % 2 > 0 else tmp
def logical_left_shift(n, bits):
"""
:param n:
:param bits:
:return:
"""
return ctypes.c_int(n << bits).value
def logical_right_shift(n, bits):
"""
:param n:
:param bits:
:return:
"""
return (n % 0x100000000) >> bits
def get_s_box(i, j):
"""
sbox
:param i:
:param j:
:return:
"""
s_box1 = [-2146402272, -2147450880, 32768, 1081376, 1048576, 32, -2146435040, -2147450848, -2147483616, -2146402272,
-2146402304, -2147483648, -2147450880, 1048576, 32, -2146435040, 1081344, 1048608, -2147450848, 0,
-2147483648, 32768, 1081376, -2146435072, 1048608, -2147483616, 0, 1081344, 32800, -2146402304,
-2146435072, 32800, 0, 1081376, -2146435040, 1048576, -2147450848, -2146435072, -2146402304, 32768,
-2146435072, -2147450880, 32, -2146402272, 1081376, 32, 32768, -2147483648, 32800, -2146402304, 1048576,
-2147483616, 1048608, -2147450848, -2147483616, 1048608, 1081344, 0, -2147450880, 32800, -2147483648,
-2146435040, -2146402272, 1081344]
s_box2 = [8396801, 8321, 8321, 128, 8396928, 8388737, 8388609, 8193, 0, 8396800, 8396800, 8396929, 129, 0, 8388736,
8388609, 1, 8192, 8388608, 8396801, 128, 8388608, 8193, 8320, 8388737, 1, 8320, 8388736, 8192, 8396928,
8396929, 129, 8388736, 8388609, 8396800, 8396929, 129, 0, 0, 8396800, 8320, 8388736, 8388737, 1, 8396801,
8321, 8321, 128, 8396929, 129, 1, 8192, 8388609, 8193, 8396928, 8388737, 8193, 8320, 8388608, 8396801,
128, 8388608, 8192, 8396928]
s_box3 = [536870928, 541065216, 16384, 541081616, 541065216, 16, 541081616, 4194304, 536887296, 4210704, 4194304,
536870928, 4194320, 536887296, 536870912, 16400, 0, 4194320, 536887312, 16384, 4210688, 536887312, 16,
541065232, 541065232, 0, 4210704, 541081600, 16400, 4210688, 541081600, 536870912, 536887296, 16,
541065232, 4210688, 541081616, 4194304, 16400, 536870928, 4194304, 536887296, 536870912, 16400, 536870928,
541081616, 4210688, 541065216, 4210704, 541081600, 0, 541065232, 16, 16384, 541065216, 4210704, 16384,
4194320, 536887312, 0, 541081600, 536870912, 4194320, 536887312]
s_box4 = [268439616, 4096, 262144, 268701760, 268435456, 268439616, 64, 268435456, 262208, 268697600, 268701760,
266240, 268701696, 266304, 4096, 64, 268697600, 268435520, 268439552, 4160, 266240, 262208, 268697664,
268701696, 4160, 0, 0, 268697664, 268435520, 268439552, 266304, 262144, 266304, 262144, 268701696, 4096,
64, 268697664, 4096, 266304, 268439552, 64, 268435520, 268697600, 268697664, 268435456, 262144, 268439616,
0, 268701760, 262208, 268435520, 268697600, 268439552, 268439616, 0, 268701760, 266240, 266240, 4160,
4160, 262208, 268435456, 268701696]
s_box5 = [16843776, 0, 65536, 16843780, 16842756, 66564, 4, 65536, 1024, 16843776, 16843780, 1024, 16778244,
16842756, 16777216, 4, 1028, 16778240, 16778240, 66560, 66560, 16842752, 16842752, 16778244, 65540,
16777220, 16777220, 65540, 0, 1028, 66564, 16777216, 65536, 16843780, 4, 16842752, 16843776, 16777216,
16777216, 1024, 16842756, 65536, 66560, 16777220, 1024, 4, 16778244, 66564, 16843780, 65540, 16842752,
16778244, 16777220, 1028, 66564, 16843776, 1028, 16778240, 16778240, 0, 65540, 66560, 0, 16842756]
s_box6 = [520, 134349312, 0, 134348808, 134218240, 0, 131592, 134218240, 131080, 134217736, 134217736, 131072,
134349320, 131080, 134348800, 520, 134217728, 8, 134349312, 512, 131584, 134348800, 134348808, 131592,
134218248, 131584, 131072, 134218248, 8, 134349320, 512, 134217728, 134349312, 134217728, 131080, 520,
131072, 134349312, 134218240, 0, 512, 131080, 134349320, 134218240, 134217736, 512, 0, 134348808,
134218248, 131072, 134217728, 134349320, 8, 131592, 131584, 134217736, 134348800, 134218248, 520,
134348800, 131592, 8, 134348808, 131584]
s_box7 = [256, 34078976, 34078720, 1107296512, 524288, 256, 1073741824, 34078720, 1074266368, 524288, 33554688,
1074266368, 1107296512, 1107820544, 524544, 1073741824, 33554432, 1074266112, 1074266112, 0, 1073742080,
1107820800, 1107820800, 33554688, 1107820544, 1073742080, 0, 1107296256, 34078976, 33554432, 1107296256,
524544, 524288, 1107296512, 256, 33554432, 1073741824, 34078720, 1107296512, 1074266368, 33554688,
1073741824, 1107820544, 34078976, 1074266368, 256, 33554432, 1107820544, 1107820800, 524544, 1107296256,
1107820800, 34078720, 0, 1074266112, 1107296256, 524544, 33554688, 1073742080, 524288, 0, 1074266112,
34078976, 1073742080]
s_box8 = [2097152, 69206018, 67110914, 0, 2048, 67110914, 2099202, 69208064, 69208066, 2097152, 0, 67108866, 2,
67108864, 69206018, 2050, 67110912, 2099202, 2097154, 67110912, 67108866, 69206016, 69208064, 2097154,
69206016, 2048, 2050, 69208066, 2099200, 2, 67108864, 2099200, 67108864, 2099200, 2097152, 67110914,
67110914, 69206018, 69206018, 2, 2097154, 67108864, 67110912, 2097152, 69208064, 2050, 2099202, 69208064,
2050, 67108866, 69208066, 69206016, 2099200, 0, 2, 69208066, 0, 2099202, 69206016, 2048, 67108866,
67110912, 2048, 2097154]
s_box = [s_box1, s_box2, s_box3, s_box4, s_box5, s_box6, s_box7, s_box8]
return s_box[i][j]
def alg_main(key, _plaintext_left, _plaintext_right):
"""
3. +
:param key:
:param _plaintext_left:
:param _plaintext_right:
:return: 16
"""
for i in range(0, len(key), 2):
_key = key[i] # 轮密钥
_key2 = key[i+1]
tmp = _plaintext_right ^ _key
tmp1 = (logical_right_shift(_plaintext_right, 4) | logical_left_shift(_plaintext_right, 28)) ^ _key2
mid = get_s_box(0, logical_right_shift(tmp, 24) & 63) | get_s_box(1, logical_right_shift(tmp, 16) & 63) | \
get_s_box(2, logical_right_shift(tmp, 8) & 63) | get_s_box(3, tmp & 63) | \
get_s_box(4, logical_right_shift(tmp1, 24) & 63) | get_s_box(5, logical_right_shift(tmp1, 16) & 63) | \
get_s_box(6, logical_right_shift(tmp1, 8) & 63) | get_s_box(7, tmp1 & 63)
tmp2 = _plaintext_left ^ mid
_plaintext_left = _plaintext_right # 左边继承右边
_plaintext_right = tmp2
# print(f"第{i}轮 ; ", _plaintext_left, _plaintext_right, mid, _key, _key2, tmp, tmp1)
# break
t1 = logical_right_shift(_plaintext_right, 1) | logical_left_shift(_plaintext_right, 31)
t2 = logical_right_shift(_plaintext_left, 1) | logical_left_shift(_plaintext_left, 31)
t3 = (logical_right_shift(t1, 1) ^ t2) & 1431655765
t4 = t2 ^ t3
t5 = t1 ^ logical_left_shift(t3, 1)
t6 = (logical_right_shift(t4, 8) ^ t5) & 16711935
t7 = t5 ^ t6
t8 = t4 ^ logical_left_shift(t6, 8)
t9 = (logical_right_shift(t8, 2) ^ t7) & 858993459
t10 = t7 ^ t9
t11 = logical_left_shift(t9, 2) ^ t8
t12 = (logical_right_shift(t10, 16) ^ t11) & 65535
t13 = t11 ^ t12
t14 = t10 ^ logical_left_shift(t12, 16)
t15 = (logical_right_shift(t14, 4) ^ t13) & 252645135
t16 = t15 ^ t13
t17 = t14 ^ logical_left_shift(t15, 4)
b1 = logical_right_shift(t17, 24)
b2 = logical_right_shift(t17, 16) & 255
b3 = logical_right_shift(t17, 8) & 255
b4 = t17 & 255
b5 = logical_right_shift(t16, 24)
b6 = logical_right_shift(t16, 16) & 255
b7 = logical_right_shift(t16, 8) & 255
b8 = t16 & 255
segment_list = [b1, b2, b3, b4, b5, b6, b7, b8]
segment = "".join([chr(s) for s in segment_list])
segment_hex = "".join([hex_2_str(s) for s in segment_list])
# print(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17)
# print(segment)
# print(segment_hex)
return segment_hex, segment
def deal_array2(array1_res, array2_res):
"""
2. alg_main中的后半部分是可逆的
:param array1_res:
:param array2_res:
:return:
"""
tmp = (logical_right_shift(array1_res, 4) ^ array2_res) & 252645135
tmp1 = array2_res ^ tmp
tmp1_ = array1_res ^ logical_left_shift(tmp, 4)
tmp2 = (logical_right_shift(tmp1_, 16) ^ tmp1) & 65535
tmp2_ = tmp1 ^ tmp2
tmp3 = logical_right_shift(tmp2_, 2)
tmp4 = tmp1_ ^ logical_left_shift(tmp2, 16)
tmp5 = (tmp3 ^ tmp4) & 858993459
tmp5_ = logical_left_shift(tmp5, 2)
tmp6 = tmp5 ^ tmp4
tmp7 = (logical_right_shift(tmp2_ ^ tmp5_, 8) ^ tmp6) & 16711935
tmp7_ = tmp6 ^ tmp7
tmp8 = logical_right_shift(tmp7_, 1)
tmp9 = tmp2_ ^ tmp5_ ^ logical_left_shift(tmp7, 8)
tmp10 = (tmp8 ^ tmp9) & 1431655765
tmp10_ = tmp9 ^ tmp10
tmp11 = tmp7_ ^ logical_left_shift(tmp10, 1)
tmp12 = logical_left_shift(tmp11, 1) | logical_right_shift(tmp11, 31)
tmp13 = logical_left_shift(tmp10_, 1) | logical_right_shift(tmp10_, 31)
return tmp12, tmp13
def deal_array(array):
"""
1.
:param array:
:return:
"""
for i in range(len(array)):
array[i] = array[i] << (24 - 8 * i)
res = reduce(lambda x, y: x | y, array)
return res
def des_encrypt(key, plain_text):
"""
des加密入口
:param key: list
:param plain_text: list
:return:
"""
array1_hex = []
array1_string = []
for m in range(0, len(plain_text), 8):
left_list, right_list = plain_text[m:m + 4], plain_text[m + 4:m + 8]
left, right = deal_array2(deal_array(left_list), deal_array(right_list))
segment_hex, segment = alg_main(key, left, right)
array1_hex.append(segment_hex)
array1_string.append(segment)
_hex = "".join(array1_hex)
_string = "".join(array1_string)
return _hex, _string

199
xhs/xhs_param.py

@ -0,0 +1,199 @@
import binascii
import json
import random
import time
from utils.tool import download_q
from xhs.xhs_aes import aes_encrypt
from xhs.xhs_des import MD5, des_encrypt, logical_right_shift
import base64
"""
xhs xs-xt aes
"""
def get_xs(a1, url, data, x_t):
"""
{"x-s":"", "x-t": ""}
:param a1:
:param url:
:param data:
:param x_t:
:return:
"""
key = [187050025, 472920585, 186915882, 876157969, 255199502, 806945584, 220596020, 958210835, 757275681, 940378667,
489892883, 705504304, 354103316, 688857884, 890312192, 219096591, 622400037, 254088489, 907618332, 52759587,
907877143, 53870614, 839463457, 389417746, 975774727, 372382245, 437136414, 909246726, 168694017, 473575703,
52697872, 1010440969]
# 7.19 改
key = [52833590, 1010372866, 188091914, 406398501, 255201040, 957421848, 741478954, 958217745, 758320394, 990653224,
958072630, 722273561, 890968096, 185282339, 890768915, 254222393, 890835209, 86457382, 907354431, 120004616,
906834724, 120984878, 841809977, 370543655, 405617431, 909250592, 439235128, 875174166, 187044111, 742001189,
184950816, 1010310941]
def get_enviroment(api="/api/sns/web/v1/feed", data="{}"):
url = f"url={api}"
info = url + data
info = MD5(info)
return info
x1 = get_enviroment(url, data)
x2 = "0|0|0|1|0|0|1|0|0|0|1|0|0|0|0"
x3 = a1
x4 = x_t
info = f"x1={x1};x2={x2};x3={x3};x4={x4};"
key_46_base64 = base64.b64encode(info.encode('utf-8')).decode('utf-8')
# base64 转为十进制数
array = [ord(i) for i in key_46_base64]
# 对上述数组进行位移操作 初始化明文 每四个数为一组进行计算
# payload = des_encrypt(key, array)[0]
payload = aes_encrypt(array)
_base_data = {
"signSvn": "53",
"signType": "x2",
"appId": "xhs-pc-web",
"signVersion": "1",
"payload": payload
}
data = json.dumps(_base_data, ensure_ascii=False, separators=(',', ':'))
x_s = base64.b64encode(data.encode("utf-8")).decode("utf-8")
return {"x-s": "XYW_" + x_s, "x-t": x4}
def decrypt_info(captcha_info):
"""
captchaInfo进行解密
:param captcha_info:
:return:
"""
d1_h = base64.b64decode(captcha_info).hex()
hex_string = [int(d1_h[i:i + 2], 16) for i in range(0, len(d1_h), 2)]
key = [302776838, 875694381, 453784583, 103810086, 571285557, 371853838, 805843717, 657858610, 909711421, 117571586,
806950164, 52892707, 890572860, 184812328, 605753650, 151859252, 890381338, 556605977, 221318145, 688470832,
20581922, 940376595, 220597508, 706218773, 707144202, 940115729, 153491459, 892344601, 171182610, 337259779,
36577297, 271782150]
payload = des_encrypt(key, hex_string)[-1]
return payload
def encrypt_info(plain_text, _type="mouseEnd"):
"""
mouseEnd: 286/360
time:
track: [x,y,]
width: 286
:param plain_text:
:param _type:
:return:
"""
key_list = {
"mouseEnd": [187040774,941175553,188094512,672732199,87169561,957943856,758133532,957159952,756225326,588057094,890963239,185401634,889790517,185020707,874067993,103227439,874058250,119748888,906756366,119942921,571280678,104203531,304941601,372245811,439101969,808593969,170278418,876218908,186782722,1010306836,187048244,942146861],
"time": [151392289,942357812,20069652,739772683,18094115,959001623,789850125,17376056,624043796,51713567,606673961,990187014,1007232771,571224112,286990908,36584204,872954931,235544848,18625028,103158589,35458076,86060551,908336391,19927066,572793385,842413842,170468621,907553325,422194994,806880270,170526239,738861861],
"track": [187578120,538642213,135659559,941298489,221391896,489174819,724704537,688193565,605502240,723068432,890766372,453786172,691602974,50791978,874190097,656489730,337328660,120465962,856495123,218504222,805646351,104007424,373040900,371339527,170658349,622200847,539437315,876100658,455352379,975704860,456002850,940583190],
"width": [187040774,941175553,188094512,672732199,87169561,957943856,758133532,957159952,756225326,588057094,890963239,185401634,889790517,185020707,874067993,103227439,874058250,119748888,906756366,119942921,571280678,104203531,304941601,372245811,439101969,808593969,170278418,876218908,186782722,1010306836,187048244,942146861]
}
key = key_list[_type]
padding = 8 - len(plain_text) % 8
plain_text = [ord(i) for i in plain_text]
for c in range(padding): # 补位
plain_text.append(padding)
array1_hex2 = des_encrypt(key, plain_text)[0]
byte_data = binascii.unhexlify(array1_hex2)
base64_data = base64.b64encode(byte_data).decode("utf-8")
return base64_data
def get_a1(platform_type="Mac OS"):
"""
cookie a1webId
:param platform_type:
:return:
"""
def genRandomString(length):
char_list = "abcdefghijklmnopqrstuvwxyz1234567890"
return ''.join(random.sample(char_list, length))
def crc32(data):
table = []
for num in range(256):
temp = num
for _ in range(8):
temp = (logical_right_shift(temp, 1)) ^ 0XEDB88320 if temp & 1 else logical_right_shift(temp, 1)
table.append(temp)
i = -1
for a in range(len(data)):
i = logical_right_shift(i, 8) ^ table[255 & (i ^ ord(data[a]))]
return logical_right_shift((-1 ^ i), 0)
def getPlatformCode(t):
# 根据不同系统返回结果
if t == "Android":
return "2"
elif t == "iOS":
return "1"
elif t == "Mac OS":
return "3"
elif t == "Linux":
return "4"
# elif t == "Windows":
# return "0"
else:
return "5"
LOCAL_ID_SECRET_VERSION = "0"
o = str(hex(int(time.time() * 1000))[2:])
o = o + genRandomString(30)
o = o + getPlatformCode(platform_type) + LOCAL_ID_SECRET_VERSION + "000"
o = o + str(crc32(o))
a1 = o[:52]
web_id = MD5(a1)
return a1, web_id
def get_comment(a1, xs, xt):
"""
node来获得x-comment参数
:param a1:
:param xs:
:param xt:
:return:
"""
url = "http://127.0.0.1:3000/xhs/get_comment"
data = {
"a1": a1,
"xs": xs,
"xt": xt
}
status, response = download_q(url, {}, {}, data=data)
return response.text
if __name__ == '__main__':
data = {
"source_note_id": "64bf4ea7000000000c035e48",
"image_formats": [
"jpg",
"webp",
"avif"
],
"extra": {
"need_body_topic": "1"
},
"xsec_source": "pc_feed",
"xsec_token": "ABSnHLvO9lnVSMKpZb-K1Cpn6d3xUC3z_mhRBvAbPGIe4="
}
data = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
res = get_xs("191452e29d5vt5m5noazsyty9z4z3z695yoiu1pn130000168225", "/api/sns/web/v1/feed",data, "1723443461839")
print(res)
Loading…
Cancel
Save