# -*- 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=="