mysql communication protocols - to create a connection

The content of the article to collate, verify (all pictures are taken from the official website) according to mysql official document content


mysql communication protocol connection phase mainly includes:

  • Exchange of information the client and server

  • If the client is set SSL, SSL communication channel provided

  • User authentication server based on the data returned by the client


When the client initiates the connection server, the server can send ERR_Packet completion of the handshake data packet or sending the client needs the initial handshake, the client may also request phase SSL connection, establish an SSL communication channel prior to authentication.


Method when the server sends the initial handshake for authentication, client information according to the received return packet assembly, until the entire connection process ERR_Packet returned from the server or end OK_Packet, the process shown below (taken from the official website):

Screenshot 3.53.23.png 2019-08-20 PM


The initial handshake:

Handshake Protocol :: Handshake initial transmission packet start from the server, after which, the client can request packet Protocol :: SSLRequest SSL connection is established, the client sends or Protocol :: HandshakeResponse package directly, explanation for each of the connections Happening

ssl connection:


    • Server sends Protocol :: Handshake packets

    • The client replies Protocol :: SSLRequest package

    • Channel setting ssl server

    • The client sends a packet Protocol :: HandshakeResponse

Screenshot 3.59.59.png 2019-08-20 PM


Generic Connectivity:


    • Server sends Protocol :: Handshake packets

    • The client sends a packet Protocol :: HandshakeResponse

When shaking hands, of course can not all be smooth sailing, there will be a variety of situations happen in this way, such as authentication fails, password authentication method does not comply, customer service side did not seek to return verification methods, here only password authentication method error condition , I would like to see the official website to learn more about the document:


    • The client connects to the server

    • Server sends Protocol :: Handshake

    • Client returns Protocol :: HandshakeResponse

    • Server sends Protocol :: AuthSwitchRequest tell the client that it needs to switch to a new authentication method.

    • Client and server authentication method may be based on client requirements to exchange more data packets.

    • OK_Packet server sends data packets or ERR_Packet

Screenshot 4.29.03.png 2019-08-20 PM

可以看出该情况比一步完成连接的情况多了Protocol::AuthSwitchRequest和客户端重新回复包的两步,当然在这其中也可能继续发生该异常或其他异常,需要继续交换数据包,下面来看下Protocol::Handshake和Protocol::HandshakeResponse两个数据包的组装情况,mysql通信协议中的所有包会有4bytes的head,前3bytes记录包的大小,第4位置的1byts记录数据包的顺序id,客户端和服务端的一次协议交互直到结束,这途中所有数据包的id都是顺序增长,所以下面介绍的结构都不包含这4bytes需要先明白:


Protocol::Handshake:

Screenshot 4.35.30.png 2019-08-20 PM

图中我们可以很清楚的看到包中包含有协议版本、mysql版本、当前连接的id、默认的字符集还有一些需要交换的信息,而密码验证方法的内容分成了两个部分,这可能是出于安全考虑吧.....


Protocol::HandshakeResponse:

Screenshot 4.49.12.png 2019-08-20 PM

Response包有Protocol::HandshakeResponse320和Protocol::HandshakeResponse41,因为Protocol::HandshakeResponse320是4.1以下版本的协议包,所以只关注了Protocol::HandshakeResponse41包的内容,毕竟现在mysql使用的主流都是5.7了,更多详细内容还是看官方文档吧,毕竟篇幅有限,这里主要说下密码的加密方式,8.0默认密码验证方式为caching_sha2_password,还没来得及认真研究,这里讲讲我们普遍使用的加密方式:mysql_navicat_password:

上面看到Protocol::Handshake有发送两部分的auth-plugin-data,这两部分数据起到加密用户密码的作用,加密方式如下:

SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) )

假如服务端未发送auth-plugin-data数据将只使用SHA1(password) ,如果有发送该部分数据就将结合这部分数据对密码进行加密,这样可以让嗅探工具不能直接查看到密码,起到一定的安全作用


