返回提交历史
Modified
README.md
+10
-6
Modified
docs/login/login_info.md
+130
-74
Renamed
docs/misc/bvid_desc.md
+0
-0
Renamed
docs/misc/errcode.md
+0
-0
Renamed
docs/misc/picture.md
+0
-0
Added
docs/misc/sign/APP.md
+65
-0
Added
docs/misc/sign/APPKey.md
+54
-0
Added
docs/misc/sign/wbi.md
+250
-0
Renamed
docs/misc/time_stamp.md
+0
-0
Deleted
docs/other/API_sign.md
+0
-93
XFEstudio/bilibili-API-collect
添加文档【Wbi 接口签名】,修改目录结构
05ac3d5
代码差异
10 个文件
+509
-173
@@ -58,10 +58,14 @@ B站 API 采用 C/S 结构,大多数接口为 REST API 和 gRPC,少部分接
58
58
59
59
计划整理分类 & 目录:(文档已完结请选中 checkbox)
60
60
61
- [x] [API 签名](docs/other/API_sign.md)
62
- [x] [公共错误码](docs/other/errcode.md)
63
- [x] [图片格式化](docs/other/picture.md)
64
- [x] [bvid 说明](docs/other/bvid_desc.md)
61
- [ ] [接口签名与验证](docs/misc/sign)
62
- [x] [APP API 签名](docs/misc/sign/APP.md)(`appkey`与`sign`)
63
- [x] [已知的 APPKey](docs/misc/sign/APPKey.md)
64
- [x] [Wbi 签名](docs/misc/sign/wbi.md)(`wts`与`w_rid`)
65
66
- [x] [公共错误码](docs/misc/errcode.md)
67
- [x] [图片格式化](docs/misc/picture.md)
68
- [x] [bvid 说明](docs/misc/bvid_desc.md)
65
69
- [ ] [gRPC API 接口定义](grpc_api)
66
70
- [ ] [登录](docs/login)
67
71
- [x] [登录操作 (人机认证)](docs/login/login_action)
@@ -234,8 +238,8 @@ B站 API 采用 C/S 结构,大多数接口为 REST API 和 gRPC,少部分接
234
238
- [ ] [终端网络查询](docs/clientinfo)
235
239
- [x] [基于ip的地理位置查询](docs/clientinfo/ip.md)
236
240
- [x] [终端信息查询](docs/clientinfo/client_info.md)
237
- [ ] [其他](docs/other)
238
- [x] [获取当前时间戳](docs/other/time_stamp.md)
241
- [ ] [其他](docs/misc)
242
- [x] [获取当前时间戳](docs/misc/time_stamp.md)
239
243
- [ ] [web端组件](docs/web_widget)
240
244
- [x] [分区当日投稿数](docs/web_widget/zone_upload.md)
241
245
- [x] [404 页漫画收集](docs/web_widget/404_manga.md)
@@ -27,30 +27,33 @@
27
27
| -------------------- | ---- | ---------------- | ------------------------------------------------- |
28
28
| isLogin | bool | 是否已登录 | false:未登录<br />true:已登录 |
29
29
| email_verified | num | 是否验证邮箱地址 | 0:未验证<br />1:已验证 |
30
| face | str | 用户头像url | |
30
| face | str | 用户头像 url | |
31
31
| level_info | obj | 等级信息 | |
32
| mid | num | 用户mid | |
32
| mid | num | 用户 mid | |
33
33
| mobile_verified | num | 是否验证手机号 | 0:未验证<br />1:已验证 |
34
34
| money | num | 拥有硬币数 | |
35
35
| moral | num | 当前节操值 | 上限为70 |
36
36
| official | obj | 认证信息 | |
37
| officialVerify | obj | 认证信息2 | |
37
| officialVerify | obj | 认证信息 2 | |
38
38
| pendant | obj | 头像框信息 | |
39
| scores | num | 0 | 作用尚不明确 |
39
| scores | num | (?) | |
40
40
| uname | str | 用户昵称 | |
41
41
| vipDueDate | num | 会员到期时间 | 毫秒 时间戳 |
42
42
| vipStatus | num | 会员开通状态 | 0:无<br />1:有 |
43
43
| vipType | num | 会员类型 | 0:无<br />1:月度大会员<br />2:年度及以上大会员 |
44
44
| vip_pay_type | num | 会员开通状态 | 0:无<br />1:有 |
45
| vip_theme_type | num | 0 | 作用尚不明确 |
45
| vip_theme_type | num | (?) | |
46
46
| vip_label | obj | 会员标签 | |
47
47
| vip_avatar_subscript | num | 是否显示会员图标 | 0:不显示<br />1:显示 |
48
48
| vip_nickname_color | str | 会员昵称颜色 | 颜色码 |
49
49
| wallet | obj | B币钱包信息 | |
50
50
| has_shop | bool | 是否拥有推广商品 | false:无<br />true:有 |
51
| shop_url | str | 商品推广页面url | |
52
| allowance_count | num | 0 | 作用尚不明确 |
53
| answer_status | num | 0 | 作用尚不明确 |
51
| shop_url | str | 商品推广页面 url | |
52
| allowance_count | num | (?) | |
53
| answer_status | num | (?) | |
54
| is_senior_member | num | 是否硬核会员 | 0:非硬核会员<br />1:硬核会员 |
55
| wbi_img | obj | Wbi 签名实时口令 | 该字段即使用户未登录也存在 |
56
| is_jury | bool | (?) | |
54
57
55
58
`data`中的`level_info`对象:
56
59
@@ -79,35 +82,42 @@
79
82
80
83
`data`中的`pendant`对象:
81
84
82
| 字段 | 类型 | 内容 | 备注 |
83
| ------ | ---- | ----------- | ------------ |
84
| pid | num | 挂件id | |
85
| name | str | 挂件名称 | |
86
| image | str | 挂件图片url | |
87
| expire | num | 0 | 作用尚不明确 |
85
| 字段 | 类型 | 内容 | 备注 |
86
| ------ | ---- | ----------- | ---- |
87
| pid | num | 挂件id | |
88
| name | str | 挂件名称 | |
89
| image | str | 挂件图片url | |
90
| expire | num | (?) | |
88
91
89
92
`data`中的`vip_label`对象:
90
93
91
94
| 字段 | 类型 | 内容 | 备注 |
92
95
| ----------- | ---- | -------- | ------------------------------------------------------------ |
93
| path | str | 空 | 作用尚不明确 |
96
| path | str | (?) | |
94
97
| text | str | 会员名称 | |
95
98
| label_theme | str | 会员标签 | vip:大会员<br />annual_vip:年度大会员<br />ten_annual_vip:十年大会员<br />hundred_annual_vip:百年大会员 |
96
99
97
100
`data`中的`wallet`对象:
98
101
99
| 字段 | 类型 | 内容 | 备注 |
100
| --------------- | ---- | ------------- | ------------ |
101
| mid | num | 登录用户mid | |
102
| bcoin_balance | num | 拥有B币数 | |
103
| coupon_balance | num | 每月奖励B币数 | |
104
| coupon_due_time | num | 0 | 作用尚不明确 |
102
| 字段 | 类型 | 内容 | 备注 |
103
| --------------- | ---- | ------------- | ---- |
104
| mid | num | 登录用户mid | |
105
| bcoin_balance | num | 拥有B币数 | |
106
| coupon_balance | num | 每月奖励B币数 | |
107
| coupon_due_time | num | (?) | |
108
109
`data`中的`wbi_img`对象:
110
111
| 字段 | 类型 | 内容 | 备注 |
112
| ------- | ---- | ------------------------------- | ---------------------------------------- |
113
| img_url | str | Wbi 签名参数 `imgKey`的伪装 url | 详见文档 [Wbi 签名](../misc/sign/wbi.md) |
114
| sub_url | str | Wbi 签名参数 `subKey`的伪装 url | 详见文档 [Wbi 签名](../misc/sign/wbi.md) |
105
115
106
116
**示例:**
107
117
108
118
```shell
109
119
curl 'https://api.bilibili.com/nav' \
110
-b 'SESSDATA=xxx'
120
-b 'SESSDATA=xxx'
111
121
```
112
122
113
123
<details>
@@ -115,64 +125,110 @@ curl 'https://api.bilibili.com/nav' \
115
125
116
126
```json
117
127
{
118
"code":0,
119
"message":"0",
120
"ttl":1,
121
"data":{
122
"isLogin":true,
123
"email_verified":1,
124
"face":"http://i1.hdslb.com/bfs/face/aebb2639a0d47f2ce1fec0631f412eaf53d4a0be.jpg",
125
"level_info":{
126
"current_level":5,
127
"current_min":10800,
128
"current_exp":17065,
129
"next_exp":28800
128
"code": 0,
129
"message": "0",
130
"ttl": 1,
131
"data": {
132
"isLogin": true,
133
"email_verified": 1,
134
"face": "https://i0.hdslb.com/bfs/face/aebb2639a0d47f2ce1fec0631f412eaf53d4a0be.jpg",
135
"face_nft": 0,
136
"face_nft_type": 0,
137
"level_info": {
138
"current_level": 6,
139
"current_min": 28800,
140
"current_exp": 52689,
141
"next_exp": "--"
142
},
143
"mid": 293793435,
144
"mobile_verified": 1,
145
"money": 172.4,
146
"moral": 70,
147
"official": {
148
"role": 0,
149
"title": "",
150
"desc": "",
151
"type": -1
152
},
153
"officialVerify": {
154
"type": -1,
155
"desc": ""
130
156
},
131
"mid":293793435,
132
"mobile_verified":1,
133
"money":33.4,
134
"moral":70,
135
"official":{
136
"role":0,
137
"title":"",
138
"desc":"",
139
"type":-1
157
"pendant": {
158
"pid": 2511,
159
"name": "初音未来13周年",
160
"image": "https://i0.hdslb.com/bfs/garb/item/4f8f3f1f2d47f0dad84f66aa57acd4409ea46361.png",
161
"expire": 0,
162
"image_enhance": "https://i0.hdslb.com/bfs/garb/item/fe0b83b53e2342b16646f6e7a9370d8a867decdb.webp",
163
"image_enhance_frame": "https://i0.hdslb.com/bfs/garb/item/127c507ec8448be30cf5f79500ecc6ef2fd32f2c.png"
140
164
},
141
"officialVerify":{
142
"type":-1,
143
"desc":""
165
"scores": 0,
166
"uname": "社会易姐QwQ",
167
"vipDueDate": 1707494400000,
168
"vipStatus": 1,
169
"vipType": 2,
170
"vip_pay_type": 0,
171
"vip_theme_type": 0,
172
"vip_label": {
173
"path": "",
174
"text": "年度大会员",
175
"label_theme": "annual_vip",
176
"text_color": "#FFFFFF",
177
"bg_style": 1,
178
"bg_color": "#FB7299",
179
"border_color": "",
180
"use_img_label": true,
181
"img_label_uri_hans": "",
182
"img_label_uri_hant": "",
183
"img_label_uri_hans_static": "https://i0.hdslb.com/bfs/vip/8d4f8bfc713826a5412a0a27eaaac4d6b9ede1d9.png",
184
"img_label_uri_hant_static": "https://i0.hdslb.com/bfs/activity-plat/static/20220614/e369244d0b14644f5e1a06431e22a4d5/VEW8fCC0hg.png"
144
185
},
145
"pendant":{
146
"pid":0,
147
"name":"",
148
"image":"",
149
"expire":0,
150
"image_enhance":""
186
"vip_avatar_subscript": 1,
187
"vip_nickname_color": "#FB7299",
188
"vip": {
189
"type": 2,
190
"status": 1,
191
"due_date": 1707494400000,
192
"vip_pay_type": 0,
193
"theme_type": 0,
194
"label": {
195
"path": "",
196
"text": "年度大会员",
197
"label_theme": "annual_vip",
198
"text_color": "#FFFFFF",
199
"bg_style": 1,
200
"bg_color": "#FB7299",
201
"border_color": "",
202
"use_img_label": true,
203
"img_label_uri_hans": "",
204
"img_label_uri_hant": "",
205
"img_label_uri_hans_static": "https://i0.hdslb.com/bfs/vip/8d4f8bfc713826a5412a0a27eaaac4d6b9ede1d9.png",
206
"img_label_uri_hant_static": "https://i0.hdslb.com/bfs/activity-plat/static/20220614/e369244d0b14644f5e1a06431e22a4d5/VEW8fCC0hg.png"
207
},
208
"avatar_subscript": 1,
209
"nickname_color": "#FB7299",
210
"role": 3,
211
"avatar_subscript_url": "",
212
"tv_vip_status": 0,
213
"tv_vip_pay_type": 0,
214
"tv_due_date": 1640793600
151
215
},
152
"scores":0,
153
"uname":"社会易姐QwQ",
154
"vipDueDate":1612454400000,
155
"vipStatus":1,
156
"vipType":2,
157
"vip_pay_type":1,
158
"vip_theme_type":0,
159
"vip_label":{
160
"path":"",
161
"text":"年度大会员",
162
"label_theme":"annual_vip"
216
"wallet": {
217
"mid": 293793435,
218
"bcoin_balance": 5,
219
"coupon_balance": 5,
220
"coupon_due_time": 0
163
221
},
164
"vip_avatar_subscript":1,
165
"vip_nickname_color":"#FB7299",
166
"wallet":{
167
"mid":293793435,
168
"bcoin_balance":8,
169
"coupon_balance":5,
170
"coupon_due_time":0
222
"has_shop": true,
223
"shop_url": "https://gf.bilibili.com?msource=main_station",
224
"allowance_count": 0,
225
"answer_status": 0,
226
"is_senior_member": 1,
227
"wbi_img": {
228
"img_url": "https://i0.hdslb.com/bfs/wbi/653657f524a547ac981ded72ea172057.png",
229
"sub_url": "https://i0.hdslb.com/bfs/wbi/6e4909c702f846728e64f6007736a338.png"
171
230
},
172
"has_shop":false,
173
"shop_url":"",
174
"allowance_count":0,
175
"answer_status":0
231
"is_jury": false
176
232
}
177
233
}
178
234
```
此文件没有可显示的逐行差异。
此文件没有可显示的逐行差异。
此文件没有可显示的逐行差异。
@@ -0,0 +1,65 @@
1
# APP API 签名与鉴权
2
3
## APP API 签名特性
4
5
部分客户端专用的 REST API 存在基于参数签名的鉴权,需要使用规定的`appkey`及其对应的`appsec`与原始请求参数进行签名计算,部分`AppKey`及与之对应的`AppSec`已经被公开:见该文档 [APPKey](APPKey.md)
6
7
- 不同 `appkey` 对应不同的 app (如客户端、概念版、必剪、漫画、bililink等)
8
9
- 不同平台同 app 也会存在不同的 `appkey` (如安卓端、ios端、TV端等)
10
11
- 同平台同 app 下不同功能也会存在不同的 `appkey`(如登录专用、取流专用等)
12
13
- 不同版本的客户端的 `appkey` 也可能不同
14
15
- **appkey与appsec一一对应**
16
17
## APP API 签名算法
18
19
1. 首先为参数中添加`appkey`字段
20
2. 然后按照参数的 Key 重新排序
21
3. 再对这个 Key-Value 进行 url query 序列化,并拼接与之对应的`appsec` (盐) 进行 **md5 Hash 运算**(32-bit 字符小写),该 hash 便是 API 签名
22
4. 最后在参数尾部增添`sign`字段,它的 Value 为上一步计算所得的 hash,一并作为表单或 Query 提交
23
24
## Demo
25
26
该 Demo 提供 [Python](#Python) 语言例程
27
28
使用 appkey = `1d8b6e7d45233436`, appsec = `560c52ccd288fed045859ed18bffd973` 对如下 `params` 参数进行签名
29
30
上述示例`appkey`、`AppSec`均来自文档 [APPKey](APPKey.md)
31
32
### Python
33
34
```python
35
import hashlib
36
import urllib.parse
37
38
def appsign(params, appkey, appsec):
39
'为请求参数进行 APP 签名'
40
params.update({'appkey': appkey})
41
params = dict(sorted(params.items())) # 按照 key 重排参数
42
query = urllib.parse.urlencode(params) # 序列化参数
43
sign = hashlib.md5((query+appsec).encode()).hexdigest() # 计算 api 签名
44
params.update({'sign':sign})
45
return params
46
47
appkey = '1d8b6e7d45233436'
48
appsec = '560c52ccd288fed045859ed18bffd973'
49
params = {
50
'id':114514,
51
'str':'1919810',
52
'test':'いいよ,こいよ',
53
}
54
signed_params = appsign(params, appkey, appsec)
55
query = urllib.parse.urlencode(signed_params)
56
print(signed_params)
57
print(query)
58
```
59
60
输出内容分别是进行 APP 签名的后参数的 key-Value 以及 url query 形式
61
62
```
63
{'appkey': '1d8b6e7d45233436', 'id': 114514, 'str': '1919810', 'test': 'いいよ,こいよ', 'sign': '01479cf20504d865519ac50f33ba3a7d'}
64
appkey=1d8b6e7d45233436&id=114514&str=1919810&test=%E3%81%84%E3%81%84%E3%82%88%EF%BC%8C%E3%81%93%E3%81%84%E3%82%88&sign=01479cf20504d865519ac50f33ba3a7d
65
```
@@ -0,0 +1,54 @@
1
# APIKey
2
3
以下为已知的 APPkey / APPSec,及部分使用场景参数信息,均来自抓包与逆向工程
4
5
| APPKEY | APPSEC | platform<sup>2</sup> | APP类型 | neuronAppId<sup>1</sup> | mobi_app<sup>2</sup> | 备注 |
6
| :--------------: | :------------------------------: | :------------------: | :----------------: | :---------------------: | :------------------: | :----------------------------------------: |
7
| 9d5889cf67e615cd | 8fd9bb32efea8cef801fd895bef2713d | `android` | Ai4cCreatorAndroid | | | |
8
| 1d8b6e7d45233436 | 560c52ccd288fed045859ed18bffd973 | `android` | 粉版 | `1` | `android` | 获取资源通用 |
9
| 783bbb7264451d82 | 2653583c8873dea268ab9386918b1d65 | `android` | 粉版 | `1` | `android` | 仅获取用户信息时使用(7.X及更新版本) |
10
| 57263273bc6b67f6 | a0488e488d1567960d3a765e8d129f90 | `android` | 粉版 | `1` | `android` | 可能来自旧版 |
11
| 07da50c9a0bf829f | 25bdede4e1581c836cab73a48790ca6e | `android` | 概念版 | `3` | `android_b` | |
12
| 191c3b6b975af184 | | `android` | 概念版 | `3` | `android_b` | 新出现, 仅获取用户信息时使用. 暂未知appsec |
13
| 178cf125136ca8ea | 34381a26236dd1171185c0beb042e1c6 | `android` | 概念版 | `3` | `android_b` | 可能来自旧版 |
14
| 7d336ec01856996b | a1ce6983bc89e20a36c37f40c4f1a0dd | `android` | 概念版 | `3` | `android_b` | 可能来自旧版 |
15
| dfca71928277209b | b5475a8825547a4fc26c7d518eaaa02e | `android` | HD 版 | `5` | `android_hd` | |
16
| bb3101000e232e27 | 36efcfed79309338ced0380abd824ac1 | `android` | 白版 | `14` | `android_i` | |
17
| ae57252b0c09105d | c75875c596a69eb55bd119e74b07cfe3 | `android` | 白版 | `14` | `android_i` | 仅获取用户信息时使用(7.X及更新版本) |
18
| 8e16697a1b4f8121 | f5dd03b752426f2e623d7badb28d190a | `android` | 白版 | `14` | `android_i` | 可能来自旧版 |
19
| 7d089525d3611b1c | acd495b248ec528c2eed1e862d393126 | `android` | 蓝版 | `30` | `bstar_a` | |
20
| iVGUTjsxvpLeuDCf | aHRmhWMLkdeMuILqORnYZocwMBpMEOdt | `android` | - | - | - | 视频取流专用, 仅5.X旧版使用 |
21
| YvirImLGlLANCLvM | JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM | `ios` | - | - | - | 视频取流专用 |
22
| 27eb53fc9058f8c3 | c2ed53a74eeefe3cf99fbd01d8c9c375 | `web`/`ios`? | - | - | - | 第三方授权使用 |
23
| 84956560bc028eb7 | 94aba54af9065f71de72f5508f1cd42e | ? | UWP 版 | - | - | 部分API不接受此appkey, 返回-663错误 |
24
| 85eb6835b0a1034e | 2ad42749773c441109bdc0191257a664 | ? | UWP 版? | - | - | 部分API不接受此appkey, 返回-663错误 |
25
| 4ebafd7c4951b366 | 8cb98205e9b2ad3669aad0fce12a4c13 | `ios` | iPhone 客户端? | `iphone` | ? | |
26
| 8d23902c1688a798 | 710f0212e62bd499b8d3ac6e1db9302a | `android` | AndroidBiliThings | ? | ? | |
27
| 4c6e1021617d40d9 | e559a59044eb2701b7a8628c86aa12ae | `android` | AndroidMallTicket | ? | ? | |
28
| c034e8b74130a886 | e4e8966b1e71847dc4a3830f2d078523 | `android` | AndroidOttSdk | `7` | ? | |
29
| 4409e2ce8ffd12b8 | 59b43e04ad6965f34319062b478f83dd | `android` | 云视听小电视(TV版) | `9`? | `android_tv_yst`? | |
30
| 37207f2beaebf8d7 | e988e794d4d4b6dd43bc0e89d6e90c43 | `android` | BiliLink | ? | ? | |
31
| 9a75abf7de2d8947 | 35ca1c82be6c2c242ecc04d88c735f31 | `android` | BiliScan | ? | ? | |
32
| aae92bc66f3edfab | af125a0d5279fd576c1b4418a3e8276d | ? | PC 投稿工具 | - | ? | |
33
| bca7e84c2d947ac6 | 60698ba2f68e01ce44738920a0ffe768 | ? | login | - | ? | |
34
35
注释:
36
37
<sup>1</sup> `neuronAppId`,产品编号,由数据平台分配,详情如下:
38
39
- 粉(国内版)=1
40
- 白(GooglePlay 版)=2
41
- 蓝(东南亚版)=3
42
- 直播姬=4
43
- HD=5
44
- 海外=6
45
- OTT=7
46
- 漫画=8
47
- TV野版=9
48
- 小视频=10
49
- 网易漫画=11
50
- 网易漫画lite=12
51
- 网易漫画HD=13,
52
- 国际版=14
53
54
<sup>2</sup> `platform`, `mobi_app` 仅供参考, 具体值需要抓包确定.
@@ -0,0 +1,250 @@
1
# Wbi签名
2
3
自 2023 年三月起,B站 Web 端部分接口开始使用 Wbi 鉴权方式,即一种独立于 [APP 鉴权](APP.md) 与其他 Cookie 鉴权的方式,表现在 REST API 请求时在 query 中添加了`w_rid`和`wts`字段,为一种 Web 端的风控手段
4
5
这些接口涵盖”用户投稿视频“、”用户投稿专栏“、”首页推送“、”推广信息“、”热搜“、”视频信息“、”视频取流“、”搜索“等待主要查询性业务接口,如果请求这些 REST API 缺失`w_rid`和`wts`字段,则会在数次请求后返回`-403:非法访问`这样的风控错误
6
7
感谢 [#631](https://github.com/SocialSisterYi/bilibili-API-collect/issues/631) 的研究与逆向工程
8
9
## Wbi签名算法
10
11
1. 获取实时口令
12
13
从 [nav 接口](../../login/login_info.md#导航栏用户信息) 中获取`img_url`、`sub_url`两个字段的参数,并保存备用(如存入 localStorage),相关内容节选如下:
14
15
**注:`img_url`、`sub_url`两个字段的值看似为存于 BFS 中的 png 图片 url,实则只是经过伪装的实时 Token,故无需且不能试图访问这两个 url**
16
17
```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
},
22
```
23
这两个 Key 均为 url 中末尾路径的无扩展名的文件名,即`img_key=653657f524a547ac981ded72ea172057`,`sub_key=6e4909c702f846728e64f6007736a338`
24
25
这两个 Key 的值无关登录 Session 与 IP,属于全站统一使用的,但**每日都会变化**,使用时应做好**缓存和刷新**处理
26
27
2. 打乱重排实时口令
28
29
把上一步获取到的`img_key`拼接在`sub_key`后面**(这里不是`img_url`和`sub_url`)**作为一个整体,将这个整体进行特定的顺序的字符打乱重排,再将重排后的字符串截取前 30 字符的切片,作为一个新的变量`mixin_key`,重排映射表长为 64,内容如下:
30
31
```javascript
32
const mixinKeyEncTab = [
33
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
34
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
35
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
36
36, 20, 34, 44, 52
37
]
38
```
39
40
打乱重排内容如下(以上述第 1 步的参数作为输入)
41
42
```
43
72136226c6a73669787ee4fd02a74c27
44
```
45
46
3. 将欲签名的请求参数排序后编码
47
48
若下方内容为欲签名的请求参数(以 js obj 为例)
49
50
```javascript
51
{
52
foo: '114',
53
bar: '514',
54
baz: 1919810
55
}
56
```
57
58
那么按照 Key 排序并进行 url query 编码后的结果应为:
59
60
```
61
bar=514&baz=1919810&foo=114
62
```
63
64
4. 为参数中添加`wts`时间戳
65
66
`wts`字段的值应为以秒为单位的 Unix TimeStamp,如`1684746387`
67
68
将`wts`参数添加在参数列表最后,即:
69
70
```
71
bar=514&baz=1919810&foo=114&wts=1684746387
72
```
73
74
5. 计算`w_rid`并添加在其后
75
76
在上一步得出的 url query 字符串后拼接第 2 步计算得出的`mixin_key`(作为盐)
77
78
```
79
bar=514&baz=1919810&foo=114&wts=168474638772136226c6a73669787ee4fd02a74c27
80
```
81
82
对这个整体进行 **md5 Hash 运算**(32-bit 字符小写),得到的值便是 Wbi Sign,也就是参数`w_rid`
83
84
```
85
d3cbd2a2316089117134038bf4caf442
86
```
87
88
最后一步,把这个计算出的值作为参数`w_rid`添加在原始参数列表后,也就完成了一次 Wbi Sign,可以调用 REST API 进行请求了
89
90
```
91
bar=514&baz=1919810&foo=114&wts=1684746387&w_rid=d3cbd2a2316089117134038bf4caf442
92
```
93
94
## Wbi签名算法实现Demo
95
96
该 Demo 提供 [Python](#Python)、[JavaScript](#JavaScript) 语言
97
98
### Python
99
100
需要`requests`依赖
101
102
```python
103
from functools import reduce
104
from hashlib import md5
105
import urllib.parse
106
import time
107
import requests
108
109
mixinKeyEncTab = [
110
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
111
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
112
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
113
36, 20, 34, 44, 52
114
]
115
116
def getMixinKey(orig: str):
117
'对 imgKey 和 subKey 进行字符顺序打乱编码'
118
return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, '')[:32]
119
120
def encWbi(params: dict, img_key: str, sub_key: str):
121
'为请求参数进行 wbi 签名'
122
mixin_key = getMixinKey(img_key + sub_key)
123
curr_time = round(time.time())
124
params['wts'] = curr_time # 添加 wts 字段
125
params = dict(sorted(params.items())) # 按照 key 重排参数
126
# 过滤 value 中的 "!'()*" 字符
127
params = {
128
k : ''.join(filter(lambda chr: chr not in "!'()*", str(v)))
129
for k, v
130
in params.items()
131
}
132
query = urllib.parse.urlencode(params) # 序列化参数
133
wbi_sign = md5((query + mixin_key).encode()).hexdigest() # 计算 w_rid
134
params['w_rid'] = wbi_sign
135
return params
136
137
def getWbiKeys() -> tuple[str, str]:
138
'获取最新的 img_key 和 sub_key'
139
resp = requests.get('https://api.bilibili.com/x/web-interface/nav')
140
resp.raise_for_status()
141
json_content = resp.json()
142
img_url: str = json_content['data']['wbi_img']['img_url']
143
sub_url: str = json_content['data']['wbi_img']['sub_url']
144
img_key = img_url.rsplit('/', 1)[1].split('.')[0]
145
sub_key = sub_url.rsplit('/', 1)[1].split('.')[0]
146
return img_key, sub_key
147
148
img_key, sub_key = getWbiKeys()
149
150
signed_params = encWbi(
151
params={
152
'foo': '114',
153
'bar': '514',
154
'baz': 1919810
155
},
156
img_key=img_key,
157
sub_key=sub_key
158
)
159
query = urllib.parse.urlencode(signed_params)
160
print(signed_params)
161
print(query)
162
```
163
164
输出内容分别是进行 Wbi 签名的后参数的 key-Value 以及 url query 形式
165
166
```
167
{'bar': '514', 'baz': '1919810', 'foo': '114', 'wts': '1684746387', 'w_rid': 'd3cbd2a2316089117134038bf4caf442'}
168
bar=514&baz=1919810&foo=114&wts=1684746387&w_rid=d3cbd2a2316089117134038bf4caf442
169
```
170
171
### JavaScript
172
173
需要`axios`、`md5`依赖
174
175
```javascript
176
import md5 from 'md5'
177
import axios from 'axios'
178
179
const mixinKeyEncTab = [
180
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
181
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
182
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
183
36, 20, 34, 44, 52
184
]
185
186
// 对 imgKey 和 subKey 进行字符顺序打乱编码
187
function getMixinKey(orig) {
188
let temp = ''
189
mixinKeyEncTab.forEach((n) => {
190
temp += orig[n]
191
})
192
return temp.slice(0, 32)
193
}
194
195
// 为请求参数进行 wbi 签名
196
function encWbi(params, img_key, sub_key) {
197
const mixin_key = getMixinKey(img_key + sub_key),
198
curr_time = Math.round(Date.now() / 1000),
199
chr_filter = /[!'\(\)*]/g
200
let query = []
201
params = Object.assign(params, {wts: curr_time}) // 添加 wts 字段
202
// 按照 key 重排参数
203
Object.keys(params).sort().forEach((key) => {
204
query.push(
205
encodeURIComponent(key) +
206
'=' +
207
// 过滤 value 中的 "!'()*" 字符
208
encodeURIComponent(('' + params[key]).replace(chr_filter, ''))
209
)
210
})
211
query = query.join('&')
212
const wbi_sign = md5(query + mixin_key) // 计算 w_rid
213
return query + '&w_rid=' + wbi_sign
214
}
215
216
// 获取最新的 img_key 和 sub_key
217
async function getWbiKeys() {
218
const resp = await axios({
219
url: 'https://api.bilibili.com/x/web-interface/nav',
220
method: 'get',
221
responseType: 'json'
222
}),
223
json_content = resp.data,
224
img_url = json_content.data.wbi_img.img_url,
225
sub_url = json_content.data.wbi_img.sub_url
226
return {
227
img_key: img_url.substring(img_url.lastIndexOf('/') + 1, img_url.length).split('.')[0],
228
sub_key: sub_url.substring(sub_url.lastIndexOf('/') + 1, sub_url.length).split('.')[0]
229
}
230
}
231
232
const wbi_keys = await getWbiKeys()
233
234
const query = encWbi(
235
{
236
foo: '114',
237
bar: '514',
238
baz: 1919810
239
},
240
wbi_keys.img_key,
241
wbi_keys.sub_key
242
)
243
console.log(query)
244
```
245
246
输出内容为进行 Wbi 签名的后参数的 url query 形式
247
248
```
249
bar=514&baz=1919810&foo=114&wts=1684805578&w_rid=bb97e15f28edf445a0e4420d36f0157e
250
```
此文件没有可显示的逐行差异。
@@ -1,93 +0,0 @@
1
# API 签名与鉴权
2
3
部分客户端专用的 RESTful API 存在基于 sign 的鉴权,需要使用规定的`appkey`及其对应的`appsec`与原始请求参数进行签名计算
4
5
不同 `appkey` 对应不同的 app (如客户端、概念版、必剪、漫画、bililink等)
6
7
不同平台同 app 也会存在不同的 `appkey` (如安卓端、ios端、TV端等)
8
9
同平台同 app 下不同功能也会存在不同的 `appkey`(如登录专用、取流专用等)
10
11
不同版本的客户端的 `appkey` 也可能不同
12
13
**appkey与appsec一一对应**
14
15
## API签名的计算方式
16
17
首先为参数中添加`appkey`字段,然后按照参数的 key 重新排序,再将重排序后的参数使用 url query 格式序列化拼接与该 appkey 相对应的 appsec (盐值) 进行**md5 hash计算**(32位小写),该 hash 便是 API 签名
18
19
为参数尾部增添`sign`字段,它的值为上一步计算所得的 hash,一并作为表单提交
20
21
**实例:**
22
23
使用 appkey = `1d8b6e7d45233436`, appsec = `560c52ccd288fed045859ed18bffd973` 对如下 `params` 参数进行签名
24
25
```python
26
import hashlib
27
import urllib.parse
28
29
def appsign(params, appkey, appsec):
30
'为请求参数进行 api 签名'
31
params.update({'appkey': appkey})
32
params = dict(sorted(params.items())) # 重排序参数 key
33
query = urllib.parse.urlencode(params) # 序列化参数
34
sign = hashlib.md5((query+appsec).encode()).hexdigest() # 计算 api 签名
35
params.update({'sign':sign})
36
return params
37
38
appkey = '1d8b6e7d45233436'
39
appsec = '560c52ccd288fed045859ed18bffd973'
40
params = {
41
'id':114514,
42
'str':'1919810',
43
'test':'いいよ,こいよ',
44
}
45
signed_params = appsign(params, appkey, appsec)
46
query = urllib.parse.urlencode(signed_params)
47
print(signed_params)
48
print(query)
49
```
50
51
输出以下内容,分别是进行 api 签名后参数的 dict 以及 url query 格式
52
53
```
54
{'appkey': '1d8b6e7d45233436', 'id': 114514, 'str': '1919810', 'test': 'いいよ,こいよ', 'sign': '01479cf20504d865519ac50f33ba3a7d'}
55
appkey=1d8b6e7d45233436&id=114514&str=1919810&test=%E3%81%84%E3%81%84%E3%82%88%EF%BC%8C%E3%81%93%E3%81%84%E3%82%88&sign=01479cf20504d865519ac50f33ba3a7d
56
```
57
## 已知的APPKEY/APPSEC, 及部分参数信息
58
59
| APPKEY | APPSEC | platform<sup>2</sup> | APP类型 | neuronAppId<sup>1</sup> | mobi_app<sup>2</sup> | 备注 |
60
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
61
| 9d5889cf67e615cd | 8fd9bb32efea8cef801fd895bef2713d | `android` | Ai4cCreatorAndroid |
62
| 1d8b6e7d45233436 | 560c52ccd288fed045859ed18bffd973 | `android` | 普通版(粉版) | `1` |`android`| 获取资源通用 |
63
| 783bbb7264451d82 | 2653583c8873dea268ab9386918b1d65 | `android` | 普通版(粉版) | `1` | `android` | 仅获取用户信息时使用(7.X及更新版本) |
64
| 57263273bc6b67f6 | a0488e488d1567960d3a765e8d129f90 | `android` | 普通版(粉版) | `1` |`android`| 可能来自旧版 |
65
| 07da50c9a0bf829f | 25bdede4e1581c836cab73a48790ca6e | `android` | 概念版(蓝版) | `3` | `android_b` |
66
| 191c3b6b975af184 | ******************************** | `android` | 概念版(蓝版) | `3` | `android_b` | 新出现, 仅获取用户信息时使用. 暂未知appsec |
67
| 178cf125136ca8ea | 34381a26236dd1171185c0beb042e1c6 | `android` | 概念版(蓝版) | `3` | `android_b` | 可能来自旧版 |
68
| 7d336ec01856996b | a1ce6983bc89e20a36c37f40c4f1a0dd | `android` | 概念版(蓝版) | `3` | `android_b` | 可能来自旧版 |
69
| dfca71928277209b | b5475a8825547a4fc26c7d518eaaa02e | `android` | HD版 | `5` | `android_hd` |
70
| bb3101000e232e27 | 36efcfed79309338ced0380abd824ac1 | `android` | play版(国际版) | `14` | `android_i` |
71
| ae57252b0c09105d | c75875c596a69eb55bd119e74b07cfe3 | `android` | play版(国际版) | `14` | `android_i` | 仅获取用户信息时使用(7.X及更新版本) |
72
| 8e16697a1b4f8121 | f5dd03b752426f2e623d7badb28d190a | `android` | play版(国际版) | `14` | `android_i` | 可能来自旧版 |
73
| 7d089525d3611b1c | acd495b248ec528c2eed1e862d393126 | `android` | 东南亚版 | `30` | `bstar_a` |
74
| iVGUTjsxvpLeuDCf | aHRmhWMLkdeMuILqORnYZocwMBpMEOdt | `android` | - | - | - | 视频取流专用, 仅5.X旧版使用 |
75
| YvirImLGlLANCLvM | JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM | `ios` | - | - | - | 视频取流专用 |
76
| 27eb53fc9058f8c3 | c2ed53a74eeefe3cf99fbd01d8c9c375 | `web`/`ios`? | - | - | - | 第三方授权使用 |
77
| 84956560bc028eb7 | 94aba54af9065f71de72f5508f1cd42e | ? | UWP版 | - | - | 部分API不接受此appkey, 返回-663错误 |
78
| 85eb6835b0a1034e | 2ad42749773c441109bdc0191257a664 | ? | UWP版? | - | - | 部分API不接受此appkey, 返回-663错误 |
79
| 4ebafd7c4951b366 | 8cb98205e9b2ad3669aad0fce12a4c13 | `ios` | iPhone客户端? | `iphone` | ? |
80
| 8d23902c1688a798 | 710f0212e62bd499b8d3ac6e1db9302a | `android` | AndroidBiliThings | ? | ? |
81
| 4c6e1021617d40d9 | e559a59044eb2701b7a8628c86aa12ae | `android` | AndroidMallTicket | ? | ? |
82
| c034e8b74130a886 | e4e8966b1e71847dc4a3830f2d078523 | `android` | AndroidOttSdk | `7` | ? |
83
| 4409e2ce8ffd12b8 | 59b43e04ad6965f34319062b478f83dd | `android` | 云视听小电视(TV版) | `9`? | `android_tv_yst`? |
84
| 37207f2beaebf8d7 | e988e794d4d4b6dd43bc0e89d6e90c43 | `android` | BiliLink | ? | ? |
85
| 9a75abf7de2d8947 | 35ca1c82be6c2c242ecc04d88c735f31 | `android` | BiliScan | ? | ? |
86
| aae92bc66f3edfab | af125a0d5279fd576c1b4418a3e8276d | ? | PC 投稿工具 | - | ? |
87
| bca7e84c2d947ac6 | 60698ba2f68e01ce44738920a0ffe768 | ? | login | - | ? |
88
89
注释:
90
91
<sup>1</sup> `neuronAppId`, 产品编号,由数据平台分配,粉=1,白=2,蓝=3,直播姬=4,HD=5,海外=6,OTT=7,漫画=8,TV野版=9,小视频=10,网易漫画=11,网易漫画lite=12,网易漫画HD=13, 国际版=14.
92
93
<sup>2</sup> `platform`, `mobi_app` 仅供参考, 具体值需要抓包确定.