注:此协议即将过期

# 流程

  1. Post 请求 https://line1-sdk-center-login-sh.biligame.net/api/client/rsa 获取 rsa 公钥,param 计算流程下方 [1],获取 rsa_keyhash
  2. Post 请求 https://line1-sdk-center-login-sh.biligame.net/api/client/login 进行登录,param 计算流程下方 [2],获取 access_keyuid ,若未能获取 access_key 或错误信息提示需要验证码,请参照 验证码 部分
  3. Post 请求 https://api-sdk.mihoyo.com/bh3_cn/combo/granter/login/v2/login 进行崩坏三渠道服登录,param 计算流程下方 [3],获取 combo_id open_idcombo_token
  4. Post 请求 https://global2.bh3.com/query_dispatch?version={oa_req_key}&t={TimeStampMs} 获取 OA 服务器,其中 oa_req_key游戏版本号_gf_android_bilibiliTimeStampMs 为系统的毫秒时间戳,获取 region_list 中第一个对象中的 dispatch_url
  5. Post 请求 {dispatch_url}?version={oa_req_key}&t={TimeStampMs} ,这两个值同上一步,获取整个对象
  6. 此时使用二维码解码库,对崩坏 3 的扫码登录二维码进行解析获取网址,获取 ticket app_idbiz_key
  7. Post 请求 https://api-sdk.mihoyo.com/{Biz_key}/combo/panda/qrcode/scan 进行扫码流程的第一步,通知扫码流程开始,param 计算流程下方 [4],获取
  8. Post 请求 https://api-sdk.mihoyo.com/{Biz_key}/combo/panda/qrcode/confirm 进行扫码流程的最后一步,确认扫码登录,param 计算流程下方 [5],若 retcode 为 0 则登录成功

# 验证码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"operators": "1",
"merchant_id": "590",
"isRoot": "0",
"domain_switch_count": "0",
"sdk_type": "1",
"sdk_log_type": "1",
"timestamp": "1613035486182",
"support_abis": "x86,armeabi-v7a,armeabi",
"access_key": "",
"sdk_ver": "3.4.2",
"oaid": "",
"dp": "1440*810",
"original_domain": "",
"imei": "330000000142738",
"version": "1",
"udid": "KREhESMUI0F4T3hPM08zSTEAZ1NhAnIYfA==",
"apk_sign": "d1b01b32b10526be2659108204a751d8",
"platform_type": "3",
"old_buvid": "XZ4596E46B8FB6B2152AD5BE95099CF082204",
"android_id": "2e7793608ac77ee7",
"fingerprint": "",
"mac": "08:00:27:B0:96:73",
"server_id": "378",
"domain": "line1-sdk-center-login-sh.biligame.net",
"app_id": "180",
"version_code": "19",
"net": "4",
"pf_ver": "6.0.1",
"cur_buvid": "XZ4596E46B8FB6B2152AD5BE95099CF082204",
"c": "1",
"brand": "Xiaomi",
"client_timestamp": "1613035487431",
"channel_id": "1",
"uid": "",
"game_id": "180",
"ver": "1.4.2-dev",
"model": "MI NOTE 3"
}
  1. 进行时间戳的替换
  2. 使用 [1] 求 sign 的流程,获取 sign
  3. &sign=xxx 拼接在最后
  4. POST 请求 https://api-sdk.mihoyo.com/bh3_cn/combo/granter/login/v2/start_captcha ,获取 gt challenge gt_user_id
  5. 使用浏览器访问 https://help.tencentbot.top/geetest/?captcha_type=1&challenge={challenge}&gt={gt}&userid={gt_user_id}&gs=1 ,进行参数的替换
  6. 完成验证码后将 validate= 后的内容复制保存
  7. 在流程的第 2 步时可以填写对应的值,详情见 [2]

