From 24397b6601e6249bab15b8f9c17481fbfb4e6d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AF=92=E5=BD=A4?= <70561268+cxw620@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:33:33 +0800 Subject: [PATCH] update wbi sign description (#892) --- docs/misc/sign/wbi.md | 126 ++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/docs/misc/sign/wbi.md b/docs/misc/sign/wbi.md index 020b56d..9aa9520 100644 --- a/docs/misc/sign/wbi.md +++ b/docs/misc/sign/wbi.md @@ -1,35 +1,43 @@ -# Wbi签名 +# WBI 签名 -自 2023 年三月起,B站 Web 端部分接口开始使用 Wbi 鉴权方式,即一种独立于 [APP 鉴权](APP.md) 与其他 Cookie 鉴权的方式,表现在 REST API 请求时在 query 中添加了`w_rid`和`wts`字段,为一种 Web 端的风控手段 +自 2023 年 3 月起,Bilibili Web 端部分接口开始采用 WBI 签名鉴权,表现在 REST API 请求时在 Query param 中添加了 `w_rid` 和 `wts` 字段。WBI 签名鉴权独立于 [APP 鉴权](APP.md) 与其他 Cookie 鉴权,目前被认为是一种 Web 端风控手段。 -这些接口涵盖”用户投稿视频“、”用户投稿专栏“、”首页推送“、”推广信息“、”热搜“、”视频信息“、”视频取流“、”搜索“等待主要查询性业务接口,如果请求这些 REST API 缺失`w_rid`和`wts`字段,则会在数次请求后返回`-403:非法访问`这样的风控错误 +经持续观察,大部分查询性接口都已经或准备采用 WBI 签名鉴权,请求 WBI 签名鉴权接口时,若签名参数 `w_rid` 与时间戳 `wts` 缺失、错误,会返回 `v_voucher`(推测为内部记录错误请求的 ID 方便 Debug),如: -感谢 [#631](https://github.com/SocialSisterYi/bilibili-API-collect/issues/631) 的研究与逆向工程 +```json +{"code":0,"message":"0","ttl":1,"data":{"v_voucher":"voucher_******"}} +``` -## Wbi签名算法 +感谢 [#631](https://github.com/SocialSisterYi/bilibili-API-collect/issues/631) 的研究与逆向工程。 -1. 获取实时口令 +细节更新:[#885](https://github.com/SocialSisterYi/bilibili-API-collect/issues/885)。 - 从 [nav 接口](../../login/login_info.md#导航栏用户信息) 中获取`img_url`、`sub_url`两个字段的参数,并保存备用(如存入 localStorage),相关内容节选如下: +## WBI 签名算法 - **注:`img_url`、`sub_url`两个字段的值看似为存于 BFS 中的 png 图片 url,实则只是经过伪装的实时 Token,故无需且不能试图访问这两个 url** +1. 获取实时口令 `img_key`、`sub_key` + + 从 [nav 接口](../../login/login_info.md#导航栏用户信息) 中获取 `img_url`、`sub_url` 两个字段的参数。 + + **注:`img_url`、`sub_url` 两个字段的值看似为存于 BFS 中的 png 图片 url,实则只是经过伪装的实时 Token,故无需且不能试图访问这两个 url** ```json - "wbi_img": { - "img_url": "https://i0.hdslb.com/bfs/wbi/653657f524a547ac981ded72ea172057.png", - "sub_url": "https://i0.hdslb.com/bfs/wbi/6e4909c702f846728e64f6007736a338.png" - }, + {"code":-101,"message":"账号未登录","ttl":1,"data":{"isLogin":false,"wbi_img":{"img_url":"https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png","sub_url":"https://i0.hdslb.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png"}}} ``` - 这两个 Key 均为 url 中末尾路径的无扩展名的文件名,即`img_key=653657f524a547ac981ded72ea172057`,`sub_key=6e4909c702f846728e64f6007736a338` - 这两个 Key 的值无关登录 Session 与 IP,属于全站统一使用的,但**每日都会变化**,使用时应做好**缓存和刷新**处理 + 截取其文件名,分别记为 `img_key`、`sub_key`,如上述例子中的 `7cd084941338484aae1ad9425b84077c` 和 `4932caff0ff746eab6f01bf08b70ac45`。 -2. 打乱重排实时口令 + `img_key`、`sub_key` 全站统一使用,观测知应为**每日更替**,使用时建议做好**缓存和刷新**处理。 - 把上一步获取到的`sub_key`拼接在`img_key`后面 **(这里不是`img_url`和`sub_url`)** 作为一个整体,将这个整体进行特定的顺序的字符打乱重排,再将重排后的字符串截取前 32 字符的切片,作为一个新的变量`mixin_key`,重排映射表长为 64,内容如下: + 特别地,发现部分接口将 `img_key`、`sub_key` 硬编码进 JavaScript 文件内,如搜索接口 `https://s1.hdslb.com/bfs/static/laputa-search/client/assets/index.1ea39bea.js`,暂不清楚原因及影响。 - ```javascript - const mixinKeyEncTab = [ +2. 打乱重排实时口令获得 `mixin_key` + + 把上一步获取到的 `sub_key` 拼接在 `img_key` 后面(下例记为 `raw_wbi_key`),遍历重排映射表 `MIXIN_KEY_ENC_TAB`,取出 `raw_wbi_key` 中对应位置的字符拼接得到新的字符串,截取前 32 位,即为 `mixin_key`。 + + 重排映射表 `MIXIN_KEY_ENC_TAB` 长为 64,内容如下: + + ```rust + const MIXIN_KEY_ENC_TAB: [u8; 64] = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, @@ -37,15 +45,35 @@ ] ``` - 打乱重排内容如下(以上述第 1 步的参数作为输入) + 重排操作如下例: - ``` - 72136226c6a73669787ee4fd02a74c27 + ```rust + fn gen_mixin_key(raw_wbi_key: impl AsRef<[u8]>) -> String { + const MIXIN_KEY_ENC_TAB: [u8; 64] = [ + 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, + 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, + 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52, + ]; + let raw_wbi_key = raw_wbi_key.as_ref(); + let mut mixin_key = { + let binding = MIXIN_KEY_ENC_TAB + .iter() + // 此步操作即遍历 MIXIN_KEY_ENC_TAB,取出 raw_wbi_key 中对应位置的字符 + .map(|n| raw_wbi_key[*n as usize]) + // 并收集进数组内 + .collect::>(); + unsafe { String::from_utf8_unchecked(binding) } + }; + let _ = mixin_key.split_off(32); // 截取前 32 位字符 + mixin_key + } ``` -3. 为参数中添加`wts`时间戳 + 如 `img_key` -> `7cd084941338484aae1ad9425b84077c`、`sub_key` -> `4932caff0ff746eab6f01bf08b70ac45` 经过上述操作后得到 `mixin_key` -> `ea1db124af3c7062474693fa704f4ff8`。 - 若下方内容为欲签名的请求参数(以 js obj 为例) +3. 计算签名(即 `w_rid`) + + 若下方内容为欲签名的**原始**请求参数(以 JavaScript Object 为例) ```javascript { @@ -55,30 +83,25 @@ } ``` - `wts`字段的值应为以秒为单位的 Unix TimeStamp,如`1684746387` + `wts` 字段的值应为当前以秒为单位的 Unix 时间戳,如 `1702204169` - 将`wts`参数添加在参数列表中,即: + 复制一份参数列表,添加 `wts` 参数,即: ```javascript { foo: '114', bar: '514', zab: 1919810, - wts: 1684746387 + wts: 1702204169 } ``` -4. 将欲签名的请求参数排序后编码 + 随后按键名升序排序后编码 URL Query,拼接前面得到的 `mixin_key`,如 `bar=514&foo=114&wts=1702204169&zab=1919810ea1db124af3c7062474693fa704f4ff8`,计算其 MD5 即为 `w_rid`。 - 按照 Key 升序排序并进行 url query 编码后的结果应为: + 需要注意的是:如果参数值含中文或特殊字符等,编码字符字母应当**大写** (部分库会编码为小写字母),空格应当编码为 `%20`(部分库按 `application/x-www-form-urlencoded` 约定编码为 `+`)。 - ``` - bar=514&foo=114&wts=1684746387&zab=1919810 - ``` + 例如: - 请注意,如果参数值为中文或特殊字符,则进行 url query 编码后的字符串中参数值对应的字母必须是**大写字母** (部分库会编码为小写字母) - - 例如 ```javascript { foo: 'one one four', @@ -87,35 +110,17 @@ } ``` - 应该被编码为 + 应该被编码为 `bar=%E4%BA%94%E4%B8%80%E5%9B%9B&baz=1919810&foo=one%20one%20four`。 - ``` - bar=%E4%BA%94%E4%B8%80%E5%9B%9B&baz=1919810&foo=one%20one%20four - ``` +4. 向原始请求参数中添加 `w_rid`、`wts` 字段 -5. 计算`w_rid`并添加在其后 + 将上一步得到的 `w_rid` 以及前面的 `wts` 追加到**原始**请求参数编码得到的 URL Query 后即可,目前看来无需对原始请求参数排序。 - 在上一步得出的 url query 字符串后拼接第 2 步计算得出的`mixin_key`(作为盐) + 如前例最终得到 `bar=514&foo=114&zab=1919810&w_rid=8f6f2b5b3d485fe1886cec6a0be8c5d4&wts=1702204169`。 - ``` - bar=514&foo=114&wts=1684746387&zab=191981072136226c6a73669787ee4fd02a74c27 - ``` +## Demo - 对这个整体进行 **md5 Hash 运算**(32-bit 字符小写),得到的值便是 Wbi Sign,也就是参数`w_rid` - - ``` - 90efcab09403023875b8516f07e9f9de - ``` - - 最后一步,把这个计算出的值作为参数`w_rid`添加在原始参数列表后,也就完成了一次 Wbi Sign,可以调用 REST API 进行请求了 - - ``` - bar=514&foo=114&wts=1684746387&zab=1919810&w_rid=90efcab09403023875b8516f07e9f9de - ``` - -## Wbi签名算法实现Demo - -该 Demo 提供 [Python](#Python)、[JavaScript](#JavaScript)、[Golang](#Golang)、[C#](#CSharp)、[Java](#Java)和[Swift](#Swift) 语言 +含 [Python](#Python)、[JavaScript](#JavaScript)、[Golang](#Golang)、[C#](#CSharp)、[Java](#Java) 和 [Swift](#Swift) 语言编写的 Demo 。 ### Python @@ -186,8 +191,8 @@ print(query) 输出内容分别是进行 Wbi 签名的后参数的 key-Value 以及 url query 形式 ``` -{'bar': '514', 'baz': '1919810', 'foo': '114', 'wts': '1684746387', 'w_rid': 'd3cbd2a2316089117134038bf4caf442'} -bar=514&baz=1919810&foo=114&wts=1684746387&w_rid=d3cbd2a2316089117134038bf4caf442 +{'bar': '514', 'baz': '1919810', 'foo': '114', 'wts': '1702204169', 'w_rid': 'd3cbd2a2316089117134038bf4caf442'} +bar=514&baz=1919810&foo=114&wts=1702204169&w_rid=d3cbd2a2316089117134038bf4caf442 ``` ### JavaScript @@ -899,4 +904,3 @@ func biliWbiSign(param: String, completion: @escaping (String?) -> Void) { } ``` -