-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
244 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# 设备各类标识算法(APP 端) | ||
|
||
## 设备唯一标识 BUVID | ||
|
||
注意区分于 Web 端的 buvid3, buvid4. | ||
|
||
BUVID 在 APP 首次安装于某设备, 且首次启动时生成. | ||
|
||
APP 首次(即每次安装后)启动, 会向云端发送本机各类设备特征, 含 `AndroidId`, `DrmId` 等, 请求是否有匹配的 BUVID, 有就使用云端的, 否则使用本地生成的. | ||
|
||
APP 请求是否有匹配的 BUVID 发送的本机各类设备特征包括(但不限于): | ||
|
||
+ `AndroidID` | ||
+ `DrmId` | ||
+ `IMEI` | ||
+ `OAID` | ||
+ 手机网卡 `MAC` | ||
+ 设备品牌 | ||
+ 设备 Model | ||
+ 本地生成的 BUVID | ||
|
||
### 生成方法 | ||
|
||
1. 选定设备特征码, 可以是 `AndroidID`, `DrmId`, 手机网卡 `MAC` 等. 记为 `ID`. 特别地, `MAC` 应当去掉 `:`, `GUID`(即 UUID) 应当去掉 `-`. | ||
|
||
2. 计算 `ID` 的 MD5. 记为 `ID_MD5`. | ||
|
||
3. 从 `ID_MD5` 抽取第 3, 13, 23 位, 失败就默认为 000, 记为 `ID_E`. | ||
|
||
4. 根据选定的设备特征码类型确定 BUVID Prefix, 见附录. 记为 `BUVID_Prefix`. | ||
|
||
5. 按 `{BUVID_Prefix}{ID_E}{ID_MD5}` 的顺序连接起来, 共37位(2+3+32). 结果应当为大写. | ||
|
||
### Demo | ||
|
||
#### Rust | ||
|
||
代码及测试样例见 [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=40b5906cf3838a60efa83fa368b15147). | ||
|
||
## 设备指纹 fp (fp_local, fp_remote) | ||
|
||
用于请求账户相关 REST API, 及 gRPC Metadata 生成. | ||
|
||
在请求头中, `fp_local` 和 `fp_remote` 设置为同一值即可, 暂不清楚区别. | ||
|
||
### 生成方法 | ||
|
||
1. 获取 BUVID. 此处一般使用 XU Prefix 的 BUVID. | ||
|
||
2. 获取设备 Model(`Build.MODEL`), 如 `NOH-AN01`. | ||
|
||
3. 获取手机无线电固件版本号(`Build.getRadioVersion()`), 失败则留空. 如 `21C20B686S000C000,21C20B686S000C000`. | ||
|
||
4. 按前述顺序拼接字符串, 计算得 MD5. | ||
|
||
5. 获取年月日, 格式 `yyyyMMddhhmmss`, 拼接到 4 得到的字符串后. | ||
|
||
6. 生成 16 位随机字符串, CharSet 为 `0123456789abcdef`, 拼接到 5 得到的字符串后, 记为 `fp_raw`. | ||
|
||
7. 计算得到一个特殊字符串, 拼接到 `fp_raw` 后, 即得到最终的 `fp`, 特殊字符串算法见下: | ||
|
||
```rust | ||
let mut veri_code = 0; | ||
// 有点像 HEX 的操作 | ||
let fp_raw_sub_str = fp_raw | ||
.as_bytes() // 将字符串 fp_raw 转换为字节数组 | ||
.chunks(2) // 按每两个字节一组进行切分 | ||
.map(|s| unsafe { ::std::str::from_utf8_unchecked(s) }) // 对每一组解析作为 UTF-8 字符串 | ||
.collect::<Vec<_>>(); // 将结果收集到 Vec 中 | ||
// 如果 fp_raw 的长度小于 62, 则向下取偶数减半作为循环终止条件, 否则终止条件为31 | ||
for i in 0..({ | ||
if fp_raw.len() < 62 { | ||
fp_raw.len() - fp_raw.len() % 2 // 取偶数 | ||
} else { | ||
62 | ||
} | ||
} / 2) | ||
{ | ||
// 将每组字符串转换为对应的 16 进制整数, 将转换得到的整数加到 veri_code 上. | ||
veri_code += i32::from_str_radix(fp_raw_sub_str[i], 16).unwrap_or(0); | ||
} | ||
// 最后将 veri_code 对 256 取余, 格式化为两位的 16 进制字符串 | ||
let veri_code = format!("{:0>2x}", veri_code % 256); | ||
``` | ||
|
||
### Demo | ||
|
||
#### Rust | ||
|
||
代码及测试样例见 [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=40b5906cf3838a60efa83fa368b15147). | ||
|
||
## 附录 | ||
|
||
### BUVID Prefix | ||
|
||
|设备特征码|BUVID Prefix|备注| | ||
|:-:|:-:|:-:| | ||
|`AndroidID`|`XX`|| | ||
|`DrmId`|`XU`|| | ||
|`IMEI`|`XZ`|已弃用| | ||
|`GUID`|`XW`|已弃用| | ||
|`MAC`|`XY`|| | ||
|`GoogleId`|`XG`|东南亚版本| | ||
|`FacebookId`|`XF`|东南亚版本| |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,149 @@ | ||
# grpc 接口定义(protobuf 结构体) | ||
# gRPC 接口定义(protobuf 结构体) | ||
|
||
注: | ||
|
||
1. proto 结构体文件按照包名分类,同级放在同一目录中 | ||
1. proto 结构体文件按照包名分类, 同级放在同一目录中 | ||
|
||
2. 暂时无说明文档,稍后添加 | ||
2. gRPC 接口定义全部来自对官方粉版(即大陆版本) APP 的逆向工程, 一般不会有错误, 但是可能有更新, 有实际应用需求的建议自行反编译 APP, 定位到 `com.bapis.*` 自行补足. | ||
|
||
3. 以下文件全部来自 apk 的逆向工程,如有疏漏请包涵 | ||
## gRPC 主机 | ||
|
||
## grpc 主机 | ||
B 站客户端的 gRPC 接口主机包括: | ||
|
||
B 站客户端的 grpc 接口主机为以下服务器 | ||
+ `grpc.biliapi.net` 原生 gRPC 接口 | ||
+ `app.bilibili.com` Failover gRPC 接口 | ||
|
||
> grpc.biliapi.net | ||
> | ||
> app.bilibili.com | ||
实际应用中, 后者速度相对更快. 但是需要设置如 gRPC 超时时间等参数时只能使用前者. | ||
|
||
## grpc 鉴权 | ||
## gRPC 鉴权 | ||
|
||
需要在请求 http 头部中添加`access_key`,如下 | ||
需要在 Metadata 中添加 `authorization`: `identify_v1 {access_key}`. | ||
|
||
``` | ||
authorization:identify_v1 {access_key} | ||
``` | ||
## gRPC Metadata | ||
|
||
参考 [gRPC Go 官方文档](https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md) 对 `Metadata` 的说明. | ||
|
||
gRPC 的 `Metadata` 简单理解,就是 HTTP 的 Header 中的 key-value 对, 本质上是一个 Map. 在 gRPC `Metadata` 中,key 永远是 String,但是 value 可以是 String 也可以是二进制数据. **需要存储二进制数据时, key 应当加上一个 `-bin` 后缀, 同时二进制 value 应当编码为 Base64**. | ||
|
||
## grpc 头部 | ||
一般而言, 设定 Binary 类型的 `Metadata` 时, 需要调用各个语言的 gRPC 库的相应方法, 库会帮我们编码二进制数据, 无需我们自行编码. | ||
|
||
需要的 `Metadata` 包括(但不限于): | ||
|
||
+ Ascii 类 | ||
+ `user-agent` 客户端 UA, 如 `Dalvik/2.1.0 (Linux; U; Android 12; {device_model} Build/{device_build}) {app_ver} os/android model/{device_model} mobi_app/{mobi_app} build/{app_build} channel/master innerVer/{app_build_inner} osVer/12 network/2 grpc-java-cronet/1.36.1`(其中 `grpc-java-cronet/1.36.1` 为原生 gRPC 接口才需要的). **必需**. | ||
+ `device_model` 设备 Model, 如 `NOH-AN01`. | ||
+ `device_build` 设备 Build, 如 `HUAWEINOH-AN01`. | ||
+ `app_ver` APP 版本号, 如 `7.38.0`. | ||
+ `mobi_app` APP 包类型, 参考 [APPKey.md](/docs/misc/sign/APPKey.md). | ||
+ `app_build` APP 版本号, 如 `7380300`. | ||
+ `app_build_inner` APP 版本号(内部), 如 `7380310`. 实际应用中设置为 `app_build` 即可. | ||
+ `x-bili-gaia-vtoken` 暂时留空. | ||
+ `x-bili-aurora-eid` 如 `UFUFQ1AA`. 算法见附录. 未登录留空. **必需**. | ||
+ `x-bili-mid` 用户 UID, 未登录默认为 0. **必需**. | ||
+ `x-bili-aurora-zone` 留空. **必需**. | ||
+ `x-bili-trace-id` 如 `06e903399574695df75be114ff63ac64:f75be114ff63ac64:0:0`. 算法见附录. **必需**. | ||
+ `authorization` 鉴权, 登录时设定为 `identify_v1 {access_key}`, 未登录时无需此项. | ||
+ `buvid` 设备唯一标识, 算法见 [device_identity.md](/docs/misc/device_identity.md). **必需(?)**. | ||
+ `bili-http-engine` 恒定为 `cronet`, 使用 `grpc.biliapi.net` 作为 gRPC 主机时无需此项. | ||
+ `te` 恒定为 `trailers`, Java gRPC 库固定添加, 使用 `app.bilibili.com` 作为 gRPC 主机时无需此项. | ||
+ Binary 类 | ||
+ `x-bili-fawkes-req-bin` 设备 Fawkes 信息, 使用 [FawkesReq](bilibili/metadata/fawkes/fawkes.proto) 生成. **必需**. | ||
+ `x-bili-metadata-bin` 使用 [Metadata](bilibili/metadata/metadata.proto) 生成. **必需**. | ||
+ `x-bili-device-bin` 设备信息, 使用 [Device](bilibili/metadata/device/device.proto) 生成. **必需**. | ||
+ `x-bili-network-bin` 设备网络信息, 使用 [Network](bilibili/metadata/network/network.proto) 生成. **必需**. | ||
+ `x-bili-restriction-bin` 限制信息, 使用 [Restriction](bilibili/metadata/restriction/restriction.proto) 生成. 本项一般直接传空值即可. **必需**. | ||
+ `x-bili-locale-bin` 设备区域信息, 使用 [Locale](bilibili/metadata/locale/locale.proto) 生成. **必需**. | ||
+ `x-bili-exps-bin` 使用 [Exps](bilibili/metadata/pararbox/pararbox.proto) 生成. 本项一般直接传空值即可. **必需**. | ||
|
||
- [bilibili.metadata](bilibili/metadata):客户端环境参数 | ||
- [bilibili.rpc](bilibili/rpc/status.proto):响应错误信息 | ||
|
||
## 接口请求定义 | ||
|
||
_稍后补充_ | ||
等待补充, 参见 proto 文件注释. 以下仅介绍常用接口: | ||
|
||
+ [bilibili.app.playeronline.v1 -> PlayerOnline](bilibili/app/playeronline/v1/playeronline.proto) 视频在线人数接口. | ||
+ [bilibili.app.playerunite.v1 -> PlayViewUnite](bilibili/app/playerunite/v1/playerunite.proto) United 视频播放链接接口(同时适用于 PGC, UGC 视频). | ||
+ [bilibili.app.playurl.v1 -> PlayURL](bilibili/app/playurl/v1/playurl.proto) UGC 视频播放链接接口(V1 版本). | ||
+ [bilibili.pgc.gateway.player.v1 -> PlayView](bilibili/pgc/gateway/player/v1/playurl.proto) PGC 视频播放链接接口(V1 版本). | ||
+ [bilibili.pgc.gateway.player.v2 -> PlayView](bilibili/pgc/gateway/player/v2/playurl.proto) PGC 视频播放链接接口(V2 版本). | ||
+ [bilibili.polymer.app.search.v1 -> SearchAll, etc](bilibili/polymer/app/search/v1/search.proto) 搜索接口(V1 版本). | ||
+ [bilibili.app.dynamic.v2 -> DynAll, etc](bilibili/app/dynamic/v2/dynamic.proto) 动态接口(V2 版本). | ||
+ ... | ||
|
||
## 示例 | ||
## 应用示例 | ||
|
||
### Golang | ||
|
||
B 站 gRPC API Golang 封装:[XiaoMiku01/bilibili-grpc-api-go](https://github.com/XiaoMiku01/bilibili-grpc-api-go) | ||
|
||
## 附录 | ||
|
||
<details> | ||
<summary>点此展开</summary> | ||
|
||
### `x-bili-aurora-eid` 生成算法 | ||
|
||
```rust | ||
pub fn gen_aurora_eid(uid: u64) -> Option<String> { | ||
if uid == 0 { | ||
return None; | ||
} | ||
let mut result_byte = Vec::with_capacity(64); | ||
// 1. 将 UID 字符串转为字节数组. | ||
let mid_byte = uid.to_string().into_bytes(); | ||
// 2. 将字节数组逐位(记为第 i 位)与 b"ad1va46a7lza" 中第 (i % 12) 位进行异或操作, 作为结果数组第 i 位. | ||
mid_byte.iter().enumerate().for_each(|(i, v)| { | ||
result_byte.push(v ^ (b"ad1va46a7lza"[i % 12])) | ||
}); | ||
// 3. 对字节数组执行 Base64 编码, 注意 no padding, 即得到 x-bili-aurora-eid. | ||
Some(base64::Engine::encode( | ||
&base64::engine::general_purpose::STANDARD_NO_PAD, | ||
result_byte, | ||
)) | ||
} | ||
``` | ||
|
||
### `x-bili-trace-id` 生成算法 | ||
|
||
```rust | ||
pub fn gen_trace_id() -> String { | ||
// 1. 生成 32 位随机字符串 random_id , Charset 为 0~9, a~z. | ||
let random_id = gen_random_string!(32); | ||
let mut random_trace_id = String::with_capacity(40); | ||
// 2. 取 random_id 前 24 位, 作为 random_trace_id. | ||
random_trace_id.push_str(&random_id[0..24]); | ||
// 3. 初始化一个长度为 3 的数组 b_arr, 初始值都为 0. | ||
let mut b_arr: [i8; 3] = [0i8; 3]; | ||
// 并获取当前时间戳 | ||
let mut ts = chrono::Local::now().timestamp(); | ||
// 使用循环从高位到低位遍历 b_arr 数组, 循环体内执行以下逻辑: | ||
// - 首先将 ts 右移 8 位 | ||
// - 然后根据条件向 b_arr 的第 i 位赋值: | ||
// - 如果 (ts / 128) % 2的结果为0, 则 b_arr[i] = ts % 256 | ||
// - 否则 b_arr[i] = ts % 256 - 256 | ||
for i in (0..3).rev() { | ||
ts >>= 8; | ||
b_arr[i] = { | ||
if ((ts / 128) % 2) == 0 { | ||
(ts % 256) as i8 | ||
} else { | ||
(ts % 256 - 256) as i8 | ||
} | ||
} | ||
} | ||
// 4. 将数组 b_arr 中的每个元素逐个转换为两位的十六进制字符串并追加到 random_trace_id 中. | ||
for i in 0..3 { | ||
random_trace_id.push_str(&format!("{:0>2x}", b_arr[i])) | ||
} | ||
// 5. 将 random_id 的第 31, 32 个字符追加到 random_trace_id 中, 此时 random_trace_id 生成完毕, 应当为 32 位长度. | ||
random_trace_id.push_str(&random_id[30..32]); | ||
// 6. 最后, 按 `{random_trace_id}:{random_trace_id[16..32]}:0:0` 的顺序拼接起来, 即为 x-bili-trace-id | ||
let mut random_trace_id_final = String::with_capacity(64); | ||
random_trace_id_final.push_str(&random_trace_id); | ||
random_trace_id_final.push_str(":"); | ||
random_trace_id_final.push_str(&random_trace_id[16..32]); | ||
random_trace_id_final.push_str(":0:0"); | ||
random_trace_id_final | ||
} | ||
``` | ||
|
||
</details> |