查找发送者
发弹幕被找到
最近,我的一个好兄弟遇到了这样一个问题:他在b站发弹幕,结果被人找到了。他对此很困惑:“发送弹幕不是匿名的吗?只有评论才能看到用户名啊,难道发弹幕也可以被找到吗?“
事实上,b站的弹幕确实是匿名的,但是为什么好兄弟却会因为发弹幕被找到呢?发送弹幕的人总是可以被找到吗?下面,我们就来揭秘一下这个问题。
使用工具
如果,你去网上搜索,“怎么样查询弹幕的发送者”,可能最常见到的,是教程告诉你使用这样一个工具,查b站弹幕发送者工具
首先,按照要求,输入一个视频地址:
此时,就查询到了很多弹幕,然后再点击查询,就可以看到弹幕的发送者了。看起来,非常厉害,但是,这是怎么做到的呢?
弹幕查询
获取视频历史弹幕
首先,我们可以通过一个视频,查询到其历史弹幕,例如,再这个函数中,只需要提供一个bv号,就可以得到视频的历史弹幕,以及发送者。
不过需要注意的是,此处的发送者并非明文的发送者,而是通过加密校验的发送者,因此,是不能够直接得到对应用户的。
import requests
from parsel import Selectordef get_danmaku_sender(bvid, agent="bilibili ganbei"):headers = {"user-agent": agent}url = "https://api.bilibili.com/x/web-interface/view"r = requests.get(url, headers=headers, params={"bvid": bvid})oid = r.json().get("data").get("cid")url2 = "https://api.bilibili.com/x/v1/dm/list.so"r2 = requests.get(url2, headers=headers, params={"oid": oid})s = Selector(r2.content.decode())sender_list = []d = s.xpath("//d")for i in d:p = i.xpath("./@p").get()sender = p.split(",")[6]text = i.xpath("./text()").get()sender_list.append((text, sender))return sender_list
crc32校验
b站的用户匿名,使用的是crc32校验,因此,我们也可以通过python编程,将用户的uid,通过crc32,得到其匿名的值。
可以使用binascii
import binasciidata = "1000000000".encode()
crc32_value = binascii.crc32(data)
print(f"CRC32校验码: {crc32_value:08x}")
也可以使用zlib
import binasciidata = "1000000000".encode()
crc32_value = zlib.crc32(data)
print(f"CRC32校验码: {crc32_value:08x}")
实际上,crc32的所有输出可能约是42.95亿。与哈希类似,是有“碰撞”的可能的,也就是,多个输入会对应到同一个输出。换句话说,就是多个不同的用户,可能有相同的匿名结果。
那么,用户的uid是怎么样的呢?这个在用户的个人页面可以看到,是纯数字的。理论上,用户的编号11位就够了。因为11位就是100亿了,如果11位可以以9开头的话,那么足足有近1000亿的账号。即使地球上所有人都要用b站,而且,每个人都要注册10个账号也是足够用的。在这个范围内,平均会碰撞2-3次。
但是很显然,问题并没有那么容易。虽然理论上11位的uid足够使用了,但是,近年来,b站可能出于安全考虑,已经将新用户的uid增加到了16位了,这是一个非常庞大的数字。比如,某位大会员,就有16位的uid,例如:3546672061745379
。
那么,在这么大的范围内,crc32的碰撞又是怎么样的呢?假如,16位的所有账号全都被启用,那么会发生数十万次的碰撞(当然,实际上不可能有那么多账号在用,大多是空号)。在这个数量下,通过匿名的crc32值进行反查是不可能的。
传统位数uid查询
根据之前的分析,16位uid是无法通过常规方式解析出来的。假设,我们不考虑新的16位uid,只考虑传统的6位-11位uid,那么,这是一个有限的范围,因此,可以将所有的结果提前计算好,然后进行存储。在需要的情况下,就可以进行“反查”,类似于md5值的爆破。
这是一个简单的例子,首先将所有计算结果存入redis数据库中(需要注意的是,即使是11位数的有限范围,也包含了数百亿的结果,因此,需要消耗大量的性能与存储空间)
import redis
import zlibr = redis.Redis(host="localhost", port=6379, db=0)def store_crc32_values():for i in range(100000, 20000000000):data = str(i).encode("utf-8")crc_value = zlib.crc32(data)crc_key = f"{crc_value:08x}"r.sadd(crc_key, i)if i % 1000000 == 0:print(f"当前的进度为: {i}")store_crc32_values()
当我们已经存储了所有情况以后,就可以查询所有可能结果了,类似于之前的查询工具。
import redisr = redis.Redis(host="localhost", port=6379, db=0)def query_crc32(crc_value):crc_key = f"{crc_value}"result = r.smembers(crc_key)if result:return [int(x.decode("utf-8")) for x in result]else:return Nonecrc_value_to_query = "efa013c8"
results = query_crc32(crc_value_to_query)if results:print(f"CRC32值{crc_value_to_query},对应的原始输入为:{results}")
else:print("未找到对应的原始输入!")
此时,找到的值,就是一个可能的弹幕发送者。如果对应了多个值,那么就说明是这几位用户中的一个。后续还要依次查看所有用户,来进一步分析可能是哪位用户。
最终结论
现在,我们知道了,根据弹幕来查找用户是可能的,但不总是准确的,也不总是能找到。
其中,如果是一个老用户,是普通的11位以下的uid,那么是可以找到发送者列表的,他可能包含了几位可能的发送者,通过进一步分析,大概可以找到发送的用户(需要注意的是,由于我们不确定是老用户发送的,还是新用户发送的,该方法已经不准确了,因为,可能是未包含在发送者列表的新用户发送的,且恰好与老用户有相同匿名)。
但是,如果是一位新用户,用的是16位的uid,那么几乎是不可能找到对应的发送者的(特别情况下,根据b站新用户uid生成规则分析,可能可以锁定一个小的范围,例如,16位数其实只有包含了明确规则的一部分被启用。这样,可以大大缩小需要分析的范围)。因为这个范围太过广泛,包含了数十万可能性,根本无从分析。
因此,最终结论是,现在通过弹幕来查找用户变得不可能了,你可以找到“嫌疑人”,但并不总是准确。