B站
w_rid 重算比对与 av/bv 互转 —— 两个完全可验的纯函数签名
B站签名验证
B站的签名没有抖音那些常量陷阱:w_rid 是一次带混淆密钥的 MD5,av ↔ bv 是纯 BigInt 位移 —— 两个完全可验的纯函数。验证器跑的就是 @ikenxuan/amagi/signing 那份代码。
同一组 keys、同一个 wts,重算即比对
签名输入
判定
w_rid 对上了 —— 同一组 keys、同一个 wts 重算出的 MD5 与之一字不差。
参与哈希的规范化 query
oid=1&type=1&wts=1767322445
签名输入
判定
↔ (往返一致)
wbi(w_rid + wts)
算法本身简单:
- 从
/nav回包的wbi_img.img_url/sub_url取两个 key(URL 末尾的文件名,去扩展名); - 按 64 位混淆表
mixinKeyEncTab把img_key + sub_key重排、截前 32 位,得到 mixin key; - 参数按 key 排序、value 滤掉
!'()*、wts一起参与排序,拼成规范化 query; w_rid = md5(规范化 query + mixin key)。
所以验证只需同一组 keys + 同一个 wts 重算一遍。两个容易翻车的点:
keys 必须取自同一次 /nav
keys 随 session 滚动。拿今天缓存里的 keys 去验昨天抓的 URL,w_rid 必然对不上 ——
这不代表签名错了,代表 inputs 变了。验证器没法替你判断 keys 的新鲜度。
参与签名的是规范化后的 query
value 里的 !'()* 会先被滤掉再 encodeURIComponent;wts 本身参与排序。
所以「我发的 URL 长这样」和「参与哈希的 query 长那样」可以不同 —— 验证器把规范化
query 折叠展示了出来,对不上的时候先看它。
amagi 侧的实现(WbiSigner)把 keys 缓存在实例上(TTL 30 分钟),sign: 'wbi' 与 sign: 'qtparam' 共用同一次 /nav。签名走 transport(reason: 'prepare' 进 trace),这与验证无关,但意味着 /nav 请求本身在 trace 里可见。
qtparam:不做离线验证的原因
视频流的 qtparam 前置会按登录态打 /nav 取 vipStatus,再决定追加 fnval=4048&fourk=1(VIP)还是 qn=64&fnval=16(非 VIP),最后叠一次 wbi 签名。它不是一个独立的签名算法,而是登录态分支 + wbi——离线验证它就得连 /nav 一起验,那不再是纯函数的事。要看它对不对,直接发请求看回的清晰度档位。
av ↔ bv
纯 BigInt 位移与 58 进制查表,双向互逆。验证器做双向转换加往返自检 —— 往返对不上只有一个可能:号码抄错了。
import { , } from '@ikenxuan/amagi/signing'
.((2)) // 'BV1xx411c7mD'
.(('BV1GJ411x7h7')) // 80433022下一步
为什么签名需要被验证
平台对签名是抽样校验的,「请求成功」证明不了任何事;四个平台的验证器与它们各自的验证强度
抖音
a_bogus / X-Bogus / secsdk / 访客令牌 —— 四个验证器,以及各自的常量表与边界
快手
$HE_ 段可整段拆开(时钟与计数器原样读回);HUDR 段拆不开,说了就是答案
小红书
x-s 的算法本体进不了浏览器 —— 这里只有形状检查;XYW 信封能拆一层 JSON
最后更新于 2026-10-02