准备:
Auth Packet格式 🔗
Auth packet包是由MySQL客户端向MySQL服务端发送的认证包,用于验证数据库账户登录,相应内容的格式:
相对包内容的位置 | 长度(字节) | 名称 | 描述 |
---|---|---|---|
0 | 2 | 协议协商(Client Capabilities) | 用于与服务端协商通讯方式 |
2 | 2 | 扩展的协议(Extended Client Capabilities) | 用于与服务端协商通讯方式 |
4 | 4 | 消息最长长度(MAX Packet) | 客户端可以发送或接收的最长长度,0表示不做任何限制 |
8 | 1 | 字符编码(Charset) | 客服端字符编码方式 |
9 | 23 | 保留字节(Unused) | 未来可能会用到,预留字节,用0代替 |
32 | 不定长 | 认证字符串(Username) | 用户名:NullTerminatedString格式编码,以0x00结尾 |
不定 | 不定长 | 认证密码(Password) | 加密后的密码:LengthEncodedString格式编码 |
不定 | 不定长 | 数据库名称 | NullTerminatedString格式编码,当客户端的权能标志位 CLIENT_CONNECT_WITH_DB 被置位时,该字段必须出现。 |
根据给出的 Protocol::HandshakeResponse41 解析说明,以下是对各字段的简要解释:
-
client_flag (int<4>): 客户端标志,表示客户端的配置和特性。其中 CLIENT_PROTOCOL_41 始终设置。
-
max_packet_size (int<4>): 最大数据包大小,表示客户端愿意接收的最大数据包大小。
-
character_set (int<1>): 客户端字符集,表示客户端使用的字符集编码。取值为 a_protocol_character_set 字段的低 8 位。
-
filler (string[23]): 用于填充的字段,将填充到握手响应数据包的大小。所有字节都设置为 0。
-
username (string
): 登录用户名。 -
auth_response (string):
- 如果
capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
成立,则为一个长度编码的字符串,表示由插件名称字段指定的身份验证方法生成的不透明身份验证响应数据。 - 否则,首先为一个字节的 auth_response_length,然后是 auth_response 字段,表示身份验证响应数据。
- 如果
-
database (string
): - 如果
capabilities & CLIENT_CONNECT_WITH_DB
成立,则为连接的初始数据库。这个字符串应该使用字符集字段指示的字符集进行解释。
- 如果
-
client_plugin_name (string
): - 如果
capabilities & CLIENT_PLUGIN_AUTH
成立,则为客户端用于在此数据包中生成 auth-response 值的身份验证方法的名称。这是一个 UTF-8 字符串。
- 如果
-
key-values (CLIENT_CONNECT_ATTRS):
- 如果
capabilities & CLIENT_CONNECT_ATTRS
成立,则包含客户端属性。以长度编码的方式表示所有键-值对。 - 每个键值对由一个长度编码的字符串表示键和值。
- 如果
-
zstd_compression_level (int<1>):
- 如果
capabilities & CLIENT_ZSTD_COMPRESSION_ALGORITHM
成立,则表示 zstd 压缩算法的压缩级别。
这个解析说明提供了每个字段的基本含义和解析方法。在实际应用中,需要根据 MySQL 协议文档和具体情况进行更详细的解析。
报文结构:
这部分内容是由客户端自己生成,所以说如果我们如果要写一个程序连接数据库,那么这个包就得按照这个格式,不然服务端将会无法识别。
特别注意:认证字符串(Username)和认证密码(Password)之间有个字节
14
,它是Protocol::HandshakeResponse41中auth_response_length
字段。
auth_response_length
字段是 Protocol::HandshakeResponse41
中的一个字节,用于表示 auth_response
字段的长度。
具体解析方式如下:
-
如果
capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
成立,表示auth_response
字段的长度由长度编码方式给出,不使用auth_response_length
字段。 -
否则,
auth_response_length
表示auth_response
字段的长度。在这种情况下,auth_response
字段是一个固定长度的字节数组。
总之,auth_response_length
用于指定 auth_response
字段的长度,具体解析方式取决于 capabilities
的设置。在解析 Protocol::HandshakeResponse41
数据包时,需要根据实际情况选择解析方式。
字符串 🔗
- String的编码格式相对Integer来说会复杂一点,主要有以下几种:
- FixedLengthString(定长方式):需先知道String的长度,MySQL中的一个例子就是ERR_Packet包(后续会讲到)就使用了这种编码方式,因为它的长度固定,用5个字节存储所有数据。
- NullTerminatedString(Null结尾方式): 字符串以遇到Null作为结束标志,相应的字节为00。
- VariableLengthString(动态计算字符串长度方式): 字符串的长度取决于其他变量计算而定,比如一个字符串由Integer + Value组成,我们通过计算Integer的值来获取Value的具体的长度。
- LengthEncodedString(指定字符串长度方式): 与VariableLengthString原理相似,是它的一种特殊情况,具体例子就是我上条举的这个例子。
- RestOfPacketString(包末端字符串方式):一个包末端的字符串,可根据包的总长度金和当前位置得到字符串的长度,实际中并不常用。
加密方式 🔗
生成Auth Packet的过程中,需要将密码加密再发送。在MySQL 4.0之前,MySQL协议仅支持旧密码身份验证,在MySQL 4.1中添加了安全密码身份验证方法,在MySQL 5.5中,可以通过身份验证插件(Authentication Plugin)实现任意身份验证方法。参考文档资料
加密方式通过Handshake Packet
包中的身份验证插件(Authentication Plugin)字段确认(参考文档资料)。由上一章知道服务器返回mysql_native_password
字段,所以密码使用该方式进行加密。可以参考go-sql-driver/mysql包内容开发加密组件。
mysql_native_password
Password,首先这个数据长度是指定的,即为20个字节。而这个加密的密码是根据HandShake中的两个Salt通过约定的加密方式的来,HandShake中第一个Salt是8位的,第二个Salt是12位的。
scramble
为Salt1+Salt2 组成。如下所示:
append(HandshakePacket.Salt1, HandshakePacket.Salt2...)
password
为客户端输入的密码。
实现代码:
// Hash password using 4.1+ method (SHA1)
func scramblePassword(scramble []byte, password string) []byte {
if len(password) == 0 {
return nil
}
// stage1Hash = SHA1(password)
crypt := sha1.New()
crypt.Write([]byte(password))
stage1 := crypt.Sum(nil)
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
// inner Hash
crypt.Reset()
crypt.Write(stage1)
hash := crypt.Sum(nil)
// outer Hash
crypt.Reset()
crypt.Write(scramble)
crypt.Write(hash)
scramble = crypt.Sum(nil)
// token = scrambleHash XOR stage1Hash
for i := range scramble {
scramble[i] ^= stage1[i]
}
return scramble
}