• php一步一步实现mysql协议(二) ——握手初始化


    交互过程

    MySQL客户端与服务器的交互主要分为两个阶段:握手认证阶段和命令执行阶段。

    握手认证阶段

    握手认证阶段为客户端与服务器建立连接后进行,交互过程如下:

    • 服务器 -> 客户端:握手初始化消息
    • 客户端 -> 服务器:登陆认证消息
    • 服务器 -> 客户端:认证结果消息

    命令执行阶段

    客户端认证成功后,会进入命令执行阶段,交互过程如下:

    • 客户端 -> 服务器:执行命令消息
    • 服务器 -> 客户端:命令执行结果

    上面就是mysql客户端和服务端的交互流程,然后结合实际中的抓包工具来看先这个过程。这里使用php的PDO扩展连接数据库并执行一条查询语句,抓包情况如下

    下面一条一条的来分析每个包中的内容,在此之前先看下报文的结构,报文分为消息头和消息体两部分,其中消息头占用固定的4个字节,消息体长度由消息头中的长度字段决定,报文结构如下:

    先看下握手初始化的报文,如图:

      其中前三个字节  4a 00 00  表示消息体的长度,但是这里需要注意的是在报文中整数是以小端存储(即低位放在低地址,高位放在高地址)的方式进行传输的,所以转为我们平时阅读的形式的话应该是  00 00 4a  转为十进制应该是 74 ,也就是报文中的消息体长度应该是 74个字节(即出去开头的 4a 00 00 00 字节消息头之外所有的蓝色背景部分)。再来看下初始化信息中包含了哪些内容

    消息体中的第一个字节表示的是协议版本号  0a   转为十进制是 10 所以协议版本号就是10,16进制整数转为10进制的实现如下

    <?php
    
    function hexToInt($hex_string){
        $z = implode("", array_reverse(str_split($hex_string,2)));
        return hexdec($z);
    }
    
    $str = "4a0000";
    echo hexToInt($str); //输出 74

    后面的7个字节表示服务器的版本号,这里使用 php转化

    <?php
    
    function hexToStr($hex_string){
        $result = "";
        $hex_array = str_split($hex_string,2);
        foreach ($hex_array as $item){
            $result .= chr(hexdec($item));
        }
        return $result;
    }
    
    $str = "352e372e313600";
    echo hexToStr($str);  //输出 5.7.16

       这里特别记录两个参数 8位挑战随机数12位挑战随机数 这两个参数用来认证用户时密码加密的时候使用。将握手初始化报文组装成对象

    HandleShake.php
    <?php
    /**
     * 服务初始化握手
     * Class HandleShake
     *
     * @author gphper 20200909
     * @package PHPMysqlMysqlPacket
     */
    class HandleShake
    {
        private $hex_string;
        private $protocol_version;
        private $server_version;
        private $thread_id;
        private $salt1;
        private $salt2;
    
        public function __construct($hex_string)
        {
            $this->hex_string = $hex_string;
            $this->setProtocolVersion();
            $this->setServerVersion();
            $this->setThreadId();
            $this->setSalt1();
            $this->setSalt2();
        }
    
        public function setProtocolVersion(){
            $this->protocol_version = UtiliHelper::HexToInt(UtiliHelper::HexSub($this->hex_string,0,1));
        }
    
        public function setServerVersion(){
            $this->server_version = UtiliHelper::HexToStr(UtiliHelper::HexSub($this->hex_string,1,7));
        }
    
        public function setThreadId(){
            $this->thread_id = UtiliHelper::HexToInt(UtiliHelper::HexSub($this->hex_string,8,4));
        }
    
        public function setSalt1(){
            $this->salt1 = UtiliHelper::HexSub($this->hex_string,12,8);
        }
    
        public function setSalt2(){
            $this->salt2 = UtiliHelper::HexSub($this->hex_string,39,12);
        }
    
        public function getSalt(){
            return $this->salt1.$this->salt2;
        }
    
    }
    
    

    UtilHelper.php

    <?php
    class UtiliHelper
    {
    
        /**
         * 将十六进制转为字符串
         * @author gphper 20200908
         * @param $hex_str
         * @return string
         */
        public static function HexToStr($hex_str){
            $send_msg = "";
            foreach (str_split($hex_str,2) as $key => $value) {
                 $send_msg .= chr(hexdec($value));
            }
            return $send_msg;
        }
    
        /**
         * 十六进制转整数
         * @author gphper 20200908
         * @param $hex_string
         * @return float|int
         */
        public static function HexToInt($hex_string){
            $z = implode("", array_reverse(str_split($hex_string,2)));
            return hexdec($z);
        }
    
        /**
         * 截取十六进制
         * @author gphper 20200908
         * @param $hex_str
         * @param $start
         * @param $length
         * @return bool|string
         */
        public static function HexSub($hex_str,$start,$length=0){
            if($length){
                return substr($hex_str, $start*2,$length*2);
            }
            return substr($hex_str, $start*2);
        }
    
        /**
         * 将十六进制消息体分段
         * @author gphper 20200908
         * @param $hex_str
         * @param int $is_body
         * @param array $all
         * @return array
         */
        public static function HexSplit($hex_str,$is_body=0,$all=[]){
            //先获取前三位
            $length = self::HexToInt(self::HexSub($hex_str,0,3));
            $total_length = $length + 4;
            $start = $is_body*8;
            $pre_str = substr($hex_str,$start,$total_length*2-$start);
            $sub_str = substr($hex_str,$total_length*2);
            $all = array_merge($all,array($pre_str));
            if($sub_str){
                return self::HexSplit($sub_str,$is_body,$all);
            }
            return $all;
        }
    
        /**
         * 十进制转为十六进制 小端存储
         * @author gphper 20200908
         * @param $length
         * @return string
         */
        public static function IntToHex($length){
            $big = str_pad(dechex($length),6,0,STR_PAD_LEFT);
            return implode("",array_reverse(str_split($big,2)));
        }
    
        /**
         * 字符转十六进制
         * @author gphper 20200908
         * @param $string
         * @return string
         */
        public static function StrToHex($string){
            $length = strlen($string);
            $hex = "";
            for ($i = 0; $i<$length; $i++){
                $hex .= str_pad(dechex(ord($string[$i])),2,0,STR_PAD_LEFT);
            }
            return $hex;
        }
    
        /**
         * 使用返回服务端返回的信息加密密码
         * $seed = "39011e567b3878441a0a560d52083e25336e3c34"
         * @author gphper 20200909
         * @param string $pass
         * @param string $seed
         * @return string
         */
        public static function scramble411($pass, $seed)
        {
            $pass1 = self::getBytes(sha1($pass, true));
            $pass2 = sha1(self::getString($pass1), true);
            $pass3 = self::getBytes(sha1(self::HexToStr($seed) . $pass2, true));
            $result = "";
            for ($i = 0, $count = count($pass3); $i < $count; ++$i) {
                $result .= str_pad(dechex(($pass3[$i] ^ $pass1[$i])),2,0,STR_PAD_LEFT);
            }
            return $result;
        }
    
        public static function getBytes($data)
        {
            $bytes = [];
            $count = strlen($data);
            for ($i = 0; $i < $count; ++$i) {
                $byte = ord($data[$i]);
                $bytes[] = $byte;
            }
    
            return $bytes;
        }
    
    
        public static function getString(array $bytes)
        {
            return implode(array_map('chr', $bytes));
        }
        
    }

    index.php

    <?php
    //创建tcp套接字
    $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
    //连接tcp
    socket_connect($socket, '127.0.0.1',3306);
    
    //初始化握手
    $msg = socket_read($socket,8190,PHP_BINARY_READ);
    $body_arr = UtiliHelper::HexSplit(bin2hex($msg),1);
    $handle_shark = new HandleShake($body_arr[0]);
    //关闭连接
    socket_close($socket);

    生成的 HandleShake 对象

    class PHPMysqlMysqlPacketHandleShake#3 (6) {
      private $hex_string =>
      string(148) "0a352e372e31360024000000232e200b147b397f00fff7080200ff811500000000000000000000735c762246383411280b5b73006d7973716c5f6e61746976655f70617373776f726400"
      private $protocol_version =>
      int(10)
      private $server_version =>
      string(7) "5.7.1600"
      private $thread_id =>
      int(36)
      private $salt1 =>
      string(16) "232e200b147b397f"
      private $salt2 =>
      string(24) "735c762246383411280b5b73"
    }

     代码分享地址

       https://github.com/gphper/PHPMysql

    参考文章:

      https://www.cnblogs.com/davygeek/p/5647175.html

  • 相关阅读:
    1.8(SQL学习笔记)触发器
    1.7(SQL学习笔记)游标
    1.6(SQL学习笔记)存储过程
    1.4(JavaScript学习笔记) window对象的属性及方法
    1.3 (JavaScript学习笔记)JavaScript对象
    1.2(JavaScript学习笔记)JavaScript HTML DOM
    1.1(JavaScript学习笔记)、JavaScript基础
    vuejs,router
    一个非常牛比的前端google插件
    vue.js 2.0开发(4)
  • 原文地址:https://www.cnblogs.com/itsuibi/p/13660811.html
Copyright © 2020-2023  润新知