luckfun233 commited on
Commit
cf4c38d
·
1 Parent(s): 3678d9b

feat: 新增数美SDK逆向研究报告及Web端接口实现文档

Browse files
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` 是预先存在的测试失败,与本次改动无关。