hmc256_demo.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. # -*- coding: utf-8 -*-
  2. # Author : Charley
  3. # Python : 3.10.8
  4. # Date : 2025/7/3 16:36
  5. import base64
  6. import re
  7. import urllib.parse
  8. from collections import OrderedDict
  9. from typing import List, Dict
  10. # ========================
  11. # Step 1: 模拟 JS 中的 $u 映射表
  12. # ========================
  13. def get_string(index):
  14. lu_list = [
  15. "dat", "htt", "ueO", "1669242vsNCrp", "hash", "2079476BHgCTx", "MzA", "2651740OMCnHx", "UKA", "development",
  16. "lYm", "linjieapp.com", "337909LsZqJc", "hanfu086.com", "some", "olv", "path", "10BvkOVt", "url",
  17. "563436jgaQxo",
  18. "0123456789abcdef", "ine", "hBd", "que", "fromCharCode", "1Y2", "T9f", "cke", "charAt", "l0b", "JlY", "Lio",
  19. "3NT", "UNH", "qiandao.com", "yxt", "red", "location", "WZh", "per", "462588MQJvSP", "sor", "HMA",
  20. "1647704YNAAwy",
  21. "length", "wM2", "charCodeAt", "972075UMDbSu", "20AarWCs", "def", "VjY", "iYW", "Q1M", "ZhY", "eUL", "rZB",
  22. "N2Y", "val", "161519TCUroc", "yNj", "isA", "8eV", "qiandao.cn", "pi.", "gRI", "JmZ", "concat", "ogr",
  23. "502096DJfVUj", "web", "0123456789ABCDEF", "nda", "DxB", "-Id", "136664rALOgK", "OTY", "6AoEOPC", "9JilTKK",
  24. "4977ANskNQ", "ZaC", "query", "host", "shift", "push", "equ", "str", "53FmkAxN", "spl", "341952KmUHty", "Tim",
  25. "forEach", "echoing.tech", "rra", "X-R", "fT6", "ipr", "184MSesMH", "test", "Pro", "NWR", "ues", "n/n",
  26. "735588HRQEfZ", "O1N", "1345113mxYnWn", "2460130PiswuR", "protocol", "production", "14037hrVEJe",
  27. "986934SQjjnp",
  28. "8bUOEvE", "HA2", "echo.tech", "localhost", "par", "1195uBtgZp", "res", "est", "//a", "3647kNKLxG",
  29. "12463moFXsz",
  30. "o.c", "28yNQNBZ", "abs", "28503cIknPz", "wXW", "min", "get", "req", "WM1", "354926IYrsjA", "44wJFZkm",
  31. "5868HCgLkL", "2274EbjwLA", "NTB", "qia", "9150940OsTyJa", "key", "ps:"
  32. ]
  33. return lu_list[index - 469]
  34. # ========================
  35. # Step 2: 实现 au() - UTF-8 编码字符串
  36. # ========================
  37. def au(s: str) -> bytes:
  38. result = []
  39. for c in s:
  40. if ord(c) <= 0x7F:
  41. result.append(ord(c))
  42. elif ord(c) <= 0x7FF:
  43. result.extend([
  44. 0xC0 | (ord(c) >> 6),
  45. 0x80 | (ord(c) & 0x3F)
  46. ])
  47. elif ord(c) <= 0xFFFF:
  48. result.extend([
  49. 0xE0 | (ord(c) >> 12),
  50. 0x80 | ((ord(c) >> 6) & 0x3F),
  51. 0x80 | (ord(c) & 0x3F)
  52. ])
  53. elif ord(c) <= 0x1FFFFF:
  54. result.extend([
  55. 0xF0 | (ord(c) >> 18),
  56. 0x80 | ((ord(c) >> 12) & 0x3F),
  57. 0x80 | ((ord(c) >> 6) & 0x3F),
  58. 0x80 | (ord(c) & 0x3F)
  59. ])
  60. return bytes(result)
  61. # ========================
  62. # Step 3: 实现 su() - 字符串转整型数组
  63. # ========================
  64. def su(data: bytes) -> List[int]:
  65. out = [0] * (len(data) // 4)
  66. for i in range(len(out)):
  67. for j in range(4):
  68. if i * 4 + j < len(data):
  69. out[i] |= data[i * 4 + j] << (24 - j * 8)
  70. return out
  71. # ========================
  72. # Step 4: 实现 pu() - 自定义哈希函数(类似 SHA-256)
  73. # ========================
  74. def cu(x, n):
  75. return (x >> n) | ((x & ((1 << n) - 1)) << (32 - n))
  76. def hu(a, b):
  77. lower = (a & 0xFFFF) + (b & 0xFFFF)
  78. upper = (a >> 16) + (b >> 16) + (lower >> 16)
  79. return (upper << 16) | (lower & 0xFFFF)
  80. # fu = [
  81. # 1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, -1841331548, -1424204075,
  82. # -670586216, 310598401, 607225278, 1426881987, 1925078388, -2132889090, -1680079193, -1046744716
  83. # ]
  84. fu = [1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, -1841331548, -1424204075, -670586216,
  85. 310598401, 607225278, 1426881987, 1925078388, -2132889090, -1680079193, -1046744716, -459576895, -272742522,
  86. 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, -1740746414, -1473132947, -1341970488,
  87. -1084653625, -958395405, -710438585, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291,
  88. 1695183700, 1986661051, -2117940946, -1838011259, -1564481375, -1474664885, -1035236496, -949202525, -778901479,
  89. -694614492, -200395387, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063,
  90. 1747873779, 1955562222, 2024104815, -2067236844, -1933114872, -1866530822, -1538233109, -1090935817, -965641998]
  91. # print(len(fu))
  92. def pu(data: List[int], length: int) -> List[int]:
  93. # 计算需要的最小 msg 长度(每项为 32 位整数)
  94. needed_blocks = (length + 64) // (32 * 4) + 1
  95. needed_length = needed_blocks * 16 # 每块 16 个整数
  96. msg = data[:]
  97. while len(msg) < needed_length:
  98. msg.append(0)
  99. # 填充长度信息
  100. msg[length >> 5] |= 0x80 << (24 - length % 32)
  101. msg[15 + ((length + 64) >> 9 << 4)] = length
  102. h = [1779033703, -1150833019, 1013904242, -1521486534, 1359893119, -1694144372, 528734635, 1541459225]
  103. for i in range(0, len(msg), 16):
  104. # 确保 w 至少有 64 个元素
  105. w = [0] * 64
  106. for x in range(min(len(msg) - i, 16)):
  107. w[x] = msg[i + x]
  108. for j in range(16, 64):
  109. s0 = cu(w[j - 15], 7) ^ cu(w[j - 15], 18) ^ (w[j - 15] >> 3)
  110. s1 = cu(w[j - 2], 17) ^ cu(w[j - 2], 19) ^ (w[j - 2] >> 10)
  111. w[j] = (w[j - 16] + s0 + w[j - 7] + s1) & 0xFFFFFFFF
  112. a, b, c, d, e, f, g, h_ = h
  113. for j in range(64):
  114. s1 = cu(e, 6) ^ cu(e, 11) ^ cu(e, 25)
  115. ch = (e & f) ^ (~e & g)
  116. temp = (s1 + ch) & 0xFFFFFFFF
  117. s0 = cu(a, 2) ^ cu(a, 13) ^ cu(a, 22)
  118. maj = (a & b) ^ (a & c) ^ (b & c)
  119. t2 = (s0 + maj) & 0xFFFFFFFF
  120. t1 = (h_ + temp + fu[j] + w[j]) & 0xFFFFFFFF
  121. h_ = g
  122. g = f
  123. f = e
  124. e = (d + t1) & 0xFFFFFFFF
  125. d = c
  126. c = b
  127. b = a
  128. a = (t1 + t2) & 0xFFFFFFFF
  129. h = [(h[k] + [a, b, c, d, e, f, g, h_][k]) & 0xFFFFFFFF for k in range(8)]
  130. return h
  131. # ========================
  132. # Step 5: 实现 iu() - Base64 编码
  133. # ========================
  134. def iu(data: bytes, url_safe: bool = False) -> str:
  135. encoded = base64.b64encode(data).decode()
  136. if url_safe:
  137. encoded = encoded.replace('+', '-').replace('/', '_').rstrip('=')
  138. return encoded
  139. # ========================
  140. # Step 6: 构建签名数据 w
  141. # ========================
  142. def parse_url(url: str) -> dict:
  143. pattern = re.compile(r'^((?:https?|ftp):)?//([^/?#]+)([^?#]*)(\?[^#]*)?(#.*)?$')
  144. match = pattern.match(url)
  145. keys = ['protocol', 'host', 'path', 'query', 'hash']
  146. return {k: (match.group(i + 1) if match and match.group(i + 1) else '') for i, k in enumerate(keys)}
  147. def parse_query(query: str) -> Dict[str, List[str]]:
  148. params = {}
  149. if query.startswith('?'):
  150. query = query[1:]
  151. for part in query.split('&'):
  152. if '=' in part:
  153. k, v = part.split('=', 1)
  154. k, v = urllib.parse.unquote(k), urllib.parse.unquote(v)
  155. params.setdefault(k, []).append(v)
  156. return params
  157. def sort_and_normalize(params: Dict[str, list]) -> Dict[str, list]:
  158. sorted_params = OrderedDict()
  159. for key in sorted(params.keys()):
  160. values = params[key]
  161. if isinstance(values, list) and len(values) == 1:
  162. sorted_params[key] = values[0]
  163. else:
  164. sorted_params[key] = values
  165. return sorted_params
  166. def build_sign_data(url: str) -> str:
  167. parsed = parse_url(url)
  168. query_str = parsed['query']
  169. params = parse_query(query_str)
  170. sorted_params = sort_and_normalize(params)
  171. # 拼接参数部分
  172. param_str = ''
  173. for k, v in sorted_params.items():
  174. if isinstance(v, list):
  175. for val in v:
  176. param_str += f"{urllib.parse.quote(k)}={urllib.parse.quote(val)}&"
  177. else:
  178. param_str += f"{urllib.parse.quote(k)}={urllib.parse.quote(v)}&"
  179. param_str = param_str.rstrip('&')
  180. # 拼接签名字符串
  181. sign_data = parsed['path'] + parsed['host'] + param_str
  182. return sign_data
  183. # ========================
  184. # Step 7: 最终签名函数
  185. # ========================
  186. def generate_sign_(m: str, url: str) -> str:
  187. w = build_sign_data(url)
  188. x = au(m)
  189. S = au(w)
  190. # Step 1: su(x)
  191. r = su(x)
  192. if len(r) > 16:
  193. r = pu(r, len(x))
  194. # 确保 r 长度至少为 16
  195. while len(r) < 16:
  196. r.append(0)
  197. # Step 2: 异或混淆
  198. i = [909522486 ^ r[a] for a in range(16)]
  199. o = [1549556828 ^ r[a] for a in range(16)]
  200. # Step 3: su(S)
  201. e_bytes = su(S)
  202. s = pu(i + e_bytes, 512 + 8 * len(S))
  203. final_hash = pu(o + s, 768)
  204. # Step 4: 转换为十六进制字符串
  205. hash_hex = ''.join(f"{(word & 0xFFFFFFFF):08x}" for word in final_hash)
  206. # Step 5: 将 hex 字符串转为 bytes 并进行 Base64 编码
  207. hash_bytes = hash_hex.encode('utf-8')
  208. return base64.b64encode(hash_bytes).decode('utf-8')
  209. import hmac
  210. from hashlib import sha256
  211. def generate_sign(m: str, url: str) -> str:
  212. w = build_sign_data(url)
  213. # 使用 HMAC-SHA256 计算签名
  214. key = m.encode('utf-8')
  215. msg = w.encode('utf-8')
  216. digest = hmac.new(key, msg, sha256).digest()
  217. # 转换为十六进制字符串
  218. hash_hex = digest.hex()
  219. # Base64 编码
  220. return base64.b64encode(hash_hex.encode()).decode()
  221. # ========================
  222. # Step 8: 示例使用
  223. # ========================
  224. if __name__ == '__main__':
  225. m = "8eVqsqhBdEKxeULQ1M72yrZB5HET9fx0"
  226. # 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'
  227. # 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'
  228. 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"
  229. 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"
  230. timestr = '1751530647285'
  231. signature = generate_sign(m, url)
  232. print("Signature:", signature)
  233. # 原始签名
  234. # OTMxYmRiMDhhYjYxM2IyMDBiN2U0ZGFlNDUzNGU4YzhiZWYxOTk4MjNjYTVkODg4OWYzZDJkOTUxMjU2Zjc2Yw==
  235. # YzNjOTNiY2NlN2UzYTcwNmJlNzQ0YTJiOTJjMTkzYTAzNmRiZGQyNjZiNGZjMzM5NDNiMGUxNjQ4MDQwYzAzYw==
  236. "OTYwYjQ1NWI2ZmYyMGNjNzQ1NDJhNmFhY2I0OGJlMTZjZGM3NzVjZjJhZTRiMzIxZTQxYTg0NTZmZjVkODgwZg=="