# RSA 公钥 [1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"operators": "1",
"merchant_id": "590",
"isRoot": "0",
"domain_switch_count": "0",
"sdk_type": "1",
"sdk_log_type": "1",
"timestamp": "1631325134181",
"support_abis": "x86,armeabi-v7a,armeabi",
"access_key": "",
"sdk_ver": "3.4.2",
"oaid": "",
"dp": "1440*810",
"original_domain": "",
"imei": "330000000142738",
"version": "1",
"udid": "KREhESMUI0F4T3hPM08zSTEAZ1NhAnIYfA",
"apk_sign": "d1b01b32b10526be2659108204a751d8",
"platform_type": "3",
"old_buvid": "XZ4596E46B8FB6B2152AD5BE95099CF082204",
"android_id": "2e7793608ac77ee7",
"fingerprint": "",
"mac": "08:00:27:B0:96:73",
"server_id": "378",
"domain": "line1-sdk-center-login-sh.biligame.net",
"app_id": "180",
"version_code": "19",
"net": "4",
"pf_ver": "6.0.1",
"cur_buvid": "XZ4596E46B8FB6B2152AD5BE95099CF082204",
"c": "1",
"brand": "Xiaomi",
"client_timestamp": "1631325134402",
"channel_id": "1",
"uid": "",
"game_id": "180",
"ver": "1.4.2-dev",
"model": "MI NOTE 3"
}
  1. 基础 json 文本,使用时将 timestampclient_timestamp 的内容替换为本机毫秒时间戳
  2. 将每个键进行 URL 拼接,形如 operators=1&merchant_id=590……
  3. 下面来计算 sign,首先将上述的所有键按照首字母进行升序排列,后将值进行直接拼接
  4. 在上述字符串后添加盐,本版本的盐为 dbf8f1b4496f430b8a3c0f436a35b931
  5. 将加盐后的字符串进行 MD5 加密,转为小写
  6. &sign=xxx 拼接在最后

# 哔哩哔哩登录 [2]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
"operators": "1",
"merchant_id": "590",
"isRoot": "0",
"domain_switch_count": "0",
"sdk_type": "1",
"sdk_log_type": "1",
"timestamp": "1631325134377",
"support_abis": "x86,armeabi-v7a,armeabi",
"access_key": "",
"sdk_ver": "3.4.2",
"oaid": "",
"dp": "1440*810",
"original_domain": "",
"imei": "330000000142738",
"version": "1",
"udid": "KREhESMUI0F4T3hPM08zSTEAZ1NhAnIYfA",
"apk_sign": "d1b01b32b10526be2659108204a751d8",
"platform_type": "3",
"old_buvid": "XZ4596E46B8FB6B2152AD5BE95099CF082204",
"android_id": "2e7793608ac77ee7",
"fingerprint": "",
"mac": "08:00:27:B0:96:73",
"server_id": "378",
"domain": "line1-sdk-center-login-sh.biligame.net",
"app_id": "180",
"pwd": "Fqjb8WntA1wfXVBods447sJClmdYIo9WvyqlrouD0bC/es3eFPmo7U27HMikCFwLEYw+8xueOSIrySACSY3kINqe/uHeF74aAvGp2U8LXybsxByg+HQUQVjhhxaG7bJKkMqh4ccAWvRYIkyAhVNgsEHV8mz0HvYhN2D2MugrIc0",
"version_code": "19",
"net": "4",
"pf_ver": "6.0.1",
"cur_buvid": "XZ4596E46B8FB6B2152AD5BE95099CF082204",
"c": "1",
"brand": "Xiaomi",
"client_timestamp": "1631325134598",
"channel_id": "1",
"uid": "0",
"game_id": "180",
"user_id": "863450594@qq.com",
"ver": "1.4.2-dev",
"model": "MI NOTE 3"
}
  1. 若有验证码结果,请将 gt_user_id``validate``challenge 填入对应的值,否则请留空
  2. user_id 的值更改为需要进行登录的账户
  3. 进行密码的 RSA 加密,首先将在 RSA 公钥请求中获取到的 hash 与密码明文拼接,之后将拼接的文本使用获取到的 RSA 公钥进行 RSA 加密 (Pkcs1)
  4. 将上一步加密后的密码,替换 pwd 的值
  5. 之后根据 [1] 流程求 sign 的过程,计算一遍 sign,拼接在最后

# 崩坏三渠道服登录 [3]

1
2
3
4
5
6
7
8
9
{
"app_id": "1",
"channel_id": 14,
"data": {
"uid": 0,
"access_key": ""
},
"device": "2e7793608ac77ee7"
}
  1. 将第二步获取到的 uidaccess_key 填入对应值中
  2. 进行求 sign 的流程,此处为崩坏三的 sign 与哔哩哔哩的 sign 不同。首先同样将所有的键按首字母升序的方式进行排序,之后将键值对按 key=value& 的形式进行拼接,注意末尾没有 &
  3. 将上一步的流程使用 HMAC-SHA256 算法处理,此版本的 Key 为 0ebc517adb1b62c6b408df153331f9aa
  4. 将 json 中添加一个名为 sign 的键,请求体为整个 json

