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

时间:2022-07-25
本文章向大家介绍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