Minecraft客户端登录协议问题:公钥字节格式错误
问题描述
我已经实现了服务器列表ping功能,但对接登录协议时遇到问题。通过Wireshark分析发现握手包与登录包被合并发送。我尝试离线登录,根据MC协议文档说明,将hasSigData设为false时无需发送公钥等数据,仅发送了用户名、设为false的hasSigData、设为true的hasPlayerUUID(基于OfflinePlayer:[username]生成的UUID v3)。
现有两个问题:
- 我的登录包编码是否正确?目标服务器处于离线模式。
- 如何拆分合并的数据包?
附Wireshark抓包十六进制截图:
,已标记数据起始位置及握手包、登录包大致分界。
服务器返回错误:
/[My IP] lost connection: Internal Exception: io.netty.handler.codec.DecoderException: Malformed public key bytes
我的代码:
LoginStart Packet (Offline)
public static byte[] getLoginStartPacket(String name) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); DataOutputStream loginStart = new DataOutputStream(buffer); UUID uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)); loginStart.writeByte(PACKET_ID); loginStart.writeChars(name); loginStart.writeBoolean(false); loginStart.writeBoolean(true); MinecraftUUID.writeBytes(loginStart, uuid); return buffer.toByteArray(); }
Sending Packet
byte[] handshakeMessage = Handshake.getHandshakePacket( this.port, this.serverAddress, this.protocolVersion, 2); VarInt.write(dataOutputStream, handshakeMessage.length); dataOutputStream.write(handshakeMessage); byte[] login = LoginOfflineStart.getLoginStartPacket(name); VarInt.write(dataOutputStream, login.length); dataOutputStream.write(login);
解决方案
问题1:登录包编码错误修正
你的登录包有两处核心编码错误,这是导致服务器报错的直接原因:
- 用户名编码不符合协议:MC协议要求字符串用「VarInt前缀+UTF-8字节」存储,你用的
writeChars会把每个字符转为双字节的UTF-16编码,服务器解析时会完全混乱。 - UUID生成规则错误:离线模式的UUID必须基于
"OfflinePlayer:" + 用户名的字节数组生成,你直接用了用户名本身的字节数组,UUID不匹配会引发解析异常。
修正后的登录包代码:
public static byte[] getLoginStartPacket(String name) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); DataOutputStream loginStart = new DataOutputStream(buffer); // 包ID必须用VarInt写入,不能直接writeByte(兼容高版本协议) VarInt.write(loginStart, PACKET_ID); // 正确写入用户名:先写长度VarInt,再写UTF-8字节 byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8); VarInt.write(loginStart, nameBytes.length); loginStart.write(nameBytes); loginStart.writeBoolean(false); // hasSigData设为false,无需后续公钥数据 boolean hasPlayerUUID = true; loginStart.writeBoolean(hasPlayerUUID); if (hasPlayerUUID) { // 按离线模式规则生成UUID String offlinePrefix = "OfflinePlayer:" + name; UUID uuid = UUID.nameUUIDFromBytes(offlinePrefix.getBytes(StandardCharsets.UTF_8)); MinecraftUUID.writeBytes(loginStart, uuid); } return buffer.toByteArray(); }
问题2:数据包合并的处理
TCP是流式传输,多个数据包合并发送是正常现象,不需要手动拆分——MC协议本身就规定了「每个数据包前必须带VarInt类型的长度前缀」,服务器会自动根据这个前缀拆分解析每个数据包。
你当前代码中已经为握手包、登录包分别写入了长度前缀,这是正确的操作。之前的合并现象不是问题,错误根源完全是登录包的编码错误,而非数据包合并。
如果担心数据发送延迟,可以在发送完两个包后调用dataOutputStream.flush(),确保数据立即推送到服务器,但这不是必须的,TCP通常会自动处理。
内容的提问来源于stack exchange,提问作者LoserEXE
相关产品推荐
相关产品推荐