# 崩坏三扫码流程_通知扫码 [4]

1
2
3
4
5
6
{
"app_id": "",
"device": "",
"ticket": "",
"ts": "",
}
  1. 将毫秒时间戳替换进 ts 的值,将二维码 URL 中的参数替换进对应的值
  2. 使用 [3] 中求 sign 的流程进行 sign 的计算
  3. 将 json 中添加一个名为 sign 的键,请求体为整个 json

# 崩坏三扫码流程_确认扫码 [5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
"app_id": "",
"device": "",
"ticket": "",
"ts": "",
"sign": "",
"payload": {
"raw": {
"heartbeat": false,
"open_id": "open_id",
"device_id": "2e7793608ac77ee7",
"app_id": "app_id",
"channel_id": "channel_id",
"combo_token": "combo_token",
"asterisk_name": "@水银之翼Bot",
"combo_id": "combo_id",
"account_type": "2"
},
"proto": "Combo",
"ext": {
"data": {
"accountType": "2",
"accountID": "open_id",
"accountToken": "combo_token",
"dispatch": {
"account_url": "oaserver.account_url",
"account_url_backup": "oaserver.account_url_backup",
"asset_boundle_url": "oaserver.asset_boundle_url",
"ex_resource_url": "oaserver.ex_resource_url",
"ext": "oaserver.ext",
"gameserver": "oaserver.gameserver",
"gateway": "oaserver.gateway",
"oaserver_url": "oaserver.oaserver_url",
"region_name": "oaserver.region_name",
"retcode": "0",
"is_data_ready": true,
"server_ext": "oaserver.server_ext"
}
}
}
}
}
  1. 建议使用上一步的 json 进行填充,或将最外层的 app_id``device 等值补充完全
  2. 首先根据前几步获取到的值进行替换
  3. oaserver. 开头的值为在第 4 步获取到的整个对象
  4. oaserver.asset_boundle_url 键不存在,使用 oaserver.asset_bundle_url_list 中随意一个值
  5. oaserver.ex_resource_url 键不存在,使用 oaserver.ex_resource_url_list 中随意一个值
  6. asterisk_name 为最终在游戏界面上展现的用户名,可随意更改
  7. 将整个 json 使用 [3] 中求 sign 的方法获取 sign, payload 只需当做一个值即可 (json 压缩),无需对其内部进行键排序
  8. 最终返回下方的这个 json
1
2
3
4
5
6
7
8
9
10
{
"device": "2e7793608ac77ee7",
"app_id": "app_id",
"ts": "ts",
"ticket": "ticket",
"payload": {
// 上方json中的payload防在这里
},
"sign": ""
}

将字段进行填充,并将 [5] 最一开始填充完成值 json 的 payload 放置在这段 json 的 payload 处,返回即可

# 参考加密算法代码

# MD5

1
2
3
4
5
6
7
using System.Security.Cryptography;
public static string MD5Encrypt(string baseStr)
{
MD5 md5 = MD5.Create();
var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(baseStr));
return BitConverter.ToString(hash).Replace("-", "");
}

# RSA

nuget 包 BouncyCastle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.OpenSsl;
public static byte[] RSAEncrypt(byte[] plainBytes, string publicKey)
{
var encryptEngine = new Pkcs1Encoding(new RsaEngine());
using (var txtreader = new StringReader(publicKey))
{
var keyParameter = (AsymmetricKeyParameter)new PemReader(txtreader).ReadObject();
encryptEngine.Init(true, keyParameter);
}
var encrypted = encryptEngine.ProcessBlock(plainBytes, 0, plainBytes.Length);
return encrypted;
}
public static string RSAEncrypt(string plainString, string publicKey)
{
return Convert.ToBase64String(RSAEncrypt(Encoding.UTF8.GetBytes(plainString), publicKey)).Replace("-", "");
}

# HMAC-SHA256

1
2
3
4
5
6
7
8
9
10
11
using System.Security.Cryptography;
public static string HmacSHA256(string secret, string signKey)
{
string signRet = string.Empty;
using (HMACSHA256 mac = new HMACSHA256(Encoding.UTF8.GetBytes(signKey)))
{
byte[] hash = mac.ComputeHash(Encoding.UTF8.GetBytes(secret));
signRet = BitConverter.ToString(hash).Replace("-", "");
}
return signRet.ToLower();
}

# 错误代码参考

哔哩哔哩游戏 SDK 开放平台文档中心