- 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>
273 lines
8.4 KiB
Markdown
273 lines
8.4 KiB
Markdown
# 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**:
|
|
```python
|
|
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:
|
|
```python
|
|
# 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:
|
|
1. Call `/resource/genUploadInfo` to obtain temporary STS credentials (`encryptTmpSecretId` / `encryptTmpSecretKey` / `encryptToken`)
|
|
2. Generate the `Authorization` header using the COS V1 signing algorithm
|
|
3. `PUT` directly to `*.cos.accelerate.myqcloud.com`
|
|
4. Obtain `resourceUrl` for subsequent image-to-3D API calls
|
|
|
|
```python
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```python
|
|
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)
|
|
|
|
```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
|
|
|
|
```python
|
|
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)
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
1. **Cookie Expiration**: Login sessions expire and need periodic re-login
|
|
2. **Quota Limit**: Each account has a generation quota limit (default 20/day)
|
|
3. **Rate Limiting**: Frequent calls may trigger anti-bot measures
|
|
4. **Concurrency Safety**: When multiple tools run simultaneously, `login.py` exclusively uses the standard profile directory, while `sniffer.py` / `generator.py` automatically copy the profile to a temporary directory to avoid Chromium `SingletonLock` conflicts
|
|
|
|
---
|
|
|
|
## 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.
|