Files
hunyuan3dweb/README_REVERSE_ENGINEERING_CN.md
KawasakiAkasei ad3c86b8ba feat: full format compatibility adaptation for model downloads
- Add get_model_urls() and download_model() to api.py and api_complete.py
  supporting all 14 discovered urlResult format keys (glb, obj, pbr maps, etc.)
- Update generator.py to extract full urlResult dict instead of just modelUrl
- Add CLI subcommands: formats (list available formats) and download (fetch by key)
- Update reverse engineering docs with complete format key table and CLI examples
2026-05-27 11:59:48 +08:00

8.9 KiB
Raw Blame History

腾讯混元3D API 逆向工程文档

项目概述

本项目通过逆向工程破解了腾讯混元3Dhttps://3d.hunyuan.tencent.com/)的前端签名算法,实现了纯 Python HTTP 调用无需浏览器环境即可使用图生3D、查询配额等功能。


核心成果

1. 签名算法破解

算法位置: webpack Chunk 3057, Module 47436

签名流程:

1. nonce 生成: 16次 Math.random(),从 62 字符集 (A-Za-z0-9) 选取
2. timestamp: Math.floor(Date.now() / 1000)(秒级时间戳)
3. 密钥派生: 硬编码字节数组 → XOR → 循环左移 → 置换 → 截断
4. 签名: HMAC-SHA256(排序后的查询参数字符串, 派生密钥) → Hex

关键常量:

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]  # 置换表

派生密钥: Hf6d6KFB3D10字符

2. 签名范围(重要发现)

⚠️ 签名只包含 URL 查询参数,不包含请求体数据

浏览器实际请求:

POST /api/3d/creations/generations?timestamp=xxx&nonce=yyy&sign=zzz
Body: {"sceneType":"playGround3D-2.0",...}

签名计算:

# 只签查询参数timestamp + nonce
param_str = "nonce=yyy&timestamp=xxx"
sign = HMAC-SHA256(param_str, key="Hf6d6KFB3D")

3. 图片上传方式演进

方式一:浏览器自动化上传(原始方式)

通过 generator.py 在浏览器内完成上传,获取腾讯内部资源格式的 resourceId

方式二:纯 Python COS 直传(新方式)

