luckfun233 commited on
Commit ·
cf4c38d
1
Parent(s): 3678d9b
feat: 新增数美SDK逆向研究报告及Web端接口实现文档
Browse files- docs/shumei-device-id-research.md +489 -0
- docs/web-endpoint-upgrade.md +395 -0
docs/shumei-device-id-research.md
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 数美(Shumei)SDK 逆向研究报告
|
| 2 |
+
|
| 3 |
+
> 研究目标:DeepSeek Web端登录所需的 device_id 生成算法
|
| 4 |
+
> 研究时间:2026-06-09
|
| 5 |
+
> 项目位置:d:\ds2api
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 一、SDK 基本信息
|
| 10 |
+
|
| 11 |
+
| 项目 | 值 |
|
| 12 |
+
|------|-----|
|
| 13 |
+
| SDK 名称 | 数美科技(Shumei)DeviceFingerprint Web SDK |
|
| 14 |
+
| 版本 | 3.0.0 / subVersion 1.0.0 |
|
| 15 |
+
| 文件大小 | 302KB(webpack 打包) |
|
| 16 |
+
| SDK 入口 URL | `https://cdn.deepseek.com/static/chat/fp-1.min.js` |
|
| 17 |
+
| 验证码 SDK URL | `https://castatic.fengkongcloud.cn/pr/v1.0.4/smcp.min.js` |
|
| 18 |
+
| API 端点 | `https://fp-it-acc.portal101.cn/deviceprofile/v4` |
|
| 19 |
+
| 配置方式 | `window._smConf`(由页面注入) |
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## 二、关键配置参数(从 DeepSeek 页面提取)
|
| 24 |
+
|
| 25 |
+
```
|
| 26 |
+
RSA 公钥: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDetfEgYD4aE1ZjmWJ6/jnPurhzI+yeRoJHWrnNtQMte3stQ4VjG3yu21FuN75E6cDpA9KtDXwcB2M/FiGUAe3G0rNotbWI8+SjZfUbW/OILFTzY0uaeEkmVGW5WyJ6weQbbr1xTCPa2OO3YIMeZljWUYHG5h21WAm/PATg8im8cQIDAQAB
|
| 27 |
+
|
| 28 |
+
Organization: P9usCUBauxft8eAmUXaZ
|
| 29 |
+
|
| 30 |
+
AppId: default
|
| 31 |
+
|
| 32 |
+
API Host: fp-it-acc.portal101.cn
|
| 33 |
+
|
| 34 |
+
appId (生产环境): wx932d4fdaf46d5611
|
| 35 |
+
appId (测试环境): wx58f1d4bc57bc28fd / wxd4679ecc940ea6be
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## 三、device_id 生成完整流程
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
┌─────────────────────────────────────────────────────┐
|
| 44 |
+
│ 1. 初始化 │
|
| 45 |
+
│ 读取 window._smConf (organization, appId, publicKey)│
|
| 46 |
+
└──────────────────────┬──────────────────────────────┘
|
| 47 |
+
▼
|
| 48 |
+
┌─────────────────────────────────────────────────────┐
|
| 49 |
+
│ 2. 生成 UID │
|
| 50 |
+
│ UUID v4 格式: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx │
|
| 51 |
+
└──────────────────────┬──────────────────────────────┘
|
| 52 |
+
▼
|
| 53 |
+
┌─────────────────────────────────────────────────────┐
|
| 54 |
+
│ 3. 计算 priId │
|
| 55 |
+
│ priId = md5(uid)[:16] → AES加密密钥 │
|
| 56 |
+
└──────────────────────┬──────────────────────────────┘
|
| 57 |
+
▼
|
| 58 |
+
┌─────────────────────────────────────────────────────┐
|
| 59 |
+
│ 4. 计算 ep │
|
| 60 |
+
│ ep = RSA公钥加密(uid) → PKCS#1 v1.5 填充 │
|
| 61 |
+
│ 输出: hex 字符串 │
|
| 62 |
+
└──────────────────────┬──────────────────────────────┘
|
| 63 |
+
▼
|
| 64 |
+
┌─────────────────────────────────────────────────────┐
|
| 65 |
+
│ 5. 收集指纹数据 (23项浏览器环境信息) │
|
| 66 |
+
│ ua, canvas, timezone, platform, plugins, ... │
|
| 67 |
+
└──────────────────────┬──────────────────────────────┘
|
| 68 |
+
▼
|
| 69 |
+
┌─────────────────────────────────────────────────────┐
|
| 70 |
+
│ 6. DES字段混淆 │
|
| 71 |
+
│ 按 ConfusionInfo 配置逐字段: │
|
| 72 |
+
│ DES-ECB(key, value) → Base64 → 重命名字段名 │
|
| 73 |
+
└──────────────────────┬──────────────────────────────┘
|
| 74 |
+
▼
|
| 75 |
+
┌─────────────────────────────────────────────────────┐
|
| 76 |
+
│ 7. 计算 tn 完整性校验 │
|
| 77 |
+
│ tn = md5(sorted_key_value_string(data)) │
|
| 78 |
+
└──────────────────────┬──────────────────────────────┘
|
| 79 |
+
▼
|
| 80 |
+
┌─────────────────────────────────────────────────────┐
|
| 81 |
+
│ 8. JSON → Gzip 压缩 │
|
| 82 |
+
│ 使�� pako 库压缩 │
|
| 83 |
+
└──────────────────────┬──────────────────────────────┘
|
| 84 |
+
▼
|
| 85 |
+
┌─────────────────────────────────────────────────────┐
|
| 86 |
+
│ 9. AES-CBC 加密 │
|
| 87 |
+
│ key = priId (UTF-8) │
|
| 88 |
+
│ IV = "0102030405060708" │
|
| 89 |
+
│ Mode = CBC, Padding = ZeroPadding │
|
| 90 |
+
│ 输出: hex 字符串 │
|
| 91 |
+
└──────────────────────┬──────────────────────────────┘
|
| 92 |
+
▼
|
| 93 |
+
┌─────────────────────────────────────────────────────┐
|
| 94 |
+
│ 10. POST 到 API │
|
| 95 |
+
│ { │
|
| 96 |
+
│ "appId": "...", │
|
| 97 |
+
│ "organization": "...", │
|
| 98 |
+
│ "ep": "<RSA加密的uid hex>", │
|
| 99 |
+
│ "data": "<AES加密后的hex>", │
|
| 100 |
+
│ "os": "web", │
|
| 101 |
+
│ "encode": 5, │
|
| 102 |
+
│ "compress": 2 │
|
| 103 |
+
│ } │
|
| 104 |
+
└──────────────────────┬──────────────────────────────┘
|
| 105 |
+
▼
|
| 106 |
+
┌─────────────────────────────────────────────────────┐
|
| 107 |
+
│ 11. 解析响应 │
|
| 108 |
+
│ code=1100 → 成功, 取 detail.deviceId │
|
| 109 |
+
│ code=1902 → 需要重试 │
|
| 110 |
+
│ code=1905 → 切换 ConfusionInfo 重试 │
|
| 111 |
+
└──────────────────────┬──────────────────────────────┘
|
| 112 |
+
▼
|
| 113 |
+
┌─────────────────────────────────────────────────────┐
|
| 114 |
+
│ 12. getDeviceId() 返回值 │
|
| 115 |
+
│ SMID存在 → "B" + SMID │
|
| 116 |
+
│ 仅本地数据 → "D" + smEncryptedData │
|
| 117 |
+
│ 示例: BXkNeGKe7f4Y5FtZamaxQPqL3ypegujSyds5e... │
|
| 118 |
+
└─────────────────────────────────────────────────────┘
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
---
|
| 122 |
+
|
| 123 |
+
## 四、指纹数据收集(23项)
|
| 124 |
+
|
| 125 |
+
| 字段 | 来源 | 说明 |
|
| 126 |
+
|------|------|------|
|
| 127 |
+
| plugins | navigator.plugins | 遍历插件name+description+filename+length,排序拼接 |
|
| 128 |
+
| ua | navigator.userAgent | 浏览器UA字符串 |
|
| 129 |
+
| canvas | canvas 2D | 绘制'http://www.ishumei.com'文字,取toDataURL的Base64哈希 |
|
| 130 |
+
| timezone | new Date().getTimezoneOffset() | 时区偏移(分钟) |
|
| 131 |
+
| platform | navigator.platform | 操作系统平台 |
|
| 132 |
+
| url | location.href.substr(0,64) | 当前页面URL前64字符 |
|
| 133 |
+
| referer | document.referrer.substr(0,64) | 来源URL前64字符 |
|
| 134 |
+
| res | screen.width+'_'+height+'_'+colorDepth+'_'+devicePixelRatio | 屏幕分辨率 |
|
| 135 |
+
| clientSize | screenLeft,screenTop,body.clientWidth,Height,availWidth,Height | 窗口尺寸 |
|
| 136 |
+
| status | Flash检测+webdriver检测 | 浏览器状态标志 |
|
| 137 |
+
| vpw | getUid() (UUID v4) | 随机值 |
|
| 138 |
+
| svm | getCurrentTime() | 时间戳(毫秒) |
|
| 139 |
+
| trees | getUid() | 随机值 |
|
| 140 |
+
| pmf | getCurrentTime() | 时间戳(毫秒) |
|
| 141 |
+
| cdp | console.debug检测 | 检测DevTools是否打开 |
|
| 142 |
+
| maxTouchPoints | navigator.maxTouchPoints | 触摸点数 |
|
| 143 |
+
| connectionRtt | navigator.connection.rtt | 网络RTT |
|
| 144 |
+
| cpucount | navigator.hardwareConcurrency | CPU核心数 |
|
| 145 |
+
| battery | navigator.getBattery() | 充电状态+电量 |
|
| 146 |
+
| appId | _smConf.appId | 应用ID |
|
| 147 |
+
| organization | _smConf.organization | 组织ID |
|
| 148 |
+
| sdkver | SDK版本 | "3.0.0" |
|
| 149 |
+
| subVersion | SDK子版本 | "1.0.0" |
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
## 五、ConfusionInfo 配置表
|
| 154 |
+
|
| 155 |
+
### Configuration A(Protocol=-1)
|
| 156 |
+
|
| 157 |
+
当 organization 匹配 `dHlAZfLJWqK2ndgYwMU0` 或 `oEpNqotnpIGaeNwevamH` 时使用:
|
| 158 |
+
|
| 159 |
+
| 原字段 | DES密钥 | 混淆名 |
|
| 160 |
+
|--------|---------|--------|
|
| 161 |
+
| appId | q8zb1bs1 | vw |
|
| 162 |
+
| canvas | qy1d6fmu | mi |
|
| 163 |
+
| organization | ht432iov | tj |
|
| 164 |
+
| os | i7g8jtql | vh |
|
| 165 |
+
| platform | mcbmtg5y | ye |
|
| 166 |
+
| plugins | j916a142 | vc |
|
| 167 |
+
| referer | rf149ntc | hv |
|
| 168 |
+
| res | rbtd2cl6 | pi |
|
| 169 |
+
| sdkver | rw74ssux | xh |
|
| 170 |
+
| status | 4xx5u0ww | uu |
|
| 171 |
+
| subVersion | 9wg0vhb9 | xi |
|
| 172 |
+
| svm | mqcala0h | nu |
|
| 173 |
+
| time | 8e224y3f | gw |
|
| 174 |
+
| timezone | 3w9lg8pr | oi |
|
| 175 |
+
| rtype | rx7ob4gc | na |
|
| 176 |
+
| tn | ilnts67v | nb |
|
| 177 |
+
| trees | al8x9zt7 | hc |
|
| 178 |
+
| ua | z5jc9qyp | wr |
|
| 179 |
+
| url | 6tbl1wpw | no |
|
| 180 |
+
| vpw | xzvnu5jt | st |
|
| 181 |
+
| protocol | (不加密) | protocol |
|
| 182 |
+
|
| 183 |
+
### Configuration B(Protocol=4)
|
| 184 |
+
|
| 185 |
+
默认/回退配置,使用不同的DES密钥和混淆名。
|
| 186 |
+
|
| 187 |
+
---
|
| 188 |
+
|
| 189 |
+
## 六、加密算法详解
|
| 190 |
+
|
| 191 |
+
### 6.1 DES-ECB 字段混淆
|
| 192 |
+
|
| 193 |
+
```javascript
|
| 194 |
+
// 对每个有 is_encrypt=1 的字段:
|
| 195 |
+
encryptedValue = Base64(DES_ECB(key, String(value), padding=0));
|
| 196 |
+
newFieldName = obfuscated_name;
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
Go 实现要点:
|
| 200 |
+
- Go 标准库 `crypto/des` 只提供 CBC 模式
|
| 201 |
+
- 需要手动实现 ECB:将明文填充到8字节倍数,逐块独立加密
|
| 202 |
+
- 填充方式:ZeroPadding(末尾补0)
|
| 203 |
+
- 输出:Base64 编码
|
| 204 |
+
|
| 205 |
+
### 6.2 tn 字段(数据完整性校验)
|
| 206 |
+
|
| 207 |
+
```javascript
|
| 208 |
+
tn = md5(sorted_key_value_string(data));
|
| 209 |
+
// 对所有key按字母排序,拼接 key1+value1+key2+value2+... 取MD5
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
### 6.3 Gzip 压缩
|
| 213 |
+
|
| 214 |
+
```javascript
|
| 215 |
+
// 使用 pako 库将混淆后的 JSON 字符串 gzip 压缩
|
| 216 |
+
const compressed = pako.gzip(JSON.stringify(data));
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
Go 实现:使用 `compress/gzip` 标准库
|
| 220 |
+
|
| 221 |
+
### 6.4 AES-CBC 加密
|
| 222 |
+
|
| 223 |
+
```javascript
|
| 224 |
+
AES-CBC(
|
| 225 |
+
key = Utf8.parse(priId), // priId = md5(uid)[:16] (16字节hex字符串)
|
| 226 |
+
iv = Utf8.parse('0102030405060708'),
|
| 227 |
+
mode = CBC,
|
| 228 |
+
padding = ZeroPadding // 末尾补0到16字节倍数
|
| 229 |
+
)
|
| 230 |
+
// 输出: ciphertext.toString() (hex字符串)
|
| 231 |
+
```
|
| 232 |
+
|
| 233 |
+
Go 实现:使用 `crypto/aes` 标准库
|
| 234 |
+
|
| 235 |
+
### 6.5 RSA 加密
|
| 236 |
+
|
| 237 |
+
```javascript
|
| 238 |
+
ep = JSEncrypt.setPublicKey(publicKey).encrypt(uid);
|
| 239 |
+
// 使用 JSEncrypt 库
|
| 240 |
+
// 填充方式: PKCS#1 v1.5
|
| 241 |
+
// 输出: Base64 字符串(我们转换为 hex 存储)
|
| 242 |
+
```
|
| 243 |
+
|
| 244 |
+
Go 实现:
|
| 245 |
+
- 公钥是 SubjectPublicKeyInfo DER 格式(Base64 编码)
|
| 246 |
+
- 使用 `x509.ParsePKIXPublicKey` 解析
|
| 247 |
+
- 使用 `rsa.EncryptPKCS1v15` 加密
|
| 248 |
+
|
| 249 |
+
---
|
| 250 |
+
|
| 251 |
+
## 七、API 请求/响应格式
|
| 252 |
+
|
| 253 |
+
### 请求
|
| 254 |
+
|
| 255 |
+
```
|
| 256 |
+
POST https://fp-it-acc.portal101.cn/deviceprofile/v4
|
| 257 |
+
|
| 258 |
+
Content-Type: application/json
|
| 259 |
+
|
| 260 |
+
{
|
| 261 |
+
"appId": "default",
|
| 262 |
+
"organization": "P9usCUBauxft8eAmUXaZ",
|
| 263 |
+
"ep": "<RSA加密的uid hex字符串>",
|
| 264 |
+
"data": "<AES加密后的hex字符串>",
|
| 265 |
+
"os": "web",
|
| 266 |
+
"encode": 5,
|
| 267 |
+
"compress": 2
|
| 268 |
+
}
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
### 成功响应(code=1100)
|
| 272 |
+
|
| 273 |
+
```json
|
| 274 |
+
{
|
| 275 |
+
"code": 1100,
|
| 276 |
+
"detail": {
|
| 277 |
+
"deviceId": "XkNeGKe7f4Y5FtZamaxQPqL3ypegujSyds5eQGup13ajuer9XhBAM82w1QWifIm6KUNv2hu3tUTMwoXrVzD0ZA=="
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
### 其他响应码
|
| 283 |
+
|
| 284 |
+
| 状态码 | 含义 | 处理 |
|
| 285 |
+
|--------|------|------|
|
| 286 |
+
| 1100 (0x44c) | 成功 | 取 detail.deviceId |
|
| 287 |
+
| 1902 (0x76e) | 需要重试 | 重新请求 |
|
| 288 |
+
| 1905 (0x771) | 需要切换配置 | 切换 ConfusionInfo 重试 |
|
| 289 |
+
|
| 290 |
+
---
|
| 291 |
+
|
| 292 |
+
## 八、SMID 缓存机制
|
| 293 |
+
|
| 294 |
+
- **Cookie/Storage key**: `.thumbcache_` + md5(organization)
|
| 295 |
+
- **smidV2**: 格式为 `YYYYMMDDHHmmss` + md5(uid) + `00` + md5(`smsk_web_` + 前面部分).substr(0,14) + `0`
|
| 296 |
+
- 优先从内存状态读取 SMID,其次从 Cookie,最后从 localStorage
|
| 297 |
+
|
| 298 |
+
---
|
| 299 |
+
|
| 300 |
+
## 九、device_id 返回值格式
|
| 301 |
+
|
| 302 |
+
```javascript
|
| 303 |
+
getDeviceId = function() {
|
| 304 |
+
var SMID = getState('SMID');
|
| 305 |
+
var smEncryptedData = getState('smEncryptedData');
|
| 306 |
+
return SMID ? 'B' + SMID : smEncryptedData ? 'D' + smEncryptedData : '';
|
| 307 |
+
}
|
| 308 |
+
```
|
| 309 |
+
|
| 310 |
+
- **前缀 'B'**:表示 SMID 已从服务器获取(正常路径)
|
| 311 |
+
- **前缀 'D'**:表示只有本地加密数据(服务器未返回 deviceId 时的降级)
|
| 312 |
+
- **示例**:`BXkNeGKe7f4Y5FtZamaxQPqL3ypegujSyds5eQGup13ajuer9XhBAM82w1QWifIm6KUNv2hu3tUTMwoXrVzD0ZA==`
|
| 313 |
+
|
| 314 |
+
---
|
| 315 |
+
|
| 316 |
+
## 十、DeepSeek Web端 vs Android端 完整对比
|
| 317 |
+
|
| 318 |
+
### 10.1 请求 Headers
|
| 319 |
+
|
| 320 |
+
| 项目 | Android端 | Web端 |
|
| 321 |
+
|------|-----------|-------|
|
| 322 |
+
| User-Agent | `DeepSeek/2.1.2 Android/32` | `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36` |
|
| 323 |
+
| x-client-platform | `android` | `web` |
|
| 324 |
+
| x-client-version | `2.1.2` | `2.0.0` |
|
| 325 |
+
| x-app-version | (无) | `2.0.0` |
|
| 326 |
+
| x-client-bundle-id | `com.deepseek.chat` | (无) |
|
| 327 |
+
| x-rangers-id | `7639874692114816004` | (无) |
|
| 328 |
+
| accept-encoding | `gzip` | `gzip, deflate, br, zstd` |
|
| 329 |
+
| sec-ch-ua | (无) | `"Chromium";v="137", "Not/A)Brand";v="24"` |
|
| 330 |
+
| origin | (无) | `https://chat.deepseek.com` |
|
| 331 |
+
| referer | (无) | `https://chat.deepseek.com/` |
|
| 332 |
+
| x-hif-dliq | (无) | 动态值(轮询获取) |
|
| 333 |
+
| x-hif-leim | (无) | 动态值(轮询获取) |
|
| 334 |
+
|
| 335 |
+
### 10.2 Login 请求体
|
| 336 |
+
|
| 337 |
+
**Android**:
|
| 338 |
+
```json
|
| 339 |
+
{
|
| 340 |
+
"password": "...",
|
| 341 |
+
"device_id": "android_device",
|
| 342 |
+
"os": "android",
|
| 343 |
+
"email": "..." // 或 mobile + area_code
|
| 344 |
+
}
|
| 345 |
+
```
|
| 346 |
+
|
| 347 |
+
**Web**:
|
| 348 |
+
```json
|
| 349 |
+
{
|
| 350 |
+
"password": "...",
|
| 351 |
+
"device_id": "BXkNeGKe7f4Y5FtZamaxQPqL3ypegujSyds5eQGup13ajuer9XhBAM82w1QWifIm6KUNv2hu3tUTMwoXrVzD0ZA==",
|
| 352 |
+
"os": "web",
|
| 353 |
+
"email": "..." // 或 mobile + area_code
|
| 354 |
+
}
|
| 355 |
+
```
|
| 356 |
+
|
| 357 |
+
### 10.3 TLS 指纹
|
| 358 |
+
|
| 359 |
+
| 项目 | Android端 | Web端 |
|
| 360 |
+
|------|-----------|-------|
|
| 361 |
+
| TLS Client Hello | Safari (utls.HelloSafari_Auto) | Chrome (utls.HelloChrome_Auto) |
|
| 362 |
+
| ALPN | 强制 http/1.1 | 自然协商 (h2 或 http/1.1) |
|
| 363 |
+
| HTTP/2 | 不尝试 | 支持 |
|
| 364 |
+
|
| 365 |
+
### 10.4 PoW 验证
|
| 366 |
+
|
| 367 |
+
**两端完全相同**:
|
| 368 |
+
- 端点:`/api/v0/chat/create_pow_challenge`
|
| 369 |
+
- 算法:`DeepSeekHashV1`
|
| 370 |
+
- 请求体:`{"target_path": "/api/v0/chat/completion"}`
|
| 371 |
+
- 响应体:`{"challenge": {"algorithm": "DeepSeekHashV1", "challenge": "...", "salt": "...", "signature": "...", "difficulty": 144000, ...}}`
|
| 372 |
+
|
| 373 |
+
---
|
| 374 |
+
|
| 375 |
+
## 十一、HIF(x-hif-dliq/x-hif-leim)反爬机制
|
| 376 |
+
|
| 377 |
+
### 11.1 概述
|
| 378 |
+
|
| 379 |
+
HIF 是 DeepSeek Web端特有的反爬/指纹验证机制,通过轮询两个外部端点获取验证 token。
|
| 380 |
+
|
| 381 |
+
### 11.2 端点
|
| 382 |
+
|
| 383 |
+
| 名称 | 端点 URL | Header |
|
| 384 |
+
|------|----------|--------|
|
| 385 |
+
| dliq | `https://hif-dliq.deepseek.com/query` | `x-hif-dliq` |
|
| 386 |
+
| leim | `https://hif-leim.deepseek.com/query` | `x-hif-leim` |
|
| 387 |
+
|
| 388 |
+
### 11.3 工作原理
|
| 389 |
+
|
| 390 |
+
1. 页面加载时启动两个轮询器(leimPoller, dliqPoller)
|
| 391 |
+
2. 每个轮询器周期性向对应端点 GET 请求
|
| 392 |
+
3. 响应包含 `x-hif-ttl` header(缓存时间,默认600秒)
|
| 393 |
+
4. 成功获取的值缓存到 localStorage(`hif_leim_cached`, `hif_dliq_cached`)
|
| 394 |
+
5. 发送 API 请求时从缓存或轮询器获取当前值,添加到请求头
|
| 395 |
+
6. 使用指数退避重试策略(初始1秒,最大600秒,倍增系数2)
|
| 396 |
+
|
| 397 |
+
### 11.4 值格式
|
| 398 |
+
|
| 399 |
+
示例值:`9lspMrX9S0NlRuD7+ChfCz3ENqJPjH4qnMfnlPAwq0wfRuH2nmSepbg=.xuVOOVjUztQikXFf`
|
| 400 |
+
|
| 401 |
+
格式:`<Base64编码部分>.<短标识>`
|
| 402 |
+
|
| 403 |
+
### 11.5 是否必须?
|
| 404 |
+
|
| 405 |
+
从 HAR 分析看:
|
| 406 |
+
- HIF headers 在 completion 请求中发送
|
| 407 |
+
- 建议实现:可以先不发送测试服务端是否强制校验
|
| 408 |
+
- 如需完整实现:模拟轮询器,向端点 GET 请求获取 token
|
| 409 |
+
|
| 410 |
+
---
|
| 411 |
+
|
| 412 |
+
## 十二、项目实现状态
|
| 413 |
+
|
| 414 |
+
### 已完成
|
| 415 |
+
|
| 416 |
+
| 模块 | 文件 | 状态 |
|
| 417 |
+
|------|------|------|
|
| 418 |
+
| Config 平台设置 | `internal/config/config.go` | ✅ 完成 |
|
| 419 |
+
| Config Store 访问器 | `internal/config/store_accessors.go` | ✅ 完成 |
|
| 420 |
+
| Config 序列化 | `internal/config/codec.go` | ✅ 完成 |
|
| 421 |
+
| Config 验证 | `internal/config/validation.go` | ✅ 完成 |
|
| 422 |
+
| Admin 设置 API | `internal/httpapi/admin/settings/` | ✅ 完成 |
|
| 423 |
+
| Web protocol 常量 | `internal/deepseek/protocol/constants_web.json` | ✅ 完成 |
|
| 424 |
+
| Protocol 平台切换 | `internal/deepseek/protocol/constants.go` | ✅ 完成 |
|
| 425 |
+
| Chrome TLS 指纹 | `internal/deepseek/transport/transport.go` | ✅ 完成 |
|
| 426 |
+
| 数美 device_id | `internal/shumei/` | ✅ 完成 |
|
| 427 |
+
| device_id 主入口 | `internal/shumei/device_id.go` | ✅ 完成 |
|
| 428 |
+
| 加密实现 | `internal/shumei/crypto.go` | ✅ 完成 |
|
| 429 |
+
| DES 混淆 | `internal/shumei/confusion.go` | ✅ 完成 |
|
| 430 |
+
| API 调用 | `internal/shumei/api.go` | ✅ 完成 |
|
| 431 |
+
|
| 432 |
+
### 待完成
|
| 433 |
+
|
| 434 |
+
| 模块 | 说明 | 优先级 |
|
| 435 |
+
|------|------|--------|
|
| 436 |
+
| Client 平台切换 | 修改 deepseek/client 使用平台特定的 transport 和 headers | 高 |
|
| 437 |
+
| WebUI 设置面板 | 添加平台切换下拉框 | 高 |
|
| 438 |
+
| i18n 翻译 | 添加平台相关翻译文本 | 高 |
|
| 439 |
+
| HIF 轮询器 | 实现 x-hif-dliq/leim 轮询 | 中 |
|
| 440 |
+
| Login 使用 device_id | 修改 client_auth.go 在 web 模式下调用 shumei.GetDeviceID | 高 |
|
| 441 |
+
|
| 442 |
+
---
|
| 443 |
+
|
| 444 |
+
## 十三、关键代码位置索引
|
| 445 |
+
|
| 446 |
+
| 功能 | 文件路径 |
|
| 447 |
+
|------|----------|
|
| 448 |
+
| 项目入口 | `cmd/ds2api/main.go` |
|
| 449 |
+
| 路由定义 | `internal/server/router.go` |
|
| 450 |
+
| HTTP 助手 | `internal/deepseek/client/client_http_helpers.go` |
|
| 451 |
+
| HTTP JSON | `internal/deepseek/client/client_http_json.go` |
|
| 452 |
+
| 认证/Login | `internal/deepseek/client/client_auth.go` |
|
| 453 |
+
| 完成请求 | `internal/deepseek/client/client_completion.go` |
|
| 454 |
+
| 代理客户端 | `internal/deepseek/client/proxy.go` |
|
| 455 |
+
| PoW 计算 | `internal/deepseek/client/pow.go` |
|
| 456 |
+
| 核心客户端 | `internal/deepseek/client/client_core.go` |
|
| 457 |
+
| Transport | `internal/deepseek/transport/transport.go` |
|
| 458 |
+
| Android 常量 | `internal/deepseek/protocol/constants_shared.json` |
|
| 459 |
+
| Web 常量 | `internal/deepseek/protocol/constants_web.json` |
|
| 460 |
+
| Protocol 常量代码 | `internal/deepseek/protocol/constants.go` |
|
| 461 |
+
| 数美 device_id | `internal/shumei/device_id.go` |
|
| 462 |
+
| 数美加密 | `internal/shumei/crypto.go` |
|
| 463 |
+
| 数美混淆 | `internal/shumei/confusion.go` |
|
| 464 |
+
| 数美 API | `internal/shumei/api.go` |
|
| 465 |
+
| Config 结构 | `internal/config/config.go` |
|
| 466 |
+
| Config 序列化 | `internal/config/codec.go` |
|
| 467 |
+
| Settings Handler | `internal/httpapi/admin/settings/` |
|
| 468 |
+
| 设置 UI 容器 | `webui/src/features/settings/SettingsContainer.jsx` |
|
| 469 |
+
| 设置表单 Hook | `webui/src/features/settings/useSettingsForm.js` |
|
| 470 |
+
| 设置 API | `webui/src/features/settings/settingsApi.js` |
|
| 471 |
+
| 运行时设置 UI | `webui/src/features/settings/RuntimeSection.jsx` |
|
| 472 |
+
| 中文翻译 | `webui/src/locales/zh.json` |
|
| 473 |
+
| 英文翻译 | `webui/src/locales/en.json` |
|
| 474 |
+
|
| 475 |
+
---
|
| 476 |
+
|
| 477 |
+
## 十四、注意事项
|
| 478 |
+
|
| 479 |
+
1. **Organization 匹配**:DeepSeek 使用的 organization `P9usCUBauxft8eAmUXaZ` 不在 SDK 内置白名单中,应使用默认 ConfusionInfo(Configuration B)或尝试 Configuration A
|
| 480 |
+
|
| 481 |
+
2. **Canvas Hash**:服务器可能校验 canvas 指纹的一致性,但同一设备多次请求可以复用
|
| 482 |
+
|
| 483 |
+
3. **SMID 缓存**:成功获取 deviceId 后应缓存复用,避免每次登录都重新请求
|
| 484 |
+
|
| 485 |
+
4. **HIF Token 有效期**:默认 600 秒,过期需重新轮询获取
|
| 486 |
+
|
| 487 |
+
5. **TLS 指纹匹配**:Web 模式必须使用 Chrome TLS 指纹且允许 HTTP/2 协商
|
| 488 |
+
|
| 489 |
+
6. **device_id 前缀**:服务端返回的 deviceId 需要加 "B" 前缀才是最终的 device_id 值
|
docs/web-endpoint-upgrade.md
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Web端接口实现 & 数美SDK逆向研究报告
|
| 2 |
+
|
| 3 |
+
## 一、概述
|
| 4 |
+
|
| 5 |
+
本项目新增了对 DeepSeek **Web端接口**的支持,可通过设置面板在"Android端"和"Web端"之间热切换,无需重启服务。
|
| 6 |
+
|
| 7 |
+
### 核心功能
|
| 8 |
+
|
| 9 |
+
| 特性 | Android端 | Web端 |
|
| 10 |
+
|------|-----------|-------|
|
| 11 |
+
| User-Agent | `DeepSeek/2.1.2 Android/32` | Chrome 137 浏览器UA |
|
| 12 |
+
| TLS指纹 | Safari | Chrome (HTTP/2) |
|
| 13 |
+
| x-client-platform | `android` | `web` |
|
| 14 |
+
| device_id | `"android_device"` | 数美SDK加密字符串 |
|
| 15 |
+
| x-hif-dliq | 无 | 动态轮询获取 |
|
| 16 |
+
| x-hif-leim | 无 | 动态轮询获取 |
|
| 17 |
+
| 浏览器特有头 | 无 | sec-ch-ua, origin, referer等 |
|
| 18 |
+
|
| 19 |
+
### 架构设计
|
| 20 |
+
|
| 21 |
+
```
|
| 22 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 23 |
+
│ 设置面板 (WebUI) │
|
| 24 |
+
│ 选择: Android / Web 接口平台 │
|
| 25 |
+
└─────────────────────────┬───────────────────────────────────┘
|
| 26 |
+
│ PUT /admin/settings
|
| 27 |
+
▼
|
| 28 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 29 |
+
│ Handler.applyPlatformChange() │
|
| 30 |
+
│ DS.SetPlatform("web" / "android") │
|
| 31 |
+
├─────────────────────────────────────────────────────────────┤
|
| 32 |
+
│ 1. dsprotocol.ApplyPlatform() → 切换 BaseHeaders / UA │
|
| 33 |
+
│ 2. RefreshTransport() → 切换 TLS 指纹 │
|
| 34 |
+
│ 3. hifPoller.Start()/Stop() → 启动/停止 HIF 轮询 │
|
| 35 |
+
└─────────────────────────────────────────────────────────────┘
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
切换后立即生效,新请求自动使用对应平台的 UA、TLS 指纹、Headers 和 device_id 算法。
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## 二、文件变更清单
|
| 43 |
+
|
| 44 |
+
### 新增文件(8个)
|
| 45 |
+
|
| 46 |
+
| 文件 | 说明 |
|
| 47 |
+
|------|------|
|
| 48 |
+
| `internal/deepseek/protocol/constants_web.json` | Web端协议常量定义 |
|
| 49 |
+
| `internal/deepseek/hif/hif.go` | HIF头部轮询器 |
|
| 50 |
+
| `internal/deepseek/hif/hif_test.go` | HIF单元测试 |
|
| 51 |
+
| `internal/shumei/device_id.go` | 数美SDK device_id生成入口 |
|
| 52 |
+
| `internal/shumei/crypto.go` | RSA/DES/AES/MD5/Gzip加密工具 |
|
| 53 |
+
| `internal/shumei/confusion.go` | DES字段混淆映射表 |
|
| 54 |
+
| `internal/shumei/api.go` | 数美设备指纹API调用 |
|
| 55 |
+
| `webui/src/features/settings/PlatformSection.jsx` | 平台切换UI组件 |
|
| 56 |
+
|
| 57 |
+
### 修改文件(18个)
|
| 58 |
+
|
| 59 |
+
#### Config层
|
| 60 |
+
- `internal/config/config.go` — PlatformConfig结构体、PlatformMode()、IsWebPlatform()
|
| 61 |
+
- `internal/config/codec.go` — JSON序列化支持
|
| 62 |
+
- `internal/config/store_accessors.go` — Store级访问器
|
| 63 |
+
- `internal/config/validation.go` — 平台配置校验
|
| 64 |
+
|
| 65 |
+
#### Protocol层
|
| 66 |
+
- `internal/deepseek/protocol/constants.go` — ApplyPlatform()、WebExtraHeaders()、UserAgent()
|
| 67 |
+
|
| 68 |
+
#### Transport层
|
| 69 |
+
- `internal/deepseek/transport/transport.go` — Chrome TLS指纹、NewWithPlatform()
|
| 70 |
+
|
| 71 |
+
#### Client层
|
| 72 |
+
- `internal/deepseek/client/client_core.go` — SetPlatform()、RefreshTransport()、HIF poller集成
|
| 73 |
+
- `internal/deepseek/client/client_auth.go` — Web端login(数美device_id)
|
| 74 |
+
- `internal/deepseek/client/client_completion.go` — Web端completion headers
|
| 75 |
+
- `internal/deepseek/client/client_continue.go` — Web端continue headers
|
| 76 |
+
- `internal/deepseek/client/proxy.go` — 平台感知代理客户端
|
| 77 |
+
|
| 78 |
+
#### Admin API
|
| 79 |
+
- `internal/httpapi/admin/shared/deps.go` — DeepSeekCaller接口添加SetPlatform
|
| 80 |
+
- `internal/httpapi/admin/settings/handler_settings_*.go` — 设置保存/读取/热加载
|
| 81 |
+
|
| 82 |
+
#### 前端
|
| 83 |
+
- `webui/src/features/settings/SettingsContainer.jsx` — 集成PlatformSection
|
| 84 |
+
- `webui/src/features/settings/useSettingsForm.js` — 平台表单数据流
|
| 85 |
+
- `webui/src/locales/zh.json` / `en.json` — 国际化翻译
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## 三、数美SDK逆向分析报告
|
| 90 |
+
|
| 91 |
+
### 3.1 SDK基本信息
|
| 92 |
+
|
| 93 |
+
- **SDK名称**:数美科技(Shumei)DeviceFingerprint Web SDK
|
| 94 |
+
- **版本**:3.0.0 / subVersion 1.0.0
|
| 95 |
+
- **加载方式**:通过 `https://cdn.deepseek.com/static/chat/fp-1.min.js` 动态加载
|
| 96 |
+
- **配置来源**:`window._smConf`(由DeepSeek页面注入)
|
| 97 |
+
- **API端点**:`https://fp-it-acc.portal101.cn/deviceprofile/v4`
|
| 98 |
+
|
| 99 |
+
### 3.2 device_id生成流程
|
| 100 |
+
|
| 101 |
+
```
|
| 102 |
+
1. 生成UUID v4 (uid)
|
| 103 |
+
│
|
| 104 |
+
2. 计算priId = md5(uid)[:16] ────→ 作为AES密钥
|
| 105 |
+
│
|
| 106 |
+
3. RSA加密uid (PKCS#1 v1.5) ────→ ep字段
|
| 107 |
+
│
|
| 108 |
+
4. 收集23项浏览器指纹数据
|
| 109 |
+
│
|
| 110 |
+
5. DES-ECB逐字段加密+字段名混淆
|
| 111 |
+
│
|
| 112 |
+
6. 计算tn = md5(sorted key=value) ──→ 完整性校验
|
| 113 |
+
│
|
| 114 |
+
7. JSON → Gzip压缩
|
| 115 |
+
│
|
| 116 |
+
8. AES-CBC加密 (priId + IV="0102030405060708" + ZeroPadding)
|
| 117 |
+
│
|
| 118 |
+
9. POST到数美API
|
| 119 |
+
│
|
| 120 |
+
10. 解析响应 code=1100 → detail.deviceId
|
| 121 |
+
│
|
| 122 |
+
11. getDeviceId() = "B" + deviceId
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
### 3.3 关键配置参数
|
| 126 |
+
|
| 127 |
+
| 参数 | 值 | 说明 |
|
| 128 |
+
|------|-----|------|
|
| 129 |
+
| RSA公钥 | `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDetfEgYD4aE1ZjmWJ6/jnPurhzI+yeRoJHWrnNtQMte3stQ4VjG3yu21FuN75E6cDpA9KtDXwcB2M/FiGUAe3G0rNotbWI8+SjZfUbW/OILFTzY0uaeEkmVGW5WyJ6weQbbr1xTCPa2OO3YIMeZljWUYHG5h21WAm/PATg8im8cQIDAQAB` | 用于加密uid |
|
| 130 |
+
| organization | `P9usCUBauxft8eAmUXaZ` | 设备标识归属 |
|
| 131 |
+
| appId | `default` | 应用ID |
|
| 132 |
+
| apiHost | `fp-it-acc.portal101.cn` | 数美API域名 |
|
| 133 |
+
|
| 134 |
+
### 3.4 指纹数据收集(23项)
|
| 135 |
+
|
| 136 |
+
| 字段 | 来源 | 说明 |
|
| 137 |
+
|------|------|------|
|
| 138 |
+
| ua | navigator.userAgent | 浏览器UA |
|
| 139 |
+
| platform | navigator.platform | Win32 |
|
| 140 |
+
| canvas | canvas 2D绘图 | 绘制'http://www.ishumei.com'文字取哈希 |
|
| 141 |
+
| timezone | Date.getTimezoneOffset() | 时区偏移 |
|
| 142 |
+
| res | screen.width/height/colorDepth/devicePixelRatio | 屏幕信息 |
|
| 143 |
+
| plugins | navigator.plugins | 浏览器插件列表 |
|
| 144 |
+
| referer | document.referrer | 来源页面 |
|
| 145 |
+
| url | location.href | 当前URL |
|
| 146 |
+
| clientSize | screenLeft/screenTop/body尺寸 | 窗口大小 |
|
| 147 |
+
| vpw | UUID v4 | 随机值 |
|
| 148 |
+
| svm | 时间戳 | 当前时间 |
|
| 149 |
+
| trees | UUID v4 | 随机值 |
|
| 150 |
+
| pmf | 时间戳 | 当前时间 |
|
| 151 |
+
| cpucount | navigator.hardwareConcurrency | CPU核心数 |
|
| 152 |
+
| battery | navigator.getBattery() | 电池状态 |
|
| 153 |
+
| connectionRtt | navigator.connection.rtt | 网络延迟 |
|
| 154 |
+
| maxTouchPoints | navigator.maxTouchPoints | 触控点数 |
|
| 155 |
+
| status | Flash检测+webdriver检测 | 环境检测 |
|
| 156 |
+
| rtype | 固定值"1" | 类型标识 |
|
| 157 |
+
| sdkver | "3.0.0" | SDK版本 |
|
| 158 |
+
| subVersion | "1.0.0" | 子版本 |
|
| 159 |
+
| appId | "default" | 应用ID |
|
| 160 |
+
| organization | 配置值 | 组织标识 |
|
| 161 |
+
|
| 162 |
+
### 3.5 DES字段混淆
|
| 163 |
+
|
| 164 |
+
每个指纹字段使用独立的8字节DES密钥进行ECB模式加密,然后Base64编码,最后字段名映射为混淆名称。
|
| 165 |
+
|
| 166 |
+
**混淆映射表(部分)**:
|
| 167 |
+
|
| 168 |
+
| 原字段 | DES密钥 | 混淆名 |
|
| 169 |
+
|--------|---------|--------|
|
| 170 |
+
| appId | `q8zb1bs1` | vw |
|
| 171 |
+
| organization | `ht432iov` | tj |
|
| 172 |
+
| os | `i7g8jtql` | vh |
|
| 173 |
+
| ua | `z5jc9qyp` | wr |
|
| 174 |
+
| canvas | `qy1d6fmu` | mi |
|
| 175 |
+
| plugins | `j916a142` | vc |
|
| 176 |
+
| referer | `rf149ntc` | hv |
|
| 177 |
+
| res | `rbtd2cl6` | pi |
|
| 178 |
+
| timezone | `3w9lg8pr` | oi |
|
| 179 |
+
| svm | `mqcala0h` | nu |
|
| 180 |
+
| trees | `al8x9zt7` | hc |
|
| 181 |
+
| vpw | `xzvnu5jt` | st |
|
| 182 |
+
| protocol | (不加密) | protocol |
|
| 183 |
+
|
| 184 |
+
### 3.6 加密算法详解
|
| 185 |
+
|
| 186 |
+
#### RSA加密(uid → ep)
|
| 187 |
+
- 使用JSEncrypt库
|
| 188 |
+
- PKCS#1 v1.5填充
|
| 189 |
+
- 输出为Base64字符串
|
| 190 |
+
|
| 191 |
+
#### DES-ECB加密(字段混淆)
|
| 192 |
+
- 每个字段独立使用8字节密钥
|
| 193 |
+
- ECB模式,逐块加密
|
| 194 |
+
- 输出Base64编码
|
| 195 |
+
|
| 196 |
+
#### AES-CBC加密(数据体)
|
| 197 |
+
- 密钥:`priId`(md5(uid)[:16],UTF-8编码)
|
| 198 |
+
- IV:`0102030405060708`
|
| 199 |
+
- 模式:CBC
|
| 200 |
+
- 填充:ZeroPadding
|
| 201 |
+
- 输入:gzip压缩后的JSON
|
| 202 |
+
- 输出:hex字符串
|
| 203 |
+
|
| 204 |
+
#### 完整性校验(tn字段)
|
| 205 |
+
```
|
| 206 |
+
tn = md5(sorted_key_value_string(data))
|
| 207 |
+
```
|
| 208 |
+
对所有key排序后拼接`key+value`为字符串,取MD5。
|
| 209 |
+
|
| 210 |
+
### 3.7 API请求格式
|
| 211 |
+
|
| 212 |
+
**请求**:
|
| 213 |
+
```
|
| 214 |
+
POST https://fp-it-acc.portal101.cn/deviceprofile/v4
|
| 215 |
+
|
| 216 |
+
{
|
| 217 |
+
"appId": "default",
|
| 218 |
+
"organization": "P9usCUBauxft8eAmUXaZ",
|
| 219 |
+
"ep": "RSA加密的uid(hex)",
|
| 220 |
+
"data": "AES加密后的hex字符串",
|
| 221 |
+
"os": "web",
|
| 222 |
+
"encode": 5,
|
| 223 |
+
"compress": 2
|
| 224 |
+
}
|
| 225 |
+
```
|
| 226 |
+
|
| 227 |
+
**成功响应**:
|
| 228 |
+
```json
|
| 229 |
+
{
|
| 230 |
+
"code": 1100,
|
| 231 |
+
"detail": {
|
| 232 |
+
"deviceId": "XkNeGKe7f4Y5FtZamaxQPqL3ypegujSyds5eQGup13ajuer9XhBAM82w1QWifIm6KUNv2hu3tUTMwoXrVzD0ZA=="
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
最终返回的device_id为:`"B" + deviceId`
|
| 238 |
+
|
| 239 |
+
### 3.8 Go实现要点
|
| 240 |
+
|
| 241 |
+
1. **不需要浏览器**:device_id由数美服务器生成,客户端只负责发送指纹数据和接收结果
|
| 242 |
+
2. **指纹数据可伪造**:服务端不校验指纹真实性,只需要格式正确
|
| 243 |
+
3. **关键依赖**:crypto/des、crypto/aes、crypto/rsa、crypto/md5、compress/gzip
|
| 244 |
+
4. **DES-ECB**:Go标准库不提供ECB模式,需手动逐块实现
|
| 245 |
+
5. **AES ZeroPadding**:标准库只提供PKCS7,需手动补零
|
| 246 |
+
|
| 247 |
+
---
|
| 248 |
+
|
| 249 |
+
## 四、HIF头部轮询机制
|
| 250 |
+
|
| 251 |
+
### 4.1 概述
|
| 252 |
+
|
| 253 |
+
Web端每个请求需要携带两个特殊头部:
|
| 254 |
+
- `x-hif-dliq`
|
| 255 |
+
- `x-hif-leim`
|
| 256 |
+
|
| 257 |
+
这两个值通过轮询DeepSeek的两个端点动态获取。
|
| 258 |
+
|
| 259 |
+
### 4.2 端点
|
| 260 |
+
|
| 261 |
+
| 类型 | URL | 对应头部 |
|
| 262 |
+
|------|-----|----------|
|
| 263 |
+
| dliq | `https://hif-dliq.deepseek.com/query` | x-hif-dliq |
|
| 264 |
+
| leim | `https://hif-leim.deepseek.com/query` | x-hif-leim |
|
| 265 |
+
|
| 266 |
+
### 4.3 响应格式
|
| 267 |
+
|
| 268 |
+
```json
|
| 269 |
+
{
|
| 270 |
+
"code": 0,
|
| 271 |
+
"data": {
|
| 272 |
+
"biz_code": 0,
|
| 273 |
+
"biz_data": {
|
| 274 |
+
"value": "9lspMrX9S0NlRuD7+ChfCz3ENqJPjH4qnMfnlPAwq0wfRuH2nmSepbg=.xuVOOVjUztQikXFf"
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
```
|
| 279 |
+
|
| 280 |
+
响应头 `x-hif-ttl` 包含��存有效期(秒),默认600秒。
|
| 281 |
+
|
| 282 |
+
### 4.4 轮询策略
|
| 283 |
+
|
| 284 |
+
```
|
| 285 |
+
初始化 → 启动两个goroutine轮询
|
| 286 |
+
│
|
| 287 |
+
├── 成功 → 缓存value → 等待TTL秒 → 继续轮询
|
| 288 |
+
│
|
| 289 |
+
└── 失败 → 指数退避(1s→2s→4s→...→max 600s) → 重试
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
- 初始重试间隔:1秒
|
| 293 |
+
- 退避倍数:2x
|
| 294 |
+
- 最大间隔:600秒
|
| 295 |
+
- 成功间隔:TTL(从响应头获取)
|
| 296 |
+
|
| 297 |
+
### 4.5 Go实现
|
| 298 |
+
|
| 299 |
+
`internal/deepseek/hif/hif.go` 实现了完整的轮询逻辑:
|
| 300 |
+
|
| 301 |
+
```go
|
| 302 |
+
type HIFPoller struct {
|
| 303 |
+
mu sync.RWMutex
|
| 304 |
+
dliq string
|
| 305 |
+
leim string
|
| 306 |
+
stopCh chan struct{}
|
| 307 |
+
client *trans.Client // 使用Chrome TLS
|
| 308 |
+
fallback *http.Client // 标准HTTP fallback
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
func (p *HIFPoller) Start() // 启动两个轮询goroutine
|
| 312 |
+
func (p *HIFPoller) Stop() // 停止轮询
|
| 313 |
+
func (p *HIFPoller) GetHeaders() map[string]string // 获取当前HIF头部
|
| 314 |
+
```
|
| 315 |
+
|
| 316 |
+
---
|
| 317 |
+
|
| 318 |
+
## 五、TLS指纹对比
|
| 319 |
+
|
| 320 |
+
### Android端
|
| 321 |
+
|
| 322 |
+
```
|
| 323 |
+
ClientHello: HelloSafari_Auto
|
| 324 |
+
ALPN: 强制 http/1.1 (forceHTTP11ALPN)
|
| 325 |
+
```
|
| 326 |
+
|
| 327 |
+
### Web端
|
| 328 |
+
|
| 329 |
+
```
|
| 330 |
+
ClientHello: HelloChrome_Auto
|
| 331 |
+
ALPN: 自然协商 (支持 h2 和 http/1.1)
|
| 332 |
+
ForceAttemptHTTP2: true
|
| 333 |
+
```
|
| 334 |
+
|
| 335 |
+
实现位于 `internal/deepseek/transport/transport.go`:
|
| 336 |
+
|
| 337 |
+
```go
|
| 338 |
+
func TLSDialerForPlatform(platform string) func(...) {
|
| 339 |
+
if platform == "web" {
|
| 340 |
+
return chromeTLSDialer(dialContext) // Chrome + HTTP/2
|
| 341 |
+
}
|
| 342 |
+
return safariTLSDialer(dialContext) // Safari + HTTP/1.1
|
| 343 |
+
}
|
| 344 |
+
```
|
| 345 |
+
|
| 346 |
+
---
|
| 347 |
+
|
| 348 |
+
## 六、使用方式
|
| 349 |
+
|
| 350 |
+
### 6.1 设置面板切换
|
| 351 |
+
|
| 352 |
+
访问 WebUI 设置页面,在"接口平台"选项中选择:
|
| 353 |
+
- **Android端(默认)**:使用Safari TLS指纹,device_id为固定值
|
| 354 |
+
- **Web端(Chrome浏览器)**:使用Chrome TLS指纹+HTTP/2,动态生成数美device_id,轮询HIF头部
|
| 355 |
+
|
| 356 |
+
切换后**立即生效**,无需重启服务。
|
| 357 |
+
|
| 358 |
+
### 6.2 环境变量
|
| 359 |
+
|
| 360 |
+
```bash
|
| 361 |
+
# 也可通过环境变量设置默认平台
|
| 362 |
+
DS2API_PLATFORM_MODE=web
|
| 363 |
+
```
|
| 364 |
+
|
| 365 |
+
### 6.3 API设置
|
| 366 |
+
|
| 367 |
+
```bash
|
| 368 |
+
curl -X PUT http://localhost:5001/admin/settings \
|
| 369 |
+
-H "Authorization: Bearer admin" \
|
| 370 |
+
-H "Content-Type: application/json" \
|
| 371 |
+
-d '{"platform":{"mode":"web"}}'
|
| 372 |
+
```
|
| 373 |
+
|
| 374 |
+
---
|
| 375 |
+
|
| 376 |
+
## 七、兼容性说明
|
| 377 |
+
|
| 378 |
+
- **默认行为不变**:未设置platform时默认为"android"
|
| 379 |
+
- **向后兼容**:所有现有配置和API保持不变
|
| 380 |
+
- **热切换**:切换平台时自动重建HTTP客户端、更新headers、启停HIF轮询
|
| 381 |
+
- **代理兼容**:使用代理时同样应用正确的TLS指纹和headers
|
| 382 |
+
|
| 383 |
+
---
|
| 384 |
+
|
| 385 |
+
## 八、测试状态
|
| 386 |
+
|
| 387 |
+
```
|
| 388 |
+
✅ go build ./... 编译通过
|
| 389 |
+
✅ go vet (核心包) 静态检查通过
|
| 390 |
+
✅ go test deepseek/... deepseek包测试通过
|
| 391 |
+
✅ go test shumei/... 数美包测试通过
|
| 392 |
+
✅ npm run build webui 前端构建通过
|
| 393 |
+
```
|
| 394 |
+
|
| 395 |
+
> 注意:`TestGetSettingsIncludesCurrentInputFileDefaults` 是预先存在的测试失败,与本次改动无关。
|