B站登录流程全解析:RSA+极验验证

B站的整个登录流程围绕 RSA 非对称加密与 极验(GeeTest)验证码两大核心技术构建。这套多层校验机制的目的是确保传输安全阻断机器自动化攻击

B站登录时,主站、风控、跨域SSO这三个系统是如何接力完成用户认证的。结合源码和抓包数据,画了张流程图,能更清晰地展示这个完整的调用链。
合作站点 (biligame/huasheng等) B站主站 (passport.bilibili.com) 浏览器 用户 合作站点 (biligame/huasheng等) B站主站 (passport.bilibili.com) 浏览器 用户 登录验证阶段 弹出极验验证码,用户完成滑块/点选 用户明文密码: hash + password,用RSA公钥加密 跨域同步阶段 登录完成 输入账号密码,点击登录 GET /x/passport-login/captcha 返回极验参数 (gt, challenge) GET /x/passport-login/web/key 返回RSA公钥和动态hash POST /x/passport-login/web/login (加密密码+极验凭证) 登录成功,返回 refresh_token GET /x/passport-login/web/sso/list 返回SSO列表(ticket数组) 触发请求 /x/passport-login/web/sso/set?ticket=... Set-Cookie (在合作站点种下登录态) 前端保存 refresh_token,用于后续token续期

复制代码
==================================获取极验参数===================================
https://passport.bilibili.com/x/passport-login/captcha?source=main-fe-header&t=0.5270644240646253&web_location=333.1007&x-bili-locale-json=%7B%22c_locale%22:%7B%22language%22:%22zh%22,%22region%22:%22CN%22%7D,%22always_translate%22:true%7D


{
    "code": 0,
    "message": "OK",
    "ttl": 1,
    "data": {
        "type": "geetest",
        "token": "ce1ab1exxxxxxxxxd4ce6",
        "geetest": {
            "challenge": "972e4d7xxxxxxxxxx4b0388e9",
            "gt": "ac597a4506fexxxxxxxxxxxx66dd4fe"
        },
        "tencent": {
            "appid": ""
        }
    }
}

==============================获取hash和RSA公钥==================================
https://passport.bilibili.com/x/passport-login/web/key?_=177xxxxxxxx51&web_location=333.1007&x-bili-locale-json=%7B%22c_locale%22:%7B%22language%22:%22zh%22,%22region%22:%22CN%22%7D,%22always_translate%22:true%7D

{
    "code": 0,
    "message": "OK",
    "ttl": 1,
    "data": {
        "hash": "80xxxxxx2b",
        "key": "-----BEGIN PUBLIC KEY-----xxxxxxxxxxxxxxxxxxxxxxxxx-----END PUBLIC KEY-----\n"
    }
}

=======================================登录==================================
https://passport.bilibili.com/x/passport-login/web/login?x-bili-locale-json=%7B%22c_locale%22:%7B%22language%22:%22zh%22,%22region%22:%22CN%22%7D,%22always_translate%22:true%7D&b_ret=9Qxxxxxxgg%3D%3DD%2F%2Fxxxxxxxxxxxxxx%3D

{
    "code": 0,
    "message": "OK",
    "ttl": 1,
    "data": {
        "is_new": false,
        "status": 0,
        "message": "",
        "url": "https://passport.biligame.com/x/passport-login/web/crossDomain?DedeUserID=47xxxxxxx03&DedeUserID__ckMd5=9dxxxxxxxx2&Expires=1793796288&SESSDATA=94b8xxxxxxxxx0&gourl=https%3A%2F%2Fwww.bilibili.com%2Fvideo%2FxxxxxxxxxLQ%2F%3Fspm_id_from%3D333.337.search-card.all.click%26vd_source%3Dxxxxxxxxxxxxa17a&first_domain=.bilibili.com",
        "refresh_token": "fe4xxxxxxxxxxxxxxa51",
        "timestamp": 1778244288968,
        "hint": "",
        "in_reg_audit": 0
    }
}

===================================跨域同步=====================================
https://passport.bilibili.com/x/passport-login/web/sso/list?x-bili-locale-json=%7B%22c_locale%22:%7B%22language%22:%22zh%22,%22region%22:%22CN%22%7D,%22always_translate%22:true%7D

{
    "code": 0,
    "message": "OK",
    "ttl": 1,
    "data": {
        "sso": [
            "https://passport.biligame.com/x/passport-login/web/sso/set?ticket=06287xxxxxxxxxxxxc608",
            "https://passport.bilibili.cn/x/passport-login/web/sso/set?ticket=dde9cc97xxxxxxxxxxxxxxxxxxxed48b821",
            "https://passport.huasheng.cn/x/passport-login/web/sso/set?ticket=31dec9f566xxxxxxxxxxxxxxxxx48667"
        ]
    }
}


