- Add cos_upload.py pure-Python COS upload method (no browser needed) - Expand API endpoint table to include all discovered endpoints - Document dual-verification login detection (URL + DOM) - Add COS V1 signing algorithm explanation - Update file reference table to match current codebase - Remove obsolete file references (get_resource_id.py, etc.) - Sync Chinese and English versions Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8.4 KiB
Tencent Hunyuan 3D API Reverse Engineering Document
Project Overview
This project reverse-engineers the frontend signing algorithm of Tencent Hunyuan 3D (https://3d.hunyuan.tencent.com/), enabling pure Python HTTP calls without a browser environment for features like image-to-3D generation and quota queries.
Core Achievements
1. Signing Algorithm Cracked
Algorithm Location: webpack Chunk 3057, Module 47436
Signing Flow:
1. nonce generation: 16 iterations of Math.random(), selecting from 62-char set (A-Za-z0-9)
2. timestamp: Math.floor(Date.now() / 1000) (second-level timestamp)
3. key derivation: hardcoded byte array → XOR → circular left shift → permutation → truncation
4. signature: HMAC-SHA256(sorted query param string, derived key) → Hex
Key Constants:
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] # left shift bits
M = [14, 11, 13, 9, 15, 10, 12, 8, 6, 3, 5, 1, 7, 2, 4, 0] # permutation table
Derived Key: Hf6d6KFB3D (10 characters)
2. Signing Scope (Important Finding)
⚠️ The signature only covers URL query parameters, NOT the request body
Actual browser request:
POST /api/3d/creations/generations?timestamp=xxx&nonce=yyy&sign=zzz
Body: {"sceneType":"playGround3D-2.0",...}
Signature computation:
# Only sign query params (timestamp + nonce)
param_str = "nonce=yyy×tamp=xxx"
sign = HMAC-SHA256(param_str, key="Hf6d6KFB3D")
3. Image Upload Evolution
Method 1: Browser Automation Upload (Original)
Upload via generator.py inside the browser to obtain a Tencent internal resourceId.
Method 2: Pure Python COS Direct Upload (New)
Upload directly via cos_upload.py without a browser:
- Call
/resource/genUploadInfoto obtain temporary STS credentials (encryptTmpSecretId/encryptTmpSecretKey/encryptToken) - Generate the
Authorizationheader using the COS V1 signing algorithm PUTdirectly to*.cos.accelerate.myqcloud.com- Obtain
resourceUrlfor subsequent image-to-3D API calls
from hunyuan3dweb.cos_upload import upload_image
resource_url = upload_image("/path/to/image.png")
File Reference
| File | Description |
|---|---|
hunyuan3dweb/sign.py |
Signing algorithm in pure Python |
hunyuan3dweb/api.py |
Basic API client (image2model, text2model) |
hunyuan3dweb/api_complete.py |
Full API client (all generation modes) |
hunyuan3dweb/cos_upload.py |
Pure Python COS upload helper (no browser needed) |
hunyuan3dweb/config.py |
User config path management (cookies, profile) |
hunyuan3dweb/cli.py |
CLI entry point |
hunyuan3dweb/browser/login.py |
Browser automation login tool |
hunyuan3dweb/browser/generator.py |
Browser automation image-to-3D |
hunyuan3dweb/browser/sniffer.py |
API request sniffer |
Usage Flow
1. Login to Obtain Cookie
hunyuan3dweb-login
# Follow prompts to enter email and verification code
# Login state is automatically saved to ~/.config/hunyuan3dweb/profile
# Cookies are also exported to ~/.config/hunyuan3dweb/cookies.txt
Login State Detection Logic (dual verification):
- Check if the current page URL contains
"login" - Check if a "Login" button exists on the page
- Only considered logged in when URL does NOT contain login AND no login button is present
2. Pure Python 3D Generation
from hunyuan3dweb import Hunyuan3DAPI
# Automatically load cookie from ~/.config/hunyuan3dweb/cookies.txt
api = Hunyuan3DAPI()
# Check quota
quota = api.get_quota_info()
# Text-to-3D
result = api.generate_text("a cute panda", title="Panda Model")
# Query status
status = api.get_generation_status(result["creationsId"])
3. Local Image Upload + Image-to-3D (Pure Python)
from hunyuan3dweb.cos_upload import upload_image
from hunyuan3dweb import Hunyuan3DAPIComplete
# Upload local image to COS
resource_url = upload_image("/path/to/image.png")
# Call image-to-3D API
api = Hunyuan3DAPIComplete()
result = api.generate_from_image(resource_url, title="My Model")
API Endpoints
| Feature | Method | Endpoint | Signature Scope |
|---|---|---|---|
| User info | GET | /getuserinfo |
Query params |
| Quota query | POST | /quotainfo |
Query params |
| Generate 3D | POST | /creations/generations |
Query params |
| Query status | GET | /creations/detail |
Query params |
| Creation list | POST | /creations/list |
Query params |
| Creation count | POST | /creations/count |
Query params |
| Cancel generation | POST | /creations/cancel |
Query params |
| Upload credentials | POST | /resource/genUploadInfo |
Query params |
| Resource review | POST | /resource/review |
Query params |
| Create share | POST | /share |
Query params |
| Global config | GET | /config |
Query params |
| Animation templates | GET | /workflow/action/templates |
Query params |
Technical Details
Signing Algorithm Implementation
def derive_key(c: bytes) -> str:
"""Derive signing key from hardcoded constants"""
# 1. XOR with D
t = bytearray(16)
for i in range(16):
t[i] = c[i] ^ D[i]
# 2. Circular left shift
o = bytearray(16)
for i in range(16):
n = U[i]
val = t[i]
o[i] = (val << n | val >> (8 - n)) & 0xFF
# 3. Permutation
n = bytearray(16)
for i in range(16):
n[i] = o[M[i]]
# 4. Truncate (find first zero byte)
try:
r = n.index(0)
except ValueError:
r = 16
return n[:r].decode('utf-8')
COS V1 Signature (for Direct Upload)
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}",
])
Request Signing
def sign(params: dict) -> dict:
result = dict(params)
result["timestamp"] = int(time.time())
result["nonce"] = generate_nonce(16)
# Sort and join (JSON format for lists/dicts)
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
Verification Results
| Test Item | Browser Signature | Python Signature | Match |
|---|---|---|---|
| Fixed params | 2214e55a... |
2214e55a... |
✅ |
| Quota query | Works | Works | ✅ |
| Generate request | Works | Works | ✅ |
| Status query | Works | Works | ✅ |
| COS direct upload | Browser upload | Python upload | ✅ |
Limitations and Notes
- Cookie Expiration: Login sessions expire and need periodic re-login
- Quota Limit: Each account has a generation quota limit (default 20/day)
- Rate Limiting: Frequent calls may trigger anti-bot measures
- Concurrency Safety: When multiple tools run simultaneously,
login.pyexclusively uses the standard profile directory, whilesniffer.py/generator.pyautomatically copy the profile to a temporary directory to avoid ChromiumSingletonLockconflicts
Dependencies
requests
cloakbrowser (for login and browser automation)
Legal Notice
This project is for educational and research purposes only. Use of this code is subject to Tencent Hunyuan 3D's Terms of Service. Do not use for commercial purposes or large-scale automated calling.