当下列条件全部满足时,【无效的玩家档案公钥签名,请尝试重启游戏】错误出现:
- 房客使用第三方皮肤站账户登录(或启用了 HMCL 的离线换肤功能);
- 房客成功获取了 个人信息公钥签名(下简称 签名);
- 房客的 UUID 符合离线账户 UUID 标准格式(
UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8))); - 房客使用 Terracotta | 陶瓦联机 提供的联机大厅进入房间,绕过了正版验证;
- 房主使用局域网开放功能。
- 房主使用正版验证或离线账户。
不满足上述任意一条条件,房客将被正版验证挡下,或正常进入房间。
Minecraft 在完成密钥交换后,服务端将执行正版验证。 如果正版验证失败(条件 #1 #6),客户端和服务端都不会断开连接(条件 #4 #5),而是通过离线账户 UUID 标准格式重新计算玩家 UUID,回传至客户端。
此时,成功获取个人信息公钥签名的客户端(条件 #2)将检查服务器回传的 UUID 是否与当前本地玩家的 UUID 匹配(条件 #3)。
如果匹配,在 Authlib-Injector 的作用下,
客户端将把获取到的签名(实际上是 byte[1])发送至服务端。
服务端收到签名后,将从本地 JAR 中加载硬编码的 Mojang 公钥来验证签名。但由于签名长度为 1 而非 256,验证必定失败。 此时,服务端将主动踢出玩家,并告知客户端【无效的玩家档案公钥签名,请尝试重启游戏】。
Minecraft 在进入多人世界时,会先执行正版验证。如果正版验证失败,客户端将主动结束连接,而服务端将立刻踢出玩家。
但是,如果当前连接的服务器是从局域网中自动发现出来的,并且服务器是局域网开放的客户端, 客户端和服务端都会在正版验证失败后保持连接,继续进行后续步骤。
当完成上述正版验证后,客户端将请求微软服务器获取 个人信息公钥签名。 如果签名获取成功(即当前账户为正版),客户端将主动向服务器递交 个人信息公钥签名。 服务端将用动态获取的证书验证签名以确保 个人信息公钥签名 无误。
如果上述 个人信息公钥签名 验证流程失败,服务器将主动踢出玩家。 如果客户端未提供 个人信息公钥签名 且 服务器强制开启了聊天验证(也称聊天举报)功能,客户端将被禁止发送任何游戏内聊天消息。
通过以上流程,Minecraft 成功实现了:
- 正版玩家将能够正确进服(既然他们能通过正版验证,个人信息公钥签名 也自然正确);
- 离线账户将能够在服务器主动关闭了正版验证时进服(离线账户从微软服务器拉取 个人信息公钥签名 时无法通过鉴权,获取的签名为空;
- 任何玩家能够在没有网络时正常局域网联机(服务器将跳过正版验证)。
Authlib-Injector 在 ProfileKeyFilter.java 向 Minecraft 提交了一个 byte[1] 的 个人信息公钥签名。
response.put("publicKeySignatureV2", "AA==");在 同一文件 中,
Signature sig = new Signature("authlib-injector-dummy-verify") {
...
@Override
protected boolean engineVerify(byte[] sigBytes) {
return true;
}
};抹去了 个人信息公钥签名 的验证过程。
通过以上流程,Authlib-Injector 实现了:
- 允许正版、第三方皮肤站玩家入服(个人信息公钥签名 的验证过程 被抹去);
- 允许离线玩家入服(客户端不会发送 个人信息公钥签名 供服务器验证);
Terracotta | 陶瓦联机 通过在房客端重新广播房间信息,滥用了 Minecraft 允许局域网联机绕过正版验证的功能。
为确保兼容性,HMCL 的离线账户使用离线账户 UUID 标准格式,但利用 Authlib-Injector 实现了换肤功能。