https://passport.biligame.com/x/passport-login/web/sso/set?ticket=062872exxxxxxxx608&x-bili-locale-json=%7B%22c_locale%22:%7B%22language%22:%22zh%22,%22region%22:%22CN%22%7D,%22always_translate%22:true%7D&b_ret=9QAAxxxxxxxggg%3D%3DD%2F%2FxxxxxxxxxxxxxxxxxxxCYII%3D


https://passport.bilibili.cn/x/passport-login/web/sso/set?ticket=dde9cc97xxxxxxxxxxxxxxxxxxxed48b821&x-bili-locale-json=%7B%22c_locale%22:%7B%22language%22:%22zh%22,%22region%22:%22CN%22%7D,%22always_translate%22:true%7D&b_ret=9QAAxxxxxxxggg%3D%3DD%2F%2FxxxxxxxxxxxxxxxxxxxCYII%3D

https://passport.huasheng.cn/x/passport-login/web/sso/set?ticket=31dec9f566xxxxxxxxxxxxxxxxx48667&x-bili-locale-json=%7B%22c_locale%22:%7B%22language%22:%22zh%22,%22region%22:%22CN%22%7D,%22always_translate%22:true%7D&b_ret=9QAAxxxxxxxggg%3D%3DD%2F%2FxxxxxxxxxxxxxxxxxxxCYII%3D

🤖 请求顺序详解

整个流程的核心在于,B站登录不只是在一个站点完成认证,它需要在一个复杂的跨域生态环境中同步你的登录状态。

  • 1. 验证码预请求 (/captcha) :登录的第一步是带着 source 等参数向此接口发起GET请求,获取验证码服务的类型(geetest)和会话token;随后前端会初始化极验验证码,完成滑块或点选等挑战后,生成一次性验证凭证。
  • 2. 获取加密公钥 (/web/key) :为保护密码安全,请求此接口获取一个动态的hash(加盐值)和一个RSA公钥key
  • 3. 提交密码登录 (/web/login) :客户端用公钥加密hash + 明文密码后,带上用户名、加密后的密码以及极验凭证(如validate, seccode, challenge)等信息,向此接口发送POST请求。成功后,会返回长效的refresh_token,需要妥善保管。
  • 4. 跨域同步登录态 (/web/sso/list.../sso/set?ticket=...) :为了让你在游戏中心等网站也是登录状态,B站会先获取SSO列表,里面包含了一系列票据;然后浏览器带着这些票据去各家兄弟网站签到,各家网站会在响应头里设置Cookie,这样你的登录状态就扩散开来了。

🧩 其他关键参数与错误处理

除了核心的加密凭证,登录请求中还包含两个关键参数:用于指定登录来源(如main-fe-header)和目标跳转地址(如go_url)的source,同时请求必须携带从cookie中获取的bili_jct作为csrf token;另一个关键参数是b_ret,它是环境风控指纹。此外,如果登录失败且返回了特定错误码(如86667, 86669, 86670),通常意味着需要进行手机短信验证,此时应引导用户切换到短信登录流程

B站 miniLogin SDK 的登录加密设计是分层防护的:

  1. 密码加密 :使用 RSA 公钥加密 hash + password,确保传输过程无法被中间人还原。
  2. 人机验证:集成极验(GeeTest)第三代验证,防止自动化攻击。
  3. 环境指纹 :通过 WASM 模块收集浏览器特征,生成 b_ret / b_wet 用于后端风控判断。
  4. 请求完整性:csrf token 防止跨站伪造,元数据头保证请求上下文一致。

如果你需要模拟登录,关键难点在于极验验证码的通过validate/seccode 的生成)以及环境 token 的模拟SecureCollectSDK 内部逻辑极其复杂)。密码本身的加密是标准 RSA,只需实时获取 key 即可。

hashkey (公钥) 通可以通过向B站的 https://passport.bilibili.com/x/passport-login/web/key 接口发送 GET 请求来获取。

📝 获取流程

  1. 请求接口 :向 https://passport.bilibili.com/x/passport-login/web/key 发送一个 GET 请求。
  2. 解析响应 :服务器会返回一个 JSON 对象,其中包含 hashkey 字段。
  3. 提取信息hash 就是加密所需的"盐";key 是 RSA 公钥。