mysql协议中连接的建立大概内容基本就这些,还是需要自己动手来验证,下面写了一个小脚本,直接看代码吧,只做了常规的步骤,过程中的各种异常并未进行处理,仅供参考:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@author: xiao cai niao
'''
import struct,sys
from socket import *
from contextlib import closing
import hashlib,os
from functools import partial

sha1_new = partial(hashlib.new, 'sha1')
SHA1_HASH_SIZE = 20
MULTI_RESULTS = 1 << 17
SECURE_CONNECTION = 1 << 15
CLIENT_PLUGIN_AUTH = 1 << 19
CLIENT_CONNECT_ATTRS = 1<< 20
CLIENT_PROTOCOL_41 = 1 << 9
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = 1<<21
LONG_PASSWORD = 1
LONG_FLAG = 1 << 2
PROTOCOL_41 = 1 << 9
TRANSACTIONS = 1 << 13

CAPABILITIES = (
    LONG_PASSWORD | LONG_FLAG | PROTOCOL_41 | TRANSACTIONS
    | SECURE_CONNECTION | MULTI_RESULTS
    | CLIENT_PLUGIN_AUTH | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | CLIENT_CONNECT_ATTRS)


CLIENT_CONNECT_WITH_DB = 9
max_packet_size = 2 ** 24 - 1
charset_id = 45


class TcpClient:
    def __init__(self,host_content,user_name,password,databases):
        _host_content = host_content.split(':')
        self.user = user_name
        self.password = password
        self.database = databases
        HOST = _host_content[0]
        PORT = int(_host_content[1])
        self.BUFSIZ = 1024
        self.ADDR = (HOST, PORT)

        self.client=socket(AF_INET, SOCK_STREAM)
        self.client.connect(self.ADDR)
        self.client.settimeout(1)

        self.server_packet_info = {}

        self.packet = None
    def header(self):
        self.offset = 0
        self.payload_length = self.packet[2] << 16 | self.packet[1] << 8 | self.packet[0]
        self.sequence_id = self.packet[3]
        self.offset += 4

    def check_packet(self):
        self.header()
        packet_header = self.packet[self.offset]
        self.offset += 1
        if packet_header == 0x00:
            print('connection ok')
        elif packet_header in (0xfe,0xff):
            print(self.packet[self.offset:])

    def Send(self):
        self.packet=self.client.recv(self.BUFSIZ)
        self.header()
        self.__read_server_info()
        self.__handshakeresponsepacket()
        response_payload = len(self.response_packet)
        self.client.send(struct.pack('<I',response_payload)[:3] + struct.pack('!B',1) + self.response_packet)

        self.packet = self.client.recv(self.BUFSIZ)
        self.header()
        packet_header = self.packet[self.offset]
        self.offset += 1
        if packet_header == 0xff:
            error_code = struct.unpack('<H', self.packet[self.offset:self.offset + 2])
            self.offset+= 2
            print(error_code,self.packet[self.offset:])
        elif packet_header == 0xfe:
            """AuthSwitchRequest"""
            _data = self.__authswitchrequest()
            self.client.send(struct.pack('<I', len(_data))[:3] + struct.pack('!B', 3) + _data)
            self.packet = self.client.recv(self.BUFSIZ)
            self.check_packet()

        elif packet_header in (0x00,0xfe):
            if self.payload_length > 7:
                print('ok packet')
            elif self.payload_length < 9:
                print('error packet')

        print(self.server_packet_info)

        """在这里停留一段时间,在mysql查看连接是否正常"""
        import time
        time.sleep(1000)


    def __authswitchrequest(self):
        end_pos = self.packet.find(b'\0', self.offset)
        auth_name = self.packet[self.offset:end_pos].decode()

        self.offset = end_pos + 1

        auth_plugin_data = self.packet[self.offset:]
        if self.server_packet_info['capability_flags'] & CLIENT_PLUGIN_AUTH and auth_name:
            data = self.__sha1_password(auth_plugin_data)

        return data


    def __read_server_info(self):
        PLUGIN_AUTH = 1 << 19
        #数据包内容
        self.server_packet_info['packet_header'] = self.packet[self.offset]
        self.offset += 1

        _s_end = self.packet.find(b'\0', self.offset)
        self.server_packet_info['server_version'] = self.packet[self.offset:_s_end]
        self.offset = _s_end + 1
        self.server_packet_info['thread_id'] = struct.unpack('<I',self.packet[self.offset:self.offset+4])
        self.offset += 4
        self.server_packet_info['auth_plugin_data'] = self.packet[self.offset:self.offset+8]
        self.offset += 8 + 1
        self.server_packet_info['capability_flags'] = struct.unpack('<H',self.packet[self.offset:self.offset+2])[0]
        self.offset += 2
        self.server_packet_info['character_set_id'],\
        self.server_packet_info['status_flags'],\
        capability_flags_2,auth_plugin_data_len = struct.unpack('<BHHB',self.packet[self.offset:self.offset+6])

        self.server_packet_info['capability_flags'] |= capability_flags_2 << 16
        self.offset += 6
        self.offset += 10
        auth_plugin_data_len = max(13,auth_plugin_data_len-8)
        if len(self.packet) - 4 >= self.offset + auth_plugin_data_len:
            # salt_len includes auth_plugin_data_part_1 and filler
            self.server_packet_info['auth_plugin_data'] += self.packet[self.offset:self.offset + auth_plugin_data_len]
            self.offset += auth_plugin_data_len

        if self.server_packet_info['capability_flags'] & PLUGIN_AUTH and len(self.packet) - 4 >= self.offset:
            _s_end = self.packet.find(b'\0',self.offset)
            self.server_packet_info['auth_plugin_name'] = self.packet[self.offset:_s_end]


    def __handshakeresponsepacket(self):
        client_flag = 0
        client_flag |= CAPABILITIES
        if self.database:
            client_flag |= CLIENT_CONNECT_WITH_DB
        server_version = (self.server_packet_info['server_version']).decode()
        if int(server_version.split('.', 1)[0]) >= 5:
            client_flag |= MULTI_RESULTS

        self.response_packet = struct.pack('<iIB23s',client_flag,max_packet_size,charset_id,b'')
        self.response_packet += self.user.encode() + b'\0'
        sha1_password = self.__sha1_password()

        if self.server_packet_info['capability_flags'] & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA:
            self.response_packet += struct.pack('!B',len(sha1_password)) + sha1_password
        elif self.server_packet_info['capability_flags'] & SECURE_CONNECTION:
            self.response_packet += struct.pack('B',len(sha1_password)) + sha1_password
        else:
            self.response_packet += sha1_password + b'\0'

        if self.server_packet_info['capability_flags'] & CLIENT_CONNECT_WITH_DB:
            self.response_packet += self.database.encode() + b'\0'
        if self.server_packet_info['capability_flags'] & CLIENT_PLUGIN_AUTH:
            self.response_packet += b'' + b'\0'
        if self.server_packet_info['capability_flags'] & CLIENT_CONNECT_ATTRS:
            _connect_attrs = {
                '_client_name': 'pymysql',
                '_pid': str(os.getpid()),
                '_client_version': '3.6.5',
                'program_name' : sys.argv[0]
            }
            connect_attrs = b''
            for k, v in _connect_attrs.items():
                k = k.encode('utf8')
                connect_attrs += struct.pack('B', len(k)) + k
                v = v.encode('utf8')
                connect_attrs += struct.pack('B', len(v)) + v
            self.response_packet += struct.pack('B', len(connect_attrs)) + connect_attrs
    def __sha1_password(self,auth_plugin_data=None):
        _pass1 = sha1_new(self.password.encode()).digest()
        _pass2 = sha1_new(_pass1).digest()
        s = sha1_new()
        if auth_plugin_data is None:
            s.update(self.server_packet_info['auth_plugin_data'][:SHA1_HASH_SIZE])
        else:
            s.update(auth_plugin_data[:SHA1_HASH_SIZE])
        s.update(_pass2)
        t = bytearray(s.digest())
        for i in range(len(t)):
            t[i] ^= _pass1[i]

        return t

    def close(self):
        self.client.close()


with closing(TcpClient('192.168.10.12:3306','root','root','sys')) as tcpclient:
    tcpclient.Send()

Q technical exchange group (479 472 450) and the number of individual public from time to time to share learning outcomes:

qrcode_for_gh_3e32c761a655_344.jpg


Guess you like

Origin blog.51cto.com/xiaozhong991/2431330