深入解析B站androidApp接口:从bilibili.api.ticket.v1.Ticket/GetTicket到SendMsg的技术分析
前言
最近一段时间,我对B站的App接口进行了深入分析,特别是关注了认证机制和私信功能的实现。通过逆向工程和网络抓包,发现了B站移动端API的底层工作原理,包括设备标识生成机制、认证流程和消息传输协议。本文将分享这些研究成果,希望能对API安全设计和防护提供一些思考。
B站接口的基本架构
B站的移动端API采用了gRPC作为主要通信协议,这与普通的REST API有较大区别。gRPC使用Protocol Buffers(protobuf)进行数据序列化,具有更高的效率和更严格的类型检查。
gRPC请求结构
B站的API请求通常包含以下几个关键部分:
[压缩标志(1字节)][消息长度(4字节)][Protobuf消息体]
- 压缩标志:通常是0(不压缩)或1(gzip压缩)
- 消息长度:大端序的4字节整数,表示消息体的长度
- 消息体:使用protobuf编码的实际数据
例如,一个典型的请求可能是:
00 00 00 00 52 0A 2A 08 88 F3 EC 8A 01 10 01 18 E4 C7 A0 85 01 20 E9 8D FF E7 07 28 01 32 0F 7B 22 63 6F 6E 74 65 6E 74 22 3A 22 32 22 7D 80 01 01 2A 24 65 65 62 35 35 63 38 37 2D 66 32 36 34 2D 34 39 64 33 2D 61 61 39 62 2D 63 37 36 34 39 66 36 31 39 34 33 38
其中:
00
- 不使用压缩00 00 00 52
- 消息长度为82字节- 剩余部分 - protobuf编码的消息体
设备标识机制深度解析
BUVID生成机制
BUVID (Bilibili Unique Video ID) 是B站用来唯一标识设备的关键值,其生成过程如下:
def generate_buvid(device_id, buvid_type):
"""
生成BUVID (B站设备唯一标识)
参数:
device_id: 设备ID (根据buvid_type的不同可以是AndroidID、MAC等)
buvid_type: BUVID类型 (XX代表Android ID, XY代表MAC地址)
"""
# 预处理device_id
if buvid_type == BUVIDType.MAC:
device_id = device_id.replace(":", "")
# 计算device_id的MD5值并转为大写
id_md5 = hashlib.md5(device_id.encode('utf-8')).hexdigest().upper()
# ..............(敏感信息脱敏处理)
# 最终BUVID: 类型前缀 + ID_E + MD5
buvid = buvid_type + id_e + id_md5
return buvid
BUVID的格式为:XX/XY
+ 3位特征码
+ 32位MD5
,共37位字符。
设备指纹(fp_local)生成与BUVID的关系
设备指纹(fp_local)与BUVID密切相关,通常用于二次验证设备身份。fp_local的生成依赖于BUVID,同时也考虑了设备型号和无线电版本信息:
def generate_fp(buvid, device_model, device_radio):
"""
根据BUVID生成设备指纹
"""
# 1. 构建基础字符串并计算MD5
base_str = buvid + device_model + device_radio
md5_hex = hashlib.md5(base_str.encode('utf-8')).hexdigest()
# 2. 添加时间戳
time_str = datetime.now().strftime("%Y%m%d%H%M%S")
# 3. 添加随机字符串(16位十六进制)
random_hex = ''.join(random.choice('0123456789abcdef') for _ in range(16))
# 4. 合并所有部分
fp_base = md5_hex + time_str + random_hex
# 5. 计算校验和,这里是关键部分。所以随机生成并不能通过验证calculate_checksum就不公布了
checksum = calculate_checksum(fp_base)
# 6. 返回完整指纹
return fp_base + checksum
关键点:
- fp_local的首32位是由BUVID、设备型号和无线电版本共同决定的MD5值
- 接下来14位是生成时的时间戳
- 再加上16位随机数据
- 最后2位是校验和
BUVID与fp_local的关系验证:
def verify_fp(buvid, fp, device_model, device_radio):
"""验证FP是否与BUVID匹配"""
# 1. 提取FP中的MD5部分(前32个字符)
fp_md5 = fp[:32]
# 2. 计算预期的MD5
base_str = buvid + device_model + device_radio
expected_md5 = hashlib.md5(base_str.encode('utf-8')).hexdigest()
# 3. 验证MD5部分是否匹配
if fp_md5 != expected_md5:
return False
# 4. 验证校验和
fp_base = fp[:-2]
expected_checksum = calculate_checksum(fp_base)
actual_checksum = fp[-2:]
# 5. 返回校验和是否匹配
return expected_checksum == actual_checksum
这种设计确保了:
- 设备更换后无法保持相同的指纹
- 服务器可以验证设备指纹的真实性
- 可以检测设备信息的篡改
Ticket认证机制分析
JWT格式的Ticket结构
Ticket采用JWT(JSON Web Token)格式,由三部分组成:Header、Payload和Signature。
典型的B站Ticket如下:
eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDQ2NTYxNjcsImlhdCI6MTc0NDYyNzA2NywiYnV2aWQiOiJYVUY4MjRFRkQ3NjUyNTE5NzhCOEIyNzlEMDYyODNFQkQ1OTZFIn0.flJFBus6TltpIlD_byS2bM0kzXyQe0o-5ndOnrtuTOc
解析后:
- Header:
{"alg":"HS256","kid":"s03","typ":"JWT"}
- Payload:
{"exp":1744656167,"iat":1744627067,"buvid":"XUF824EFD765251978B8B279D06283EBD596E"}
- Signature: HMAC-SHA256签名
Ticket获取请求的构造
获取Ticket的请求较为复杂,涉及多层嵌套的protobuf消息和签名计算:
def create_ticket_request(self, device_bin):
"""创建Ticket请求数据"""
# 创建请求数据
request_data = bytearray()
# 创建x-fingerprint的值
fingerprint_value = self.create_fingerprint_value()
# 添加字段1: context (x-fingerprint)
# 添加字段1: context (x-exbadbasket)
# 将两个context条目添加到请求中
# 添加字段2: key_id = "ec01"
# 添加字段3: sign
# 压缩数据
compressed_data = gzip.compress(bytes(request_data))
# 构造gRPC消息
# ----------- 此处省略
return bytes(grpc_message)
签名计算的安全性
签名计算是认证系统的核心安全机制:
def calculate_app_sign(self, device_bin, fingerprint_value, exbadbasket_value=b''):
"""计算应用签名"""
# 签名密钥
key = b'E2lc5tgtl' # 注:此处为示例密钥,非真实值
# 将base64编码的device_bin解码
device_bin_data = base64.b64decode(device_bin)
# 创建HMAC对象
h = hmac.new(key, digestmod=hashlib.sha256)
# 按特定顺序更新数据
# 此处脱敏处理,省略
# 获取十六进制摘要
return h.hexdigest()
Protobuf消息的构造与解析
protobuf字段类型
protobuf编码中,每个字段都由标签和值组成:
- 标签:
(field_number << 3) | wire_type
- 值:根据wire_type决定编码方式
常见的wire_type:
- 0: Varint (int32, int64, bool等)
- 1: 64-bit (fixed64, double等)
- 2: Length-delimited (string, bytes, embedded messages等)
- 5: 32-bit (fixed32, float等)
手动构造protobuf消息
没有.proto定义文件时,我们需要手动构造protobuf消息,当然你也可以用现成的库去处理:
def append_tag(self, buf, field_number, wire_type):
"""添加字段标签"""
tag = (field_number << 3) | wire_type
self.append_varint(buf, tag)
def append_varint(self, buf, value):
"""添加varint类型的值"""
while value >= 0x80:
buf.append((value & 0x7f) | 0x80)
value >>= 7
buf.append(value & 0x7f)
def append_string(self, buf, value):
"""添加字符串类型字段"""
encoded = value.encode('utf-8')
self.append_varint(buf, len(encoded)) # 长度前缀
buf.extend(encoded) # 字符串内容
解析protobuf消息
从二进制数据解析protobuf消息也很关键:
def parse_protobuf(data):
"""解析protobuf格式数据"""
result = {}
try:
offset = 0
while offset < len(data):
# 获取字段编号和类型
field_and_type = data[offset]
field_num = field_and_type >> 3
wire_type = field_and_type & 0x07
offset += 1
# 基于wire_type处理不同类型的数据
if wire_type == 0: # Varint
value = 0
shift = 0
while True:
b = data[offset]
offset += 1
value |= ((b & 0x7f) << shift)
if not (b & 0x80):
break
shift += 7
result[str(field_num)] = value
elif wire_type == 2: # Length-delimited
length = 0
shift = 0
while True:
b = data[offset]
offset += 1
length |= ((b & 0x7f) << shift)
if not (b & 0x80):
break
shift += 7
# 尝试解析为字符串、JSON或嵌套消息
try:
string_value = data[offset:offset+length].decode('utf-8')
if string_value.startswith('{') and string_value.endswith('}'):
try:
json_value = json.loads(string_value)
result[str(field_num)] = string_value
except:
result[str(field_num)] = string_value
else:
result[str(field_num)] = string_value
except:
# 嵌套消息或二进制数据
nested_result = parse_protobuf(data[offset:offset+length])
if nested_result:
result[str(field_num)] = nested_result
else:
result[str(field_num)] = data[offset:offset+length].hex()
offset += length
# 处理其他wire_type...
except Exception as e:
print(f"Protobuf解析错误: {e}")
return result
私信发送功能详解
私信消息的protobuf结构
私信消息的protobuf结构如下:
消息 {
field1: { // 内部消息
field1: 接收者UID (VarInt)
field2: 消息类型 (VarInt)
field3: 序列号 (VarInt)
field4: 设备标识 (VarInt)
field5: 标志位 (VarInt)
field6: 消息内容JSON (String)
field16: 标志位 (VarInt)
}
field5: 消息ID (String)
}
构造和发送私信
def create_and_send_message(self, content, receiver_uid, message_id=None):
"""构造并发送B站私信消息"""
# 生成消息ID
if message_id is None:
message_id = str(uuid.uuid4())
# 创建消息数据
message_data = bytearray()
# 添加内部消息
inner_msg = bytearray()
# 添加接收者UID
# 添加消息类型
# 添加序列号
# 添加设备标识
# 添加标志位
# 添加消息内容(JSON格式)
# 添加标志位
self.append_tag(inner_msg, 16, 0)
self.append_varint(inner_msg, 1)
# 将内部消息添加到外层消息
self.append_tag(message_data, 1, 2)
self.append_varint(message_data, len(inner_msg))
message_data.extend(inner_msg)
# 添加消息ID
self.append_tag(message_data, 5, 2)
self.append_string(message_data, message_id)
# 创建gRPC消息
# 准备请求头
headers = {
"APP-KEY": "android64",
"Content-Type": "application/grpc",
"User-Agent": "...",
"buvid": self.buvid,
"x-bili-device-bin": self.device_bin(),
"x-bili-fawkes-req-bin": self.fawkes_req_bin(),
"x-bili-locale-bin": "...",
"x-bili-metadata-bin": self.generate_bili_metadata(),
"authorization": f"identify_v1 {self.access_key}",
"x-bili-ticket": self.ticket
# 其他必要的头信息...
}
# 发送请求
url = "https://app.bilibili.com/bilibili.im.interface.v1.ImInterface/SendMsg"
response = requests.post(url, headers=headers, data=bytes(grpc_message))
return response
解析Base64编码的错误消息
B站的错误响应常常是Base64编码的protobuf消息:
def parse_base64_protobuf(base64_str):
"""解析Base64编码的protobuf数据"""
try:
# 补全padding
padding = len(base64_str) % 4
if padding:
base64_str += '=' * (4 - padding)
# 解码Base64
decoded_data = base64.b64decode(base64_str)
# 解析protobuf
return result
except Exception as e:
print(f"解析Base64 protobuf数据失败: {e}")
return None
例如,错误消息CAISBTIxMDI2GlAKJ3R5cGUuZ29vZ2xlYXBpcy5jb20vYmlsaWJpbGkucnBjLlN0YXR1cxIlCKKkARIf5LiN6IO957uZ6Ieq5bex5Y+R6YCB5raI5oGv5ZOmfg
解析后可能包含"不允许给自己发送消息"的提示。
技术要点分析
1. 多层认证机制
B站采用了多层认证策略:
- BUVID:设备唯一标识
- fp_local:基于BUVID的设备指纹
- Ticket:JWT格式的临时凭证
- access_key:长期授权令牌
这种多层机制增加了伪造身份的难度。
2. 签名验证
请求过程中,多处使用了HMAC签名来确保数据完整性和来源可靠性。例如:
- Ticket请求中对device_bin和fingerprint_value进行签名
- JWT格式的Ticket本身也包含签名
3. 设备指纹算法
设备指纹(fp_local)的生成算法具有以下特点:
- 基于设备特征(BUVID、型号、无线电版本)
- 包含时间戳,允许追踪指纹生成时间
- 添加校验和,验证数据完整性
- 部分随机化,防止完全预测
4. protobuf格式的优势利用
B站充分利用了protobuf的优势:
- 高效的序列化/反序列化
- 严格的类型检查
- 二进制格式难以直接分析和修改
- 版本兼容性好
5. 协议头中各个值的算法
Ascii 类
user-agent
客户端 UA, 必需.
device_model
设备 Model
device_build
设备 Build
app_ver
APP 版本号
mobi_app
APP 包类型
app_build
APP 版本号
app_build_inner
APP 版本号(内部)
x-bili-gaia-vtoken
暂时留空.
x-bili-aurora-eid
未登录留空. 必需.
x-bili-mid
用户 UID, 未登录默认为 0. 必需.
x-bili-aurora-zone
留空. 必需.
x-bili-trace-id
如 16e903399574695df75be114ff63ac64:f75be114ff63ac64:0:0. 需要算法处理. 必需.
authorization
鉴权, 登录时设定为 identify_v1 {access_key}, 未登录时无需此项.
buvid
设备唯一标识, 有自己的一套算法.
bili-http-engine
cronet
te
trailers
Binary 类
x-bili-fawkes-req-bin
设备 Fawkes 信息,加密信息,必须
x-bili-metadata-bin
使用 Metadata 生成. 加密信息,必须
x-bili-device-bin
设备信息, 加密信息,必须
x-bili-network-bin
设备网络信息, 加密信息,必须
x-bili-restriction-bin
限制信息, 加密信息,必须
x-bili-locale-bin
设备区域信息,加密信息,必须
x-bili-exps-bin
加密信息,必须
安全漏洞与防范建议
通过分析,我发现了几个潜在的安全问题及其防范措施:
1. 设备模拟防范
问题:通过分析可以伪造设备信息,包括BUVID和指纹。
防范措施:
- 增加设备特征采集,如硬件序列号、系统指纹等
- 引入设备行为特征分析,识别异常行为模式
- 实施设备绑定策略,限制账号可用设备数量
- 定期更新设备指纹算法,增加逆向难度
2. 签名密钥保护
问题:客户端存储的签名密钥可能被提取。
防范措施:
- 使用白盒加密技术保护客户端密钥
- 实施密钥分散存储和动态派生
- 定期轮换密钥
- 服务端增加额外验证,不完全依赖客户端签名
3. Protobuf结构保护
问题:通过逆向工程可以分析出protobuf结构。
防范措施:
- 混淆字段名和字段编号
- 定期更新协议结构
- 添加冗余或诱饵字段
- 对关键字段进行额外加密
4. JWT安全加固
问题:JWT可能面临重放攻击或篡改。
防范措施:
- 缩短Token有效期
- 实施Token绑定(将Token与特定会话或设备绑定)
- 使用更强的签名算法(如ES256而非HS256)
- 服务端维护已吊销Token列表
5. 请求频率限制
问题:可能出现API滥用。
防范措施:
- 实施基于账号和设备的请求频率限制
- 为敏感操作增加验证码或其他人机验证
- 监控异常请求模式并实施自动封禁
- 对同一设备的多账号操作进行关联分析
实践经验与优化方向
代码优化
在研究过程中,我发现以下编码实践更有效:
# 更清晰的protobuf构造
def create_message(receiver_id, content):
msg = bytearray()
# 使用辅助函数增加可读性
add_varint_field(msg, 1, receiver_id)
add_string_field(msg, 6, json.dumps({"content": content}))
return msg
调试技巧
分析和调试protobuf消息:
def debug_protobuf(hex_data):
"""将十六进制数据解析为人类可读的形式"""
data = binascii.unhexlify(hex_data.replace(" ", ""))
result = parse_protobuf(data)
# 美化输出
return json.dumps(result, indent=2, ensure_ascii=False)
结语
通过对B站App接口的深入分析,我们不仅理解了其身份验证和消息发送机制,也掌握了protobuf消息的构造和解析技术。这些知识不仅适用于B站,也可以应用到其他使用类似技术栈的应用分析中。
对于API开发者,本文揭示了现代移动应用API安全设计的多层次防护策略,以及可能的改进方向。对于研究者,则提供了一个深入理解gRPC和protobuf在实际应用中如何使用的案例。
希望本文对你了解现代移动应用的API实现有所帮助。记住,技术研究应当遵守伦理和法律边界,尊重平台和用户的权益。
本文内容仅供学习和研究使用,请勿用于任何非法或违反相关服务条款的目的。使用本文中的技术进行未授权访问或攻击行为可能违反《网络安全法》等相关法律法规,责任自负,如有需要联系作者可通过 dGcgQGludm9rZXlvdQ==
(base64decode后查看联系方式) 联系我。