🔄 从旧接口更新

B站的这个接口经历过一次更新:

  • 旧接口https://passport.bilibili.com/login?act=getkey
  • 当前接口https://passport.bilibili.com/x/passport-login/web/key
python 复制代码
import requests
import json

def get_encryption_key():
    """
    从B站获取用于密码加密的 hash 和公钥。
    Returns:
        tuple: (hash_value, public_key) 或 (None, None)
    """
    url = "https://passport.bilibili.com/x/passport-login/web/key"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }
    
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status() # 检查请求是否成功
        data = response.json()
        
        if data.get('code') == 0:
            hash_value = data['data']['hash']
            public_key = data['data']['key']
            print("成功获取 hash 和公钥")
            return hash_value, public_key
        else:
            print(f"接口返回错误: {data.get('message')}")
            return None, None
    except requests.exceptions.RequestException as e:
        print(f"请求发生异常: {e}")
        return None, None

# 示例调用
hash_str, key_str = get_encryption_key()
if hash_str and key_str:
    print(f"获取到 hash: {hash_str}")
    print(f"获取到 key: {key_str}")
python 复制代码
def encrypt_bilibili_password(hash_value, public_key_pem, plain_password):
    """
    模拟B站对密码进行RSA加密。
    :param hash_value: 从 /login?act=getkey 接口获取的 hash
    :param public_key_pem: 从 /login?act=getkey 接口获取的 PEM 格式公钥
    :param plain_password: 用户输入的明文密码
    :return: Base64 编码的加密密文
    """
    # 1. 将 hash 和密码明文进行拼接
    message = hash_value + plain_password
    
    # 2. 导入 PEM 格式的公钥。注意:传入的 key 必须是字节串(bytes)
    rsa_key = RSA.import_key(public_key_pem.encode('utf-8'))
    
    # 3. 使用 PKCS#1 v1.5 填充模式创建加密器(B站前端JSEncrypt默认使用PKCS#1 v1.5填充)
    cipher = PKCS1_v1_5.new(rsa_key)
    
    # 4. 加密消息,并转换为 Base64 字符串
    encrypted_message = cipher.encrypt(message.encode('utf-8'))
    encrypted_base64 = base64.b64encode(encrypted_message).decode('utf-8')
    
    return encrypted_base64

🔗 与登录流程集成

拿到 hashkey 后,你就可以将它们集成到完整的登录流程中了,类似于这样:

python 复制代码
# 1. 获取 hash 和 key
hash_str, key_str = get_encryption_key()

if hash_str and key_str:
    # 2. 加密密码
    password = "用户的明文密码"
    encrypted_password = encrypt_bilibili_password(hash_str, key_str, password)
    
    # 3. 构造登录请求
    login_data = {
        'username': 'your_username',
        'password': encrypted_password,
        # ... 其他验证码等参数
    }
    
    # 4. 发送登录请求
    # login_response = requests.post('https://passport.bilibili.com/web/login', data=login_data)

风控环境参数 (b_ret / b_wet)

在代码中有一个叫 RISK_ENV_TOKEN 的中间件(yo),用于收集设备的风险环境指纹,生成一个 token 添加到请求参数中。

js 复制代码
// 动态加载 SecureCollectSDK
loadScript("//s1.hdslb.com/bfs/seed/jinkela/short/minntaki-wasm-sdk/bili-sc-sdk.umd.js", { lib: "SecureCollectSDK" })
  .then(function(SecureCollectSDK) {
    if (typeof SecureCollectSDK.getEnvToken === "function") {
      var token = SecureCollectSDK.getEnvToken(raw); // raw = true/false
      if (token) {
        var key = raw ? "b_ret" : "b_wet";           // raw 为 true 时用 b_ret
        request.params[key] = token;
      }
    }
  });

说明

  • SecureCollectSDK 是一个 WebAssembly 模块,用于收集浏览器指纹、Canvas、WebGL 等信息。
  • 生成的环境 token 在请求参数中可能命名为 b_ret (原始模式)或 b_wet
  • 登录时会在 /x/passport-login/web/login 中携带该参数,作为风控检测的一部分。

核心生成逻辑与调用关系

b_ret 的生成发生在 RISK_ENV_TOKEN 中间件,具体逻辑如下:

  • 加载SDK :B站动态注入 bili-sc-sdk.umd.js,该文件会初始化 SecureCollectSDK 这个 WASM 模块。
  • 调用方法 :核心方法是 SecureCollectSDK.getEnvToken(raw),该方法内部执行浏览器环境的数据采集、指纹计算和加密,并返回最终的 Token。
  • 参数控制 :方法接受的布尔参数 raw 决定了输出格式:当 raw = true 时生成 b_ret;当 raw = false 时生成 b_wet

