前言
这里我们来看一下 mysql 的认证的流程
我们这里仅仅看 我们最常见的一个 认证的处理流程
我们经常会登录的时候 碰到各种异常信息
认证失败的大体流程
大概的流程是这样
客户端和服务器建立连接之后, 服务器向客户端发送 salt
然后 客户端根据 salt 将客户端传入的密码加密之后, 以及相关登录信息传递给服务器
然后 服务器进行验证, 验证失败, 响应对应的错误信息给客户端
服务器发送 salt 的信息如下
客户端发送的认证请求如下
服务器响应的认证失败信息如下
服务器发送 slat 的信息是在这里, 这里的 salt 是随机生成的
读取客户端的加密之后的信息
然后是 密码的验证, 我们这里的流程是这样, 这里 数据库用 root 用户没有密码, 但是客户端这边传入了密码, 因此这里直接响应了 CA_AUTH_USER_REDENTIALS
然后接着是 响应错误信息给客户端, 这里是响应 1045 ACCESS_DENIED_ERROR “Access denied for user 'root'@'192.168.220.1' (using password: YES)”
Access denied for user 'root'@'192.168.220.1' (using password: YES)
这里同上, 几种情况
首先需要传递密码, 其次是 root用户密码为空 或者 密码不对 会响应如下问题
上面演示了 mysql 密码为空, 但是 我输入了密码 之后的校验情况
我们现在来看一下 密码对不上的情况
用到了一些加密的特性来验证, 客户端输入的密文和数据库是否匹配
scramble_arg 表示的是客户端传入的密文, message 表示数据库的密文, hash_stage2 表示此次登录验证的 slat
Access denied for user 'root'@'192.168.220.1' (using password: NO)
这种情况就是 该用户有密码, 但是客户端 未传递 密码
验证是在这里, 如果数据库中该用户也没有密码, 直接 OK, 否则 响应 CR_AUTH_USER_CREDENTIALS
到后面的错误输出环节, 得到的错误信息 就是上面
客户端加密, 服务器验证的逻辑仿写
这里主要是 实现了一个 客户端这边 传递的密码的加密方式的处理
以及 服务器这边 验证 客户端秘钥, 服务器秘钥 的一个验证方式
可以参考学习一下, 需要导入 mysql-driver, netty 等等相关工具
salt 是服务器的 ServerGreeting 传递回来的, 是一个 20自己的随机字节序列
clientPwd 是根据用户名, 密码 salt 加密之后的一个结果
serverPwd 是 mysql 中 mysql.user 库中该用户记录的 authentication_string 秘钥序列
/**
* Test09MysqlLoginEncrypt
*
* @author Jerry.X.He
* @version 1.0
* @date 2023/8/9 9:31
*/
public class Test09MysqlLoginEncrypt {
// Test09MysqlLoginEncrypt
public static void main(String[] args) throws Exception {
String salt = ("2c 4f 04 2a 30 13 69 71").replaceAll("\\s+", "")
+ ("03 17 0a 1d 64 55 7e 68 1f 19 73 0a").replaceAll("\\s+", "");
String clientPwd = "012cb36acb2a4c77217d8d70dc43e058c1c6448a";
String serverPwd = "81F5E21E35407D884A6CD4A731AEBFB6AF209E1B";
String clientPwdEncoded = ByteBufUtil.hexDump(clientEncryptPwd("root", "root", salt));
boolean clientPwdIfMatch = checkClientPwdIfMatch(clientPwd, salt, serverPwd);
AssertUtils.assert0(clientPwd, clientPwdEncoded, " check if clientEncryptPwd match ");
AssertUtils.assert0(clientPwdIfMatch, " checkClientPwdIfMatch match ");
int x = 0;
}
/**
* checkClientPwdIfMatch
*
* @return boolean
* @author Jerry.X.He
* @date 2023/8/9 10:31
*/
public static boolean checkClientPwdIfMatch(String clientPwd, String salt, String serverPwd) {
byte[] saltBytes = ByteBufUtil.decodeHexDump(salt);
byte[] pwdInClient = ByteBufUtil.decodeHexDump(clientPwd);
byte[] pwdInDb = ByteBufUtil.decodeHexDump(serverPwd);
MessageDigest digest = DigestUtils.getSha1Digest();
digest.reset();
digest.update(saltBytes);
digest.update(pwdInDb);
byte[] pwdSaltUpdated = digest.digest();
my_crypt(pwdSaltUpdated, pwdSaltUpdated, pwdInClient);
digest.reset();
digest.update(pwdSaltUpdated);
byte[] pwdFinalUpdated = digest.digest();
boolean clientPwdEqualsDb = ByteBufUtil.hexDump(pwdFinalUpdated).equalsIgnoreCase(serverPwd);
return clientPwdEqualsDb;
}
/**
* clientEncryptPwd
*
* @return byte[]
* @author Jerry.X.He
* @date 2023/8/9 10:22
*/
public static byte[] clientEncryptPwd(String username, String password, String salt) {
MysqlNativePasswordPlugin plugin = new MysqlNativePasswordPlugin();
NativeProtocol protocol = new MyNativeProtocol(new NullLogger(""));
plugin.init(protocol);
NativePacketPayload fromServer = new NativePacketPayload(ByteBufUtil.decodeHexDump(salt));
List<NativePacketPayload> toSaveList = new ArrayList<>();
plugin.setAuthenticationParameters(username, password);
plugin.nextAuthenticationStep(fromServer, toSaveList);
return toSaveList.get(0).getByteBuffer();
}
/**
* my_crypt
*
* @return void
* @author Jerry.X.He
* @date 2023/8/9 10:21
*/
public static void my_crypt(byte[] to, byte[] s1, byte[] s2) {
for (int i = 0; i < s1.length; i++) {
to[i] = (byte) (s1[i] ^ s2[i]);
}
}
/**
* Test09MysqlLoginEncrypt
*
* @author Jerry.X.He
* @version 1.0
* @date 2023/8/9 10:19
*/
private static class MyNativeProtocol extends NativeProtocol {
public MyNativeProtocol(Log logger) {
super(logger);
}
@Override
public String getPasswordCharacterEncoding() {
return "UTF-8";
}
}
}
运行处理, 信息如下
完