#!/usr/bin/env python3 """ 腾讯混元3D签名算法实现 从 webpack 模块逆向提取 """ import hmac import hashlib import time import random import string # 密钥派生相关常量(从 JS 提取) C = bytes([122, 59, 92, 165, 30, 79, 166, 139, 142, 129, 139, 89, 219, 131, 101, 204]) D = bytes([122, 59, 92, 45, 30, 79, 106, 139, 156, 13, 46, 63, 74, 91, 108, 125]) U = [3, 5, 2, 7, 1, 4, 6, 2, 5, 3, 1, 4, 2, 6, 3, 5] M = [14, 11, 13, 9, 15, 10, 12, 8, 6, 3, 5, 1, 7, 2, 4, 0] def derive_key(c: bytes) -> str: """密钥派生函数 - 从硬编码常量派生签名密钥""" if len(c) != 16: raise ValueError("输入必须是一个16字节的数组") # 步骤1: XOR with D t = bytearray(16) for i in range(16): t[i] = c[i] ^ D[i] # 步骤2: 循环左移 o = bytearray(16) for i in range(16): n = U[i] # Python 的移位和 JS 不同,需要模拟 8 位无符号整数 val = t[i] o[i] = (val << n | val >> (8 - n)) & 0xFF # 步骤3: 置换 n = bytearray(16) for i in range(16): n[i] = o[M[i]] # 步骤4: 找到第一个0字节,截断 try: r = n.index(0) except ValueError: r = 16 return n[:r].decode('utf-8') def generate_nonce(length: int = 16) -> str: """生成随机 nonce""" chars = string.ascii_letters + string.digits # 62字符 return ''.join(random.choice(chars) for _ in range(length)) def get_timestamp() -> int: """获取当前时间戳(秒)""" return int(time.time()) def sort_params(params: dict) -> list: """排序参数,过滤空值""" import json items = [] for k, v in params.items(): if v is not None and v != "": # 对列表和字典使用 JSON 格式 if isinstance(v, (list, dict, bool)): items.append((k, json.dumps(v, separators=(',', ':'), ensure_ascii=False))) else: items.append((k, str(v))) return sorted(items, key=lambda x: x[0]) def join_params(items: list) -> str: """拼接参数为查询字符串""" return '&'.join(f"{k}={v}" for k, v in items) def sign(params: dict, nonce_length: int = 16, timestamp_field: str = "timestamp", nonce_field: str = "nonce", sign_field: str = "sign") -> dict: """ 主签名函数 Args: params: 请求参数 nonce_length: nonce 长度 timestamp_field: 时间戳字段名 nonce_field: nonce 字段名 sign_field: 签名字段名 Returns: 包含签名的新参数字典 """ result = dict(params) result[timestamp_field] = get_timestamp() result[nonce_field] = generate_nonce(nonce_length) # 排序并拼接 sorted_items = sort_params(result) param_str = join_params(sorted_items) # 派生密钥 key = derive_key(C) # HMAC-SHA256 签名,然后转为 Hex signature = hmac.new( key.encode('utf-8'), param_str.encode('utf-8'), hashlib.sha256 ).hexdigest() result[sign_field] = signature return result def sign_with_custom_nonce(params: dict, timestamp: int, nonce: str, timestamp_field: str = "timestamp", nonce_field: str = "nonce", sign_field: str = "sign") -> dict: """使用指定的 timestamp 和 nonce 生成签名(用于验证)""" result = dict(params) result[timestamp_field] = timestamp result[nonce_field] = nonce sorted_items = sort_params(result) param_str = join_params(sorted_items) key = derive_key(C) signature = hmac.new( key.encode('utf-8'), param_str.encode('utf-8'), hashlib.sha256 ).hexdigest() result[sign_field] = signature return result if __name__ == "__main__": # 测试密钥派生 key = derive_key(C) print(f"派生密钥: {key}") print(f"密钥长度: {len(key)}") # 测试签名 test_params = { "sceneType": "playGround3D-2.0", "count": 1, "modelType": "image2ModelV3.1" } signed = sign(test_params) print(f"\n测试签名:") for k, v in sorted(signed.items()): print(f" {k}: {v}")