发送Encryption Response后Minecraft服务器返回异常数据包问题排查
我正在开发Java程序模拟Minecraft客户端登录服务器,已经完成Handshake、Login Start、Encryption Request和Encryption Response数据包的处理,但始终收不到预期的Login Success(包ID 0x02),反而每次读取到随机的数据包ID。本地测试时服务器日志提示用户名会话无效,直接断开连接。
读取数据包代码
// S->C Login Success int loginPacketSize = readVarInt(input); // packet size int loginPacketId = readVarInt(input); // packet id if(loginPacketId != 0x02) { // We want login success System.out.println("Bad packet id: " + loginPacketId); if(loginPacketId == 0x00) { // If it's a disconnect packet disconnected(input); } }
readVarInt方法
public static int readVarInt(DataInputStream in) throws IOException { int i = 0; int j = 0; while (true) { int k = in.readByte(); i |= (k & 0x7F) << j++ * 7; if (j > 5) throw new RuntimeException("VarInt too big"); if ((k & 0x80) != 128) break; } return i; }
错误现象
每次运行输出随机数据包ID:
Bad packet id: 134
Bad packet id: 3757
Bad packet id: 9673
本地服务器日志:
[14:41:32 ERROR]: Username 'EnderPoint_07' tried to join with an invalid session
[14:41:32 INFO]: /127.0.0.1:54157 lost connection: Failed to verify username!
问题根源与修复方案
1. 加密数据流未处理(核心问题)
在发送Encryption Response之后,服务器和客户端之间的所有通信都会使用协商好的共享密钥加密。你现在直接用原始的DataInputStream读取,拿到的是加密后的乱码,自然解析出随机的VarInt值(也就是你看到的随机数据包ID)。
修复:发送完Encryption Response后,立即将输入输出流包装成加密流:
// 假设已生成sharedSecret和服务器公钥 Cipher decryptCipher = Cipher.getInstance("AES/CFB8/NoPadding"); decryptCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sharedSecret, "AES"), new IvParameterSpec(sharedSecret)); Cipher encryptCipher = Cipher.getInstance("AES/CFB8/NoPadding"); encryptCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sharedSecret, "AES"), new IvParameterSpec(sharedSecret)); // 替换原流为加密流 input = new DataInputStream(new CipherInputStream(input, decryptCipher)); output = new DataOutputStream(new CipherOutputStream(output, encryptCipher));
之后所有数据包的读写都要通过这个加密后的流进行。
2. 在线模式验证失败
本地服务器默认开启在线模式,会向Mojang验证用户会话。你的客户端没有完成Mojang的会话认证流程,所以服务器直接拒绝登录,发送Disconnect包(但因为你没解密,读出来的ID是乱的)。
两种解决方式:
- 离线测试:修改服务器
server.properties中的online-mode=false,关闭在线验证,即可正常收到Login Success包。 - 支持在线模式:需要完成Mojang会话认证——用服务器发送的
serverId、共享密钥、公钥生成哈希,向Mojang认证服务器请求会话令牌,服务器验证通过后才会发送Login Success。
3. 修正Disconnect包处理
现在的代码判断loginPacketId == 0x00永远不会触发,因为数据包是加密的。必须先启用加密流,才能正确读取Disconnect包的内容,获取具体的拒绝原因。
内容的提问来源于stack exchange,提问作者EnderPoint