具体是怎么拼出来的

  1. 生成 Canvas 指纹 :利用 Fingerprint2 库在 <canvas> 上绘制特定文本和图形,然后用 toDataURL 导出 Base64 图片数据。从完整的 Canvas 指纹字符串中截取最后 20 个字符 作为 canvas
  2. 生成 WebGL 指纹 :获取 WebGL 的渲染器、供应商等信息,拼接成一个长字符串。从完整的 WebGL 指纹字符串中截取最后 50 个字符 作为 webgl_str
  3. 拼接 :最终参数 b_ret = canvas + webgl_str,即完全是这两个截取片段的简单黏合。

结论与验证

你可以直接用下面的代码在浏览器控制台验证,它破解了指纹采集过程,直接拿到 canvaswebgl_str,然后像 SDK 一样拼接,得到的就是 b_ret

javascript

js 复制代码
// 模拟 canvas 指纹截取(需要 Fingerprint2 或自行绘制)
// 这里直接用 Fingerprint2 示例 (需在 B 站页面环境)
Fingerprint2.get({ excludes: {} }, (components) => {
  const canvasValue = components.find(c => c.key === 'canvas').value;
  const canvasStr = Array.isArray(canvasValue) ? String(canvasValue[1]).slice(-20) : '';
  const webglValue = components.find(c => c.key === 'webgl').value;
  const webglStr = Array.isArray(webglValue) ? String(webglValue[0]).slice(-50) : '';
  const b_ret = canvasStr + webglStr;
  console.log('b_ret:', b_ret);
});

这就完全拆解了 b_ret 的生成过程。它就是一个典型的、基于前端 Fingerprint 拼接的环境校验参数,目的不是为了加密,而是为了给后端提供一份稳定的浏览器/设备特征签名。

Uncaught ReferenceError: Fingerprint2 is not defined at :5:1

getEnvironmentInfo 中,它返回一个 promise,内部使用了 b.get (Fingerprint2.get)。但是 Fingerprint2 是在函数内部通过 var b = f(p.exports) 引用的,没有暴露到全局,所以我们无法直接调用 Fingerprint2.get。但我们可以调用 SecureCollectSDK.getEnvironmentInfo() 这个方法。

那么控制台代码可以这样写:

js 复制代码
// 确保 SecureCollectSDK 已初始化
window.SecureCollectSDK.getEnvironmentInfo().then(env => {
  const canvasStr = String(env.canvas || '');
  const webglStr = String(env.webgl_str || '');
  const b_ret = canvasStr + webglStr;
  console.log('b_ret:', b_ret);
});

你可以在控制台运行下面这行代码,直接异步拿到 b_ret 的值:

js 复制代码
window.SecureCollectSDK.getEnvToken(true).then(console.log)

这行代码调用了 SDK 的公开方法,它会:

  1. 异步采集 Canvas 和 WebGL 指纹
  2. 因为参数为 true,直接把这两个指纹片段拼接起来返回给你

其他安全参数

相关推荐
zhaoyong2221 小时前
如何在 MySQL 中实现基于全字段唯一性的重复行计数更新
jvm·数据库·python
X56611 小时前
为什么宝塔面板网站无法正常连接外部远程数据库_检查服务器安全组放行端口并开启IP授权
jvm·数据库·python
woxihuan1234561 小时前
C#怎么使用CancellationToken C#如何用取消令牌优雅地取消异步任务和长时间操作【进阶】
jvm·数据库·python
测试员周周1 小时前
【AI测试功能5】AI功能测试的“黄金数据集“构建指南:从0到1搭建质量评估体系
运维·服务器·开发语言·人工智能·python·功能测试·集成测试
yexuhgu1 小时前
MySQL主从复制支持跨版本吗_不同版本间同步的注意事项
jvm·数据库·python
好运的阿财1 小时前
7天没有打开OpenClaw了
python·机器学习·ai·ai编程·openclaw
十有八七1 小时前
AI Agent的“骨架”之争:四种Harness设计哲学深度解构
前端·人工智能
woxihuan1234561 小时前
CSS怎样调整弹性项目排列顺序_使用order属性轻松控制DOM显示顺序
jvm·数据库·python
卡次卡次11 小时前
14.2:详细补充:子进程会复制什么
前端·python·php