| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- # -*- coding: utf-8 -*-
- # Author : Charley
- # Python : 3.10.8
- # Date : 2025/7/3 16:36
- import base64
- import re
- import urllib.parse
- from collections import OrderedDict
- from typing import List, Dict
- # ========================
- # Step 1: 模拟 JS 中的 $u 映射表
- # ========================
- def get_string(index):
- lu_list = [
- "dat", "htt", "ueO", "1669242vsNCrp", "hash", "2079476BHgCTx", "MzA", "2651740OMCnHx", "UKA", "development",
- "lYm", "linjieapp.com", "337909LsZqJc", "hanfu086.com", "some", "olv", "path", "10BvkOVt", "url",
- "563436jgaQxo",
- "0123456789abcdef", "ine", "hBd", "que", "fromCharCode", "1Y2", "T9f", "cke", "charAt", "l0b", "JlY", "Lio",
- "3NT", "UNH", "qiandao.com", "yxt", "red", "location", "WZh", "per", "462588MQJvSP", "sor", "HMA",
- "1647704YNAAwy",
- "length", "wM2", "charCodeAt", "972075UMDbSu", "20AarWCs", "def", "VjY", "iYW", "Q1M", "ZhY", "eUL", "rZB",
- "N2Y", "val", "161519TCUroc", "yNj", "isA", "8eV", "qiandao.cn", "pi.", "gRI", "JmZ", "concat", "ogr",
- "502096DJfVUj", "web", "0123456789ABCDEF", "nda", "DxB", "-Id", "136664rALOgK", "OTY", "6AoEOPC", "9JilTKK",
- "4977ANskNQ", "ZaC", "query", "host", "shift", "push", "equ", "str", "53FmkAxN", "spl", "341952KmUHty", "Tim",
- "forEach", "echoing.tech", "rra", "X-R", "fT6", "ipr", "184MSesMH", "test", "Pro", "NWR", "ues", "n/n",
- "735588HRQEfZ", "O1N", "1345113mxYnWn", "2460130PiswuR", "protocol", "production", "14037hrVEJe",
- "986934SQjjnp",
- "8bUOEvE", "HA2", "echo.tech", "localhost", "par", "1195uBtgZp", "res", "est", "//a", "3647kNKLxG",
- "12463moFXsz",
- "o.c", "28yNQNBZ", "abs", "28503cIknPz", "wXW", "min", "get", "req", "WM1", "354926IYrsjA", "44wJFZkm",
- "5868HCgLkL", "2274EbjwLA", "NTB", "qia", "9150940OsTyJa", "key", "ps:"
- ]
- return lu_list[index - 469]
- # ========================
- # Step 2: 实现 au() - UTF-8 编码字符串
- # ========================
- def au(s: str) -> bytes:
- result = []
- for c in s:
- if ord(c) <= 0x7F:
- result.append(ord(c))
- elif ord(c) <= 0x7FF:
- result.extend([
- 0xC0 | (ord(c) >> 6),
- 0x80 | (ord(c) & 0x3F)
- ])
- elif ord(c) <= 0xFFFF:
- result.extend([
- 0xE0 | (ord(c) >> 12),
- 0x80 | ((ord(c) >> 6) & 0x3F),
- 0x80 | (ord(c) & 0x3F)
- ])
- elif ord(c) <= 0x1FFFFF:
- result.extend([
- 0xF0 | (ord(c) >> 18),
- 0x80 | ((ord(c) >> 12) & 0x3F),
- 0x80 | ((ord(c) >> 6) & 0x3F),
- 0x80 | (ord(c) & 0x3F)
- ])
- return bytes(result)
- # ========================
- # Step 3: 实现 su() - 字符串转整型数组
- # ========================
- def su(data: bytes) -> List[int]:
- out = [0] * (len(data) // 4)
- for i in range(len(out)):
- for j in range(4):
- if i * 4 + j < len(data):
- out[i] |= data[i * 4 + j] << (24 - j * 8)
- return out
- # ========================
- # Step 4: 实现 pu() - 自定义哈希函数(类似 SHA-256)
- # ========================
- def cu(x, n):
- return (x >> n) | ((x & ((1 << n) - 1)) << (32 - n))
- def hu(a, b):
- lower = (a & 0xFFFF) + (b & 0xFFFF)
- upper = (a >> 16) + (b >> 16) + (lower >> 16)
- return (upper << 16) | (lower & 0xFFFF)
- # fu = [
- # 1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, -1841331548, -1424204075,
- # -670586216, 310598401, 607225278, 1426881987, 1925078388, -2132889090, -1680079193, -1046744716
- # ]
- fu = [1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, -1841331548, -1424204075, -670586216,
- 310598401, 607225278, 1426881987, 1925078388, -2132889090, -1680079193, -1046744716, -459576895, -272742522,
- 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, -1740746414, -1473132947, -1341970488,
- -1084653625, -958395405, -710438585, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291,
- 1695183700, 1986661051, -2117940946, -1838011259, -1564481375, -1474664885, -1035236496, -949202525, -778901479,
- -694614492, -200395387, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063,
- 1747873779, 1955562222, 2024104815, -2067236844, -1933114872, -1866530822, -1538233109, -1090935817, -965641998]
- # print(len(fu))
- def pu(data: List[int], length: int) -> List[int]:
- # 计算需要的最小 msg 长度(每项为 32 位整数)
- needed_blocks = (length + 64) // (32 * 4) + 1
- needed_length = needed_blocks * 16 # 每块 16 个整数
- msg = data[:]
- while len(msg) < needed_length:
- msg.append(0)
- # 填充长度信息
- msg[length >> 5] |= 0x80 << (24 - length % 32)
- msg[15 + ((length + 64) >> 9 << 4)] = length
- h = [1779033703, -1150833019, 1013904242, -1521486534, 1359893119, -1694144372, 528734635, 1541459225]
- for i in range(0, len(msg), 16):
- # 确保 w 至少有 64 个元素
- w = [0] * 64
- for x in range(min(len(msg) - i, 16)):
- w[x] = msg[i + x]
- for j in range(16, 64):
- s0 = cu(w[j - 15], 7) ^ cu(w[j - 15], 18) ^ (w[j - 15] >> 3)
- s1 = cu(w[j - 2], 17) ^ cu(w[j - 2], 19) ^ (w[j - 2] >> 10)
- w[j] = (w[j - 16] + s0 + w[j - 7] + s1) & 0xFFFFFFFF
- a, b, c, d, e, f, g, h_ = h
- for j in range(64):
- s1 = cu(e, 6) ^ cu(e, 11) ^ cu(e, 25)
- ch = (e & f) ^ (~e & g)
- temp = (s1 + ch) & 0xFFFFFFFF
- s0 = cu(a, 2) ^ cu(a, 13) ^ cu(a, 22)
- maj = (a & b) ^ (a & c) ^ (b & c)
- t2 = (s0 + maj) & 0xFFFFFFFF
- t1 = (h_ + temp + fu[j] + w[j]) & 0xFFFFFFFF
- h_ = g
- g = f
- f = e
- e = (d + t1) & 0xFFFFFFFF
- d = c
- c = b
- b = a
- a = (t1 + t2) & 0xFFFFFFFF
- h = [(h[k] + [a, b, c, d, e, f, g, h_][k]) & 0xFFFFFFFF for k in range(8)]
- return h
- # ========================
- # Step 5: 实现 iu() - Base64 编码
- # ========================
- def iu(data: bytes, url_safe: bool = False) -> str:
- encoded = base64.b64encode(data).decode()
- if url_safe:
- encoded = encoded.replace('+', '-').replace('/', '_').rstrip('=')
- return encoded
- # ========================
- # Step 6: 构建签名数据 w
- # ========================
- def parse_url(url: str) -> dict:
- pattern = re.compile(r'^((?:https?|ftp):)?//([^/?#]+)([^?#]*)(\?[^#]*)?(#.*)?$')
- match = pattern.match(url)
- keys = ['protocol', 'host', 'path', 'query', 'hash']
- return {k: (match.group(i + 1) if match and match.group(i + 1) else '') for i, k in enumerate(keys)}
- def parse_query(query: str) -> Dict[str, List[str]]:
- params = {}
- if query.startswith('?'):
- query = query[1:]
- for part in query.split('&'):
- if '=' in part:
- k, v = part.split('=', 1)
- k, v = urllib.parse.unquote(k), urllib.parse.unquote(v)
- params.setdefault(k, []).append(v)
- return params
- def sort_and_normalize(params: Dict[str, list]) -> Dict[str, list]:
- sorted_params = OrderedDict()
- for key in sorted(params.keys()):
- values = params[key]
- if isinstance(values, list) and len(values) == 1:
- sorted_params[key] = values[0]
- else:
- sorted_params[key] = values
- return sorted_params
- def build_sign_data(url: str) -> str:
- parsed = parse_url(url)
- query_str = parsed['query']
- params = parse_query(query_str)
- sorted_params = sort_and_normalize(params)
- # 拼接参数部分
- param_str = ''
- for k, v in sorted_params.items():
- if isinstance(v, list):
- for val in v:
- param_str += f"{urllib.parse.quote(k)}={urllib.parse.quote(val)}&"
- else:
- param_str += f"{urllib.parse.quote(k)}={urllib.parse.quote(v)}&"
- param_str = param_str.rstrip('&')
- # 拼接签名字符串
- sign_data = parsed['path'] + parsed['host'] + param_str
- return sign_data
- # ========================
- # Step 7: 最终签名函数
- # ========================
- def generate_sign_(m: str, url: str) -> str:
- w = build_sign_data(url)
- x = au(m)
- S = au(w)
- # Step 1: su(x)
- r = su(x)
- if len(r) > 16:
- r = pu(r, len(x))
- # 确保 r 长度至少为 16
- while len(r) < 16:
- r.append(0)
- # Step 2: 异或混淆
- i = [909522486 ^ r[a] for a in range(16)]
- o = [1549556828 ^ r[a] for a in range(16)]
- # Step 3: su(S)
- e_bytes = su(S)
- s = pu(i + e_bytes, 512 + 8 * len(S))
- final_hash = pu(o + s, 768)
- # Step 4: 转换为十六进制字符串
- hash_hex = ''.join(f"{(word & 0xFFFFFFFF):08x}" for word in final_hash)
- # Step 5: 将 hex 字符串转为 bytes 并进行 Base64 编码
- hash_bytes = hash_hex.encode('utf-8')
- return base64.b64encode(hash_bytes).decode('utf-8')
- import hmac
- from hashlib import sha256
- def generate_sign(m: str, url: str) -> str:
- w = build_sign_data(url)
- # 使用 HMAC-SHA256 计算签名
- key = m.encode('utf-8')
- msg = w.encode('utf-8')
- digest = hmac.new(key, msg, sha256).digest()
- # 转换为十六进制字符串
- hash_hex = digest.hex()
- # Base64 编码
- return base64.b64encode(hash_hex.encode()).decode()
- # ========================
- # Step 8: 示例使用
- # ========================
- if __name__ == '__main__':
- m = "8eVqsqhBdEKxeULQ1M72yrZB5HET9fx0"
- # url = 'https://api.qiandao.com/shelf-web/list-home-recommender?limit=10&offset=280&cmd=b2c_homepage_feed&project=channel&name=manghe&type%5B%5D=BLIND_BOX_MACHINE&type%5B%5D=KUJI&type%5B%5D=NEW_LUCKY_BAG&type%5B%5D=B2C&typeIds%5B%5D=15&channelId=397228442736842784&screenName=%E6%8E%A8%E8%8D%90'
- # url = 'https://api.qiandao.com/shelf-web/list-home-recommender?limit=10&offset=300&cmd=b2c_homepage_feed&project=channel&name=manghe&type%5B%5D=BLIND_BOX_MACHINE&type%5B%5D=KUJI&type%5B%5D=NEW_LUCKY_BAG&type%5B%5D=B2C&typeIds%5B%5D=15&channelId=397228442736842784&screenName=%E6%8E%A8%E8%8D%90'
- url = "/shelf-web/list-home-recommendertypeIds%5B%5D=15&type%5B%5D=NEW_LUCKY_BAG&type%5B%5D=KUJI&type%5B%5D=BLIND_BOX_MACHINE&type%5B%5D=B2C&screenName=%E6%8E%A8%E8%8D%90&project=channel&offset=310&name=manghe&limit=10&cmd=b2c_homepage_feed&channelId=3972284427368427841751530694089"
- t ="https://api.qiandao.com/shelf-web/list-home-recommender?limit=10&offset=310&cmd=b2c_homepage_feed&project=channel&name=manghe&type%5B%5D=BLIND_BOX_MACHINE&type%5B%5D=KUJI&type%5B%5D=NEW_LUCKY_BAG&type%5B%5D=B2C&typeIds%5B%5D=15&channelId=397228442736842784&screenName=%E6%8E%A8%E8%8D%90"
- timestr = '1751530647285'
- signature = generate_sign(m, url)
- print("Signature:", signature)
- # 原始签名
- # OTMxYmRiMDhhYjYxM2IyMDBiN2U0ZGFlNDUzNGU4YzhiZWYxOTk4MjNjYTVkODg4OWYzZDJkOTUxMjU2Zjc2Yw==
- # YzNjOTNiY2NlN2UzYTcwNmJlNzQ0YTJiOTJjMTkzYTAzNmRiZGQyNjZiNGZjMzM5NDNiMGUxNjQ4MDQwYzAzYw==
- "OTYwYjQ1NWI2ZmYyMGNjNzQ1NDJhNmFhY2I0OGJlMTZjZGM3NzVjZjJhZTRiMzIxZTQxYTg0NTZmZjVkODgwZg=="
|