update wbi sign description (#892)
This commit is contained in:
parent
494b8c1c85
commit
24397b6601
@ -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 签名算法
|
||||
|
||||
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::<Vec<u8>>();
|
||||
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) {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user