通过 cos_upload.py 直接调用腾讯 COS API 上传文件,无需浏览器:

  1. 调用 /resource/genUploadInfo 获取临时 STS 凭证(encryptTmpSecretId / encryptTmpSecretKey / encryptToken
  2. 使用 COS V1 签名算法生成 Authorization 请求头
  3. 直接 PUT 上传到 *.cos.accelerate.myqcloud.com
  4. 获得 resourceUrl 供后续图生3D API 使用
from hunyuan3dweb.cos_upload import upload_image

resource_url = upload_image("/path/to/image.png")

文件说明

文件 说明
hunyuan3dweb/sign.py 签名算法纯 Python 实现
hunyuan3dweb/api.py 基础 API 客户端图生3D、文生3D
hunyuan3dweb/api_complete.py 完整 API 客户端(所有生成模式)
hunyuan3dweb/cos_upload.py 纯 Python COS 上传工具(无需浏览器)
hunyuan3dweb/config.py 用户配置路径管理cookie、profile
hunyuan3dweb/cli.py CLI 入口
hunyuan3dweb/browser/login.py 浏览器自动化登录工具
hunyuan3dweb/browser/generator.py 浏览器自动化图生3D
hunyuan3dweb/browser/sniffer.py API 请求嗅探工具

使用流程

hunyuan3dweb-login
# 按提示输入邮箱和验证码
# 登录状态自动保存到 ~/.config/hunyuan3dweb/profile
# Cookie 同时导出到 ~/.config/hunyuan3dweb/cookies.txt

登录状态检测逻辑(双重验证):

  • 检查当前页面 URL 是否包含 "login"
  • 检查页面上是否存在"登录"按钮
  • 只有 URL 不含 login 没有登录按钮时,才判定为已登录

2. 纯 Python 生成3D模型

from hunyuan3dweb import Hunyuan3DAPI

# 自动从 ~/.config/hunyuan3dweb/cookies.txt 加载 cookie
api = Hunyuan3DAPI()

# 查询配额
quota = api.get_quota_info()

# 文生3D
result = api.generate_text("一只可爱的熊猫", title="熊猫模型")

# 查询状态
status = api.get_generation_status(result["creationsId"])

3. 本地图片上传 + 图生3D纯 Python

from hunyuan3dweb.cos_upload import upload_image
from hunyuan3dweb import Hunyuan3DAPIComplete

# 上传本地图片到 COS
resource_url = upload_image("/path/to/image.png")

# 调用图生3D API
api = Hunyuan3DAPIComplete()
result = api.generate_from_image(resource_url, title="我的模型")

4. CLI 下载模型

# 列出某个创作所有可用的下载格式
hunyuan3dweb formats <creation_id>

# 下载指定格式(默认 glb
hunyuan3dweb download <creation_id> --format glb -o model.glb

API 端点

功能 方法 端点 签名范围
用户信息 GET /getuserinfo 查询参数
配额查询 POST /quotainfo 查询参数
生成3D POST /creations/generations 查询参数
查询状态 GET /creations/detail 查询参数
作品列表 POST /creations/list 查询参数
作品数量 POST /creations/count 查询参数
取消生成 POST /creations/cancel 查询参数
上传凭证 POST /resource/genUploadInfo 查询参数
资源审核 POST /resource/review 查询参数
创建分享 POST /share 查询参数
全局配置 GET /config 查询参数
动画模板 GET /workflow/action/templates 查询参数

模型下载格式

/creations/detail 返回的 urlResult 字段最多包含 14 种格式键:

键名 说明
glb 带 PBR 贴图的 GLB 二进制模型
obj OBJ 模型(通常打包在 zip 中)
mtl MTL 材质文件
obj_url 独立 OBJ 文件 URL
geometryGlb 纯几何 GLB无材质
textureGlb GLB 纹理包
textureObj OBJ 纹理包
image_url 预览图/缩略图
pbrImage PBR 综合贴图
pbrMetallicImage PBR 金属度贴图
pbrRoughnessImage PBR 粗糙度贴图
pbrNormalImage PBR 法线贴图
invisible_wall 不可见碰撞墙
air_wall 空气墙(碰撞体)

技术细节

签名算法实现

def derive_key(c: bytes) -> str:
    """从硬编码常量派生签名密钥"""
    # 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]
        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')

COS V1 签名(用于直传)

def _cos_v1_auth(method, pathname, query_params, headers, secret_id, secret_key, key_time):
    sign_key = hmac.new(secret_key.encode("utf-8"), key_time.encode("utf-8"), hashlib.sha1).hexdigest()

    query_str = _obj2str(query_params, True)
    headers_str = _obj2str(headers, True)
    format_string = f"{method}\n{pathname}\n{query_str}\n{headers_str}\n"

    format_string_sha1 = hashlib.sha1(format_string.encode("utf-8")).hexdigest()
    string_to_sign = f"sha1\n{key_time}\n{format_string_sha1}\n"

    signature = hmac.new(sign_key.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha1).hexdigest()

    return "&".join([
        "q-sign-algorithm=sha1",
        f"q-ak={secret_id}",
        f"q-sign-time={key_time}",
        f"q-key-time={key_time}",
        f"q-header-list={';'.join(sorted(headers.keys())).lower()}",
        f"q-url-param-list={';'.join(sorted(query_params.keys())).lower()}",
        f"q-signature={signature}",
    ])

请求签名

def sign(params: dict) -> dict:
    result = dict(params)
    result["timestamp"] = int(time.time())
    result["nonce"] = generate_nonce(16)
    
    # 排序并拼接JSON格式用于列表/字典)
    sorted_items = sort_params(result)
    param_str = join_params(sorted_items)
    
    # HMAC-SHA256
    key = derive_key(C)
    signature = hmac.new(
        key.encode('utf-8'),
        param_str.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    result["sign"] = signature
    return result

验证结果

测试项 浏览器签名 Python 签名 匹配
固定参数 2214e55a... 2214e55a...
配额查询 可用 可用
生成请求 可用 可用
状态查询 可用 可用
COS 直传 浏览器上传 Python 上传

限制与注意事项

  1. Cookie 有效期: 登录状态会过期,需要定期重新登录
  2. 配额限制: 每个账号有生成配额限制默认20次/天)
  3. 风控检测: 频繁调用可能触发风控
  4. 并发安全: 多个工具同时运行时,login.py 独享标准 profile 目录,sniffer.py / generator.py 会自动复制 profile 到临时目录启动,避免 Chromium SingletonLock 冲突

依赖

requests
cloakbrowser (用于登录和浏览器自动化)

法律声明

本项目仅供学习和研究使用。使用本代码需遵守腾讯混元3D的服务条款。请勿用于商业用途或大规模自动化调用。