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 PHPMysql\MysqlPacket
*/
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 PHPMysql\MysqlPacket\HandleShake#3 (6) { private $hex_string => string(148) "0a352e372e31360024000000232e200b147b397f00fff7080200ff811500000000000000000000735c762246383411280b5b73006d7973716c5f6e61746976655f70617373776f726400" private $protocol_version => int(10) private $server_version => string(7) "5.7.16\000" 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