准备:
Handshake Packet格式 🔗
Handshake packet是由Server向Client发送的初始化包,因为所有从Server向Client端发送的包都是一样的格式,前面的四个字节是包头,其中前三位代表Handshake packet数据长度,第四位是包序列号。下面是Handshake packet具体内容的格式:
相对包内容的位置 | 长度(字节) | 名称 | 描述 |
---|---|---|---|
0 | 1 | 协议版本(Protocol) | 协议版本的版本号,通常为10(0x0A)协议版本的版本号,通常为10(0x0A) |
1 | len = strlen (server_version) + 1(0x00表示结束) | 数据库版本(Version) | 长度为数据库版本字符串的长度+上标示结束的的一个字节 |
len + 1 | 4 | 线程ID(Thread ID) | 此次连接MySQL Server启动的线程ID |
len + 5 | 8 + 1(0x00表示结束) | 密码盐值(第一部分)(Salt) | 用于后续账户密码验证 |
len + 14 | 2 | 数据库提供的功能(Server Capabilities) | 用于与客户端协商通讯方式 |
len + 16 | 1 | 编码格式(Server Language) | 标识数据库目前的编码方式 |
len + 17 | 2 | 服务器状态(Server Status) | 用于表示服务器状态,比如是否是事务模式或者自动提交模式 |
len + 19 | 2 | 扩展的协议,数据库提供的功能(Extended Server Capabilities) | 用于与客户端协商通讯方式 |
len + 21 | 1 | 身份验证插件长度(Authentication Plugin Length) | 身份验证插件长度 |
len + 22 | 10 | 保留字节(Unused) | 未来可能会用到,预留字节 |
len + 32 | 12 + 1(0x00表示结束) | 密码盐值(第二部分)(Salt) | 用于后续账户密码验证 |
len + 45 | 21 + 1(0x00表示结束) | 身份验证插件(Authentication Plugin) | 密码加密方式 |
使用的MySQL 5.7.26增加了Extended Server Capabilities
和Authentication Plugin Length
字段,占用了len+19~len+21位的保留字节,现在预留字节还剩余10个字节。其它低版本MySQL预留字节剩余13个。
报文结构:
点击Server Greeting proto=10 version=5.7.26
可以看到从Frame物理层到MySQL Protocol
应用层各层的具体内容。点击MySQL Protocol可以看到具体的十六进制的内容,以及它的ASCII码。
Wireshark
抓包软件中的Packet Length
和Packet Number
中的值74、0分别就是payload_length
sequence_id
。Server Greeting
内是具体的包内容payload
。
MySQL完整的Handshake Packet包内容4a0000000a352e372e3236003a0000006d6d5c126f20082f00fff7c00200ff81150000000000000000000065446a040c41620a5a0a653f006d7973716c5f6e61746976655f70617373776f726400
根据以上信息解析的某次连接数据库的Handshake packet包payload
内容,仅供参考:
&{10 5.7.26 56 [94 14 72 8 12 22 51 99] 63487 192 2 33279 21 [20 92 85 1 34 62 25 56 111 43 14 64] mysql_native_password }
Go代码实现:
func (HandshakePacket *HandshakePacket) Handshake(packet []byte, username, pwd string) []byte {
protocolPacket := []byte{0x00, 0x00}
copy(protocolPacket, packet[0:1])
HandshakePacket.Protocol = binary.LittleEndian.Uint16(protocolPacket)
idx := bytes.IndexByte(packet[1:], 0x00)
dbVer := packet[1:idx]
HandshakePacket.Version = string(dbVer)
idx = idx + 2
HandshakePacket.ThreadId = binary.LittleEndian.Uint32(packet[idx : idx+4])
HandshakePacket.Salt1 = packet[idx+4 : idx+4+8]
HandshakePacket.ServerCapabilities = binary.LittleEndian.Uint16(packet[idx+4+8+1 : idx+4+8+1+2])
languagePacket := []byte{0x00, 0x00}
copy(languagePacket, packet[idx+4+8+1+2:idx+4+8+1+2+1])
HandshakePacket.ServerLanguage = binary.LittleEndian.Uint16(append(languagePacket, 0x00))
HandshakePacket.ServerStatus = binary.LittleEndian.Uint16(packet[idx+4+8+1+2+1 : idx+4+8+1+2+1+2])
HandshakePacket.ExtendedServerCapabilities = binary.LittleEndian.Uint16(packet[idx+4+8+1+2+1+2 : idx+4+8+1+2+1+2+2])
pluginLengthPacket := []byte{0x00, 0x00}
copy(pluginLengthPacket, packet[idx+4+8+1+2+1+2+2:idx+4+8+1+2+1+2+2+1])
HandshakePacket.AuthPluginLength = binary.LittleEndian.Uint16(pluginLengthPacket)
HandshakePacket.Unused = string(packet[idx+4+8+1+2+1+2+2+1 : idx+4+8+1+2+1+2+2+1+10])
salt2Idx := bytes.IndexByte(packet[idx+4+8+1+2+1+2+2+1+10:], 0x00)
HandshakePacket.Salt2 = packet[idx+4+8+1+2+1+2+2+1+10 : idx+4+8+1+2+1+2+2+1+10+salt2Idx]
HandshakePacket.AuthenticationPlugin = string(packet[idx+4+8+1+2+1+2+2+1+10+len(HandshakePacket.Salt2):])
HandshakePacket.echo()
return GetAuthPacket(append(HandshakePacket.Salt1, HandshakePacket.Salt2...), username, pwd)
}