本文主要讲mysql连接协议.
了解了mysql的连接协议后, 就可以直接写mysql连接(驱动)了, 就可以模拟mysql client去连接数据库了, 还能模拟mysql服务端, 就可以制作mysql中间件来做读写分离, 分布式数据库 之类的了. 不过本文不会讲到那么多.
COM_QUERY下次讲.
本文使用的密码加密策略为 NativePassword
本文不使用SSL
mysql包格式如下
名字 | 大小(字节) | 描述 |
---|---|---|
payload_length | 3 | 包长度(2**(3*8)) 所以最大就是16MB (0xFFFFFF表示包超过16MB, 继续读) |
sequence_id | 1 | 序列号(0-255) |
payload | 具体的包了 |
过程如下:
client 连接 server (socket.connect())
server 发送握手协议(包括数据库版本, 加密策略,capability_flags, salt等信息) (HandshakeV10)
client 回复账号密码等信息 (HandshakeResponse41)
server回复 OK/ERR 包, 如果是err包, 客户端和服务端就会断开连接. OK包就进入命令解析阶段(下章讲)
客户端直接建立socket连接即可. (本文不含本地socket, 均走TCP)
执行如下py代码连接mysql后, 服务端就会发送handshake包(扫描服务器版本就可以使用这种方法,这一步不要账号密码)
本文解析的handshake为v10版本.
名字 | 大小(字节) | 条件 | 描述 |
---|---|---|---|
protocol version | 1 | v10版本固定为10 (b'\n') | |
server version | 以0x00结尾 | mysql版本,字符串 | |
thread id | 4 | server端对应的thread id | |
salt | 8 | 加密需要的(scramble) | |
filler | 1 | 固定 0x00 | |
capability_flags_1 | 2 | 功能标记,比如带不带DB | |
character_set | 1 | 字符集 | |
status_flags | 2 | server状态,比如是否自动提交 | |
capability_flags_2 | 2 | 类似capability_flags_1 | |
auth_plugin_data_len | 1 | capabilities & CLIENT_PLUGIN_AUTH | salt大小 |
reserved | 10 | 固定10个空字符(填充) | |
auth-plugin-data-part-2 | MAX(13, length of auth-plugin-data - 8) | salt的剩余部分(毕竟上面只有8字节, 一般是20字节) | |
auth_plugin_name | 剩下的字节 | 密码插件名字 |
mysql的NativePassword加密策略虽然是做两次 sha1 就行, 但是网络传说中不安全(会被截获), 所以服务端每次都会生成随机的salt给密码加密. 就是加盐...
注意:每次连接的salt都不一样
客户端服务端通用的, 固定4(2+2)字节(32bit) , 每个bit位代表一个, 比如第九位代表CLIENT_PROTOCOL_41
本文客户端使用CAPABILITIES = (
LONG_PASSWORD
| LONG_FLAG
| PROTOCOL_41
| TRANSACTIONS
| SECURE_CONNECTION
| MULTI_RESULTS
| PLUGIN_AUTH
| PLUGIN_AUTH_LENENC_CLIENT_DATA
| CONNECT_ATTRS
)
即3842565, 换成bit就是如下
就是字符编号, 可以在mysql服务端查询编号, 比如45表示utf8mb4
就是加密插件名字, 本文使用mysql_native_password
本文解析的为HandshakeResponse41
名字 | 大小(字节) | 条件 | 描述 |
---|---|---|---|
client_flag | 4 | capability_flags 同server | |
max_packet_size | 4 | 最大包大小, 默认16MB | |
character_set | 1 | 字符集 | |
filler | 23 | 填充字符 | |
username | 以0x00结尾 | 用户名 | |
auth_response | 取决于密码长度 | capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | 密码长度(使用变成类型)和密码, 使用sha1加盐 |
dbname | 0x00 | capabilities & CLIENT_CONNECT_WITH_DB(就是capabilities 中的DB位是否为1, 就是有没有设置DB的意思) | 数据库名(本文不含) |
client_plugin_name | 0x00 | capabilities & CLIENT_PLUGIN_AUTH | 加密插件名字(以空0x00字符结尾) |
attrs | 变长长度 | CLIENT_CONNECT_ATTRS | 客户端名字,版本,pid信息 |
密码长度+密码
密码长度使用变长类型, 可参考上一章
密码是加密的, 可使用如下函数加密, 也可以使用官方的c代码(sql/auth/password.c::scramble)
比如你要设置 数据库的话, 就
self.server_capabilities & MYSQL_CONNECT_WITH_DB #与MYSQL_CONNECT_WITH_DB (1 << 3)相与就是设置第N位为1
Ok包就是 第一字节为 0x00
err包就是 第一字节为 0xfe
上面已经解析了mysql的连接过程了, 这里就使用python连接看看
bytes([self._next_seq_id]) 写成了bytes(self._next_seq_id) 坑了我2小时...... 一致报Got packets out of order.....
显示没问题, 去服务端瞧瞧, 也没得问题, 信息都是对得上的, 说明我们解析mysql连接协议成功了. 下章在讲发送SQL命令
1. mysql包 分为header(3+1)和payload
2. 当连上mysql的时候, mysql就会发送它的版本信息和salt过来
3. 客户端根据salt把密码加密发送过去, 如果成功就返回OK包, 失败就返回err包
mysql的字符常量均为 0x00结尾. 可变长字符 均为: 变长长度+值.
参考资料
mysql官方: https://dev.mysql.com/doc/dev/mysql-server/latest/
pymsyql: https://github.com/PyMySQL/PyMySQL
上面已经解析了mysql的连接了, 那么我们就可以模拟mysql服务端了
测试代码链接: https://github.com/ddcw/ddcw/blob/master/python/mysql_joker.py
然后客户端使用mysql连接测试, 就出现了password is not exists. will drop all database.
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。