抖音 Web 端 a_bogus、X-Bogus 算法拆解:从浏览器参数到请求签名
摘要:本文基于本地web目录的四个文件,专门拆解抖音 Web 端a_bogus、X-Bogus的参数链路、浏览器环境、JavaScript 签名器和 Python 请求编排。文章只保留结构性信息,真实 Cookie、msToken、verifyFp、webid和完整签名统一脱敏。
关键词:抖音 a_bogus、X-Bogus、六神算法、detail.py、浏览器指纹、请求签名、SM3、RC4、Web 协议分析
文章目录
- 抖音 Web 端 a_bogus、X-Bogus 算法拆解:从浏览器参数到请求签名
- 先说结论:Web 侧不是 Android 七神的同一套代码
- 一、目录里的文件分别做什么?
- 二、`detail.py` 的请求链路
- 三、`19ab.js` 的 a_bogus 高层流程
- 1. 对输入做固定后缀处理
- 2. 做双重摘要
- 3. 把 UA 与浏览器环境混入字节数组
- 4. 随机掩码、重排与校验
- 5. 使用自定义 64 字符表编码
- 四、pure / 164 两份实现怎么对照?
- `a_bogus_pure.js`
- `a_bogus164.js`
- 五、浏览器参数为什么比 aweme_id 更容易出问题?
- 六、最容易踩的坑
- 1. 把“六神”当作官方固定名称
- 2. 把硬编码 Cookie 当成长期方案
- 3. 只看签名长度
- 4. 忽略随机与时间
- 5. 把 pure.js 当成标准实现
- 6. 只看 HTTP 200
- 7. 忽略 JavaScript 运行时差异
- 七、建议的脱敏调试基线
- 结语:真正难的是把浏览器上下文对齐
先说结论:Web 侧不是 Android 七神的同一套代码
“六神”是社区常用称呼,不是抖音官方算法名。当前web目录能直接观察到的重点,是三条 Web 签名实现线:
19ab.js:导出式调用getABogus,由detail.py通过 ExecJS 调用;a_bogus_pure.js:可读的DualBogusSigner,同时提供sign_a_bogus和sign_x_bogus;a_bogus164.js:另一份同类长签名实现,入口是generate_a_bogus。
所以本文讨论的是Web 侧的 a_bogus / X-Bogus 请求签名链路,不把它和 Android 目录里的 X-Gorgon、X-Khronos、X-Medusa 等字段混成一套,更不把本地脚本描述成官方、永久有效或已完成线上验签的实现。
图 1:从浏览器输入到 a_bogus 生成,再到接口响应的高层流程。
一、目录里的文件分别做什么?
| 文件 | 角色 | 代码中能确认的行为 |
|---|---|---|
detail.py | 业务请求编排 | 构造 Query、调用 ExecJS、追加a_bogus、发起 detail GET |
19ab.js | 长签名入口 | getABogus混入参数、data、UA、时间和随机字段,再做自定义编码 |
a_bogus_pure.js | 可读对照实现 | DualBogusSigner同时提供短签名和长签名接口 |
a_bogus164.js | 同类历史实现 | generate_rc4_bb_str→generate_random_str→generate_a_bogus |
图 2:业务入口、浏览器上下文、参数调度和 signer 的职责边界。
二、detail.py的请求链路
detail.py的核心流程可以压缩成六步:
- 准备
device_platform、aid、channel、aweme_id等业务参数; - 补齐浏览器语言、平台、屏幕、CPU、内存、网络和版本字段;
- 放入
webid、msToken、verifyFp、fp等动态上下文; - 用
urllib.parse.urlencode序列化 Query; - 调用
19ab.js的getABogus(query, data, userAgent); - 把返回值追加到
params['a_bogus'],再请求/aweme/v1/web/aweme/detail/。
对应的脱敏伪代码如下,值全部是占位符:
params={"device_platform":"webapp","aid":"<AID>","channel":"<CHANNEL>","aweme_id":"<AWEME_ID>","browser_name":"<BROWSER>","browser_version":"<VERSION>","screen_width":"<WIDTH>","screen_height":"<HEIGHT>","webid":"<WEBID>","msToken":"<MS_TOKEN>","verifyFp":"<VERIFY_FP>","fp":"<VERIFY_FP>",}query=urllib.parse.urlencode(params)a_bogus=js.call("getABogus",query,"",USER_AGENT)params["a_bogus"]=a_bogus response=requests.get("https://www.douyin.com/aweme/v1/web/aweme/detail/",params=params,headers=REDACTED_HEADERS,)这里有两个工程重点:先固定 Query,再签名,最后追加a_bogus;签名后不要再偷偷改变参数顺序、编码或 UA。当前源码把空 Body 传成None,而 JavaScript 中又执行data += 'dhzx',在 JavaScript 语义下可能把null转成字符串的一部分。这是一个值得单独验证的实现细节,不能直接当成服务端协议结论。
三、19ab.js的 a_bogus 高层流程
19ab.js的getABogus不是简单的 MD5 拼接,大致可拆成下面几层:
1. 对输入做固定后缀处理
代码会把 Query 和 data 分别拼接固定后缀,再进入后续处理。源码中能看到dhzx字样;这类固定后缀属于实现输入的一部分,版本变化后可能改变。
2. 做双重摘要
get_arr内部实现了 SM3 风格的压缩流程,get_arr29会分别处理参数串和 data,并取摘要中的部分字节参与后续字段组装。文章只解释数据流,不展开常量表和可复用签名材料。
3. 把 UA 与浏览器环境混入字节数组
UA 会经过数组置换、异或和自定义编码;同时,时间戳、两次时间的微小差值、UA 长度、窗口环境、AID、pageId等字段会进入中间数组。也就是说,同一组 Query 换一个 UA、窗口尺寸或时间,输出就可能变化。
4. 随机掩码、重排与校验
代码使用随机字节生成掩码,对数组进行重排并计算异或校验,再把结果拼成字符串。随机性意味着同一输入不一定得到同一串文本,调试时应比较字段长度、字符集和中间阶段,而不是硬编码最终值。
5. 使用自定义 64 字符表编码
最终结果使用定制字符表做 Base64-like 编码,形成约 160 字符左右的a_bogus字符串。当前目录的不同实现和历史样例长度并不完全一致,不能用“长度正确”代替线上兼容性验证。
四、pure / 164 两份实现怎么对照?
a_bogus_pure.js
DualBogusSigner暴露两条入口:
sign_a_bogus(query, userAgent):处理 Query、固定后缀、UA、时间字段和窗口环境,再做 RC4 与s4表编码;sign_x_bogus(query, body):对 Query/Body 做双重摘要,组装短载荷、随机 key 和校验字节,再用 RC4 与s2表编码。
这份文件适合阅读模块边界,但审计发现其中的 SM3 压缩实现和标准 SM3 有差异,且字段装配与a_bogus164.js并非完全等价。因此它更适合作为实验性对照实现,不能直接宣称是真实服务端算法。
a_bogus164.js
这份代码把流程拆成generate_rc4_bb_str、generate_random_str和generate_a_bogus。它同样会混入 Query、UA、窗口环境、时间、AID、pageId和随机字段,再经过 RC4 与自定义 64 表编码。文件末尾带有历史测试调用,版本字段、UA 和示例环境互相并不完全一致,这也是版本漂移的直接证据。
五、浏览器参数为什么比 aweme_id 更容易出问题?
Web 请求通常可以分成四层:
- 业务层:
aweme_id、接口路径、分页或场景字段; - 版本层:
device_platform、aid、channel、version_code、version_name; - 能力层:屏幕尺寸、CPU 核数、内存、网络类型、浏览器和渲染引擎字段;
- 身份层:
webid、msToken、verifyFp/fp、Cookie 和 referer。
这些字段不是越多越好,而是要互相一致。例如:
- UA 声称 Chrome 版本与
browser_version不匹配; - Windows 平台与移动端字段混用;
- Query 编码前后发生二次转义;
- 签名前使用一个 UA,发请求时又换了另一个 UA;
- Cookie、
msToken、verifyFp已过期,却只重新生成a_bogus。
当前目录还存在历史快照之间的环境不一致:detail.py的屏幕/UA 参数、19ab.js内部环境字符串,以及pure/164里的固定窗口字段并不完全相同。它们应视为不同版本或实验样例,不能拼接成一套“标准环境”。
图 3:字段名和职责可以展示,真实 Cookie、Token、webid 和完整签名不要公开。
六、最容易踩的坑
1. 把“六神”当作官方固定名称
当前目录的代码围绕a_bogus/X-Bogus,而不是 Android 七神容器。文章、标题和对外宣传应明确 Web 侧视角。
2. 把硬编码 Cookie 当成长期方案
detail.py内有完整样式的 Cookie、msToken、verifyFp和webid。它们可能过期、失效或属于特定会话,不能原样复制到博客、日志、截图或示例代码。
3. 只看签名长度
本地函数能返回 16 字符或约 160 字符,只能说明输出走通。没有官方 golden vector、服务端对照和目标版本样本时,不能把长度、字符集或 Base64 外观当作线上验签。
4. 忽略随机与时间
a_bogus过程混入Date.now()和随机字段;重复运行出现不同结果是预期现象。回归测试应固定输入、记录时间窗口,并比较中间摘要与结构。
5. 把 pure.js 当成标准实现
当前 pure 版本与标准 SM3、a_bogus164.js的字段装配存在差异。它适合做阅读和模块拆分,不适合直接宣传成现网通杀方案。
6. 只看 HTTP 200
需要同时记录 HTTP 状态、业务 JSON、响应体大小、错误字段和分页状态。请求成功返回 200,不代表业务层已经接受全部上下文。
7. 忽略 JavaScript 运行时差异
19ab.js和a_bogus164.js存在隐式全局变量、加载即执行测试输出等写法;放进严格模式、CommonJS 或长期驻留进程后,可能出现状态污染、重复初始化或ReferenceError。部署前应把函数封装进明确模块,隔离随机状态,并为目标 Node/ExecJS 运行时单独做回归。
七、建议的脱敏调试基线
推荐“一条请求、一个 UA、一个固定 Query、一个占位会话”开始:
- 记录参数名、编码后 Query 的长度和摘要;
- 用
<WEBID>、<MS_TOKEN>、<VERIFY_FP>、<COOKIE_REDACTED>替换敏感值; - 记录
a_bogus/X-Bogus的长度、字符集和生成耗时; - 对比签名前 Query 与最终 URL,确认只新增预期字段;
- 记录 UA、屏幕、平台和版本字段是否一致;
- 把服务端响应和版本信息加入 golden case;
- 不把本地格式检查写成“永久线上可用”。
本地脱敏检查可以验证:sign_a_bogus输出约 159 字符、sign_x_bogus输出 16 字符,字符集落在自定义 Base64 范围内;这只是结构性结果,不代表服务端验签通过。
结语:真正难的是把浏览器上下文对齐
真正影响稳定性的,是 Query 顺序、URL 编码、UA、窗口环境、时间随机性、动态会话和接口版本能否处在同一条请求链上。
本文只对本地web目录做结构化阅读,不公开任何可复用凭据,也不把实验性实现包装成官方算法。
说明:版本、页面脚本和服务端策略会变化。本文用于协议阅读、兼容性测试和工程排错;真实 Cookie、Token、webid、verifyFp 与完整签名值均不应公开。