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