从源码分析 MySQL 身份验证插件的实现细节

最近在分析ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)这个报错的常见原因。

在分析的过程中,不可避免会涉及到 MySQL 身份验证的一些实现细节。

加之之前对这一块就有很多疑问,包括:

  1. 一个明文密码,是如何生成 mysql.user 表中的 authentication_string?

  1. 在进行身份验证时,客户端是否会直接发送明文密码给 MySQL 服务端?

  1. MySQL 8.0 为什么要将默认的身份认证插件调整为 caching_sha2_password,mysql_native_password 有什么问题嘛?

所以,就从代码层面对 MySQL 身份验证插件(主要是 mysql_native_password)的一些实现细节进行了分析。

本文主要包括以下几部分:

  1. 服务端是如何对明文密码进行加密的?

  2. 服务端是如何进行客户端身份验证的?

  3. 客户端是如何处理明文密码的?会直接发送明文密码给服务端么?

  4. 服务端是如何验证客户端密码是否正确的?

  5. 为什么 MySQL 8.0 要将默认的身份认证插件调整为 caching_sha2_password?

服务端是如何对明文密码进行加密的?

在 mysql_native_password 中,对明文密码进行加密是在 my_make_scrambled_password_sha1函数中实现的。

// sql/auth/password.cc
void my_make_scrambled_password_sha1(char *to, const char *password,
                                     size_t pass_len) {
  uint8 hash_stage2[SHA1_HASH_SIZE];

  /* Two stage SHA1 hash of the password. */
  compute_two_stage_sha1_hash(password, pass_len, (uint8 *)to, hash_stage2);

  /* convert hash_stage2 to hex string */
  *to++ = PVERSION41_CHAR;
  octet2hex(to, (const char *)hash_stage2, SHA1_HASH_SIZE);
}

// sql/auth/password.cc
inline static void compute_two_stage_sha1_hash(const char *password,
                                               size_t pass_len,
                                               uint8 *hash_stage1,
                                               uint8 *hash_stage2) {
  /* Stage 1: hash password */
  compute_sha1_hash(hash_stage1, password, pass_len);

  /* Stage 2 : hash first stage's output. */
  compute_sha1_hash(hash_stage2, (const char *)hash_stage1, SHA1_HASH_SIZE);
}

实现其实非常简单:

  1. 使用 OpenSSL 库中的函数对输入的密码进行 SHA-1 哈希,生成 hash_stage1。

  1. 对生成的 hash_stage1 进行二次 SHA-1 哈希,生成 hash_stage2。

3 .将 hash_stage2 转换为十六进制表示。

最后生成的字符串即我们在mysql.user中看到的authentication_string

相同的功能用下面这段 Python 代码很容易就能实现出来。

import hashlib

def compute_sha1_hash(data):
    sha1 = hashlib.sha1()
    sha1.update(data)
    return sha1.digest()

password = "123456".encode('utf-8')
hash_stage1 = compute_sha1_hash(password)
hash_stage2 = compute_sha1_hash(hash_stage1)
print('*%s'%hash_stage2.hex().upper())

密码是123456,最后打印的结果是 *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9

mysql.user中的authentication_string的值完全一样。

mysql> create user u1@'%' identified with mysql_native_password by '123456';
Query OK, 0 rows affected (0.04 sec)

mysql> select user,host,authentication_string from mysql.user where user='u1';
+------+------+-------------------------------------------+
| user | host | authentication_string                     |
+------+------+-------------------------------------------+
| u1   | %    | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+------+------+-------------------------------------------+
1 row in set (0.00 sec)

有木有一种很简单的感觉?

服务端是如何进行客户端身份验证的?

在 mysql_native_password 中,对客户端进行身份验证是在 native_password_authenticate函数中实现的。

static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio,
                                        MYSQL_SERVER_AUTH_INFO *info) {
  uchar *pkt;
  int pkt_len;
  MPVIO_EXT *mpvio = (MPVIO_EXT *)vio;

  DBUG_TRACE;

  // 生成盐值(Salt)。
  if (mpvio->scramble[SCRAMBLE_LENGTH])
    generate_user_salt(mpvio->scramble, SCRAMBLE_LENGTH + 1);

  // 将盐值发送给客户端
  if (mpvio->write_packet(mpvio, (uchar *)mpvio->scramble, SCRAMBLE_LENGTH + 1))
    return CR_AUTH_HANDSHAKE;

  // 读取客户端的响应,其中pkt用来存储响应包的内容,pkt_len是包的长度。
  if ((pkt_len = mpvio->read_packet(mpvio, &pkt)) < 0) return CR_AUTH_HANDSHAKE;
  DBUG_PRINT("info", ("reply read : pkt_len=%d", pkt_len));

  ...
  // 如果响应包的长度为0,则意味着客户端没有指定密码
  if (pkt_len == 0) {
    info->password_used = PASSWORD_USED_NO;
    return mpvio->acl_user->credentials[PRIMARY_CRED].m_salt_len != 0
               ? CR_AUTH_USER_CREDENTIALS
               : CR_OK;
  } else
    info->password_used = PASSWORD_USED_YES;
  bool second = false;
  // 如果响应包的长度等于盐值的长度,则会验证密码是否正确。
  if (pkt_len == SCRAMBLE_LENGTH) {
    if (!mpvio->acl_user->credentials[PRIMARY_CRED].m_salt_len ||
        check_scramble(pkt, mpvio->scramble,
                       mpvio->acl_user->credentials[PRIMARY_CRED].m_salt)) {
      second = true;
      // 如果验证失败,则会验证第二个密码是否设置且正确。
      // 在 MySQL 8.0 中,一个账户可以设置两个密码。
      if (!mpvio->acl_user->credentials[SECOND_CRED].m_salt_len ||
          check_scramble(pkt, mpvio->scramble,
                         mpvio->acl_user->credentials[SECOND_CRED].m_salt)) {
        return CR_AUTH_USER_CREDENTIALS;
      } else {
        if (second) {...}
        return CR_OK;
      }
    } else {
      return CR_OK;
    }
  }

  my_error(ER_HANDSHAKE_ERROR, MYF(0));
  return CR_AUTH_HANDSHAKE;
}

该函数的主要作用如下:

  1. 通过generate_user_salt生成一个 20 位的盐值(Salt)。

    "盐值"(Salt)是密码学中一个常用的概念。它是一个随机生成的数据块,通常与密码一同进行哈希。

    相同的密码,由于盐值的不同,生成的哈希值也会不同。

    引入盐值可有效防止彩虹表攻击和碰撞攻击,提高密码的安全性。

  2. 将盐值发送给客户端。客户端会基于盐值对明文密码进行加密(具体的加密细节后面会介绍),然后将加密后的结果返回给服务端。

  3. 读取客户端的响应。

  4. 如果响应包的长度等于盐值的长度,则会调用 check_scramble验证客户端返回的加密密码是否与数据库中存储的加密密码相匹配(具体的匹配细节后面会介绍)。

客户端是如何处理明文密码的?

这里以 JDBC 驱动为例,客户端在接受到 MySQL 服务端发送的盐值后,会调用Security类中的scramble411方法对明文密码进行加密。

下面我们看看具体的实现细节。

// src/main/core-impl/java/com/mysql/cj/protocol/Security.java
public static byte[] scramble411(byte[] password, byte[] seed) {
    MessageDigest md;
    try {
        md = MessageDigest.getInstance("SHA-1");
    } catch (NoSuchAlgorithmException ex) {
        throw new AssertionFailedException(ex);
    }

    byte[] passwordHashStage1 = md.digest(password);
    md.reset();

    byte[] passwordHashStage2 = md.digest(passwordHashStage1);
    md.reset();

    md.update(seed);
    md.update(passwordHashStage2);

    byte[] toBeXord = md.digest();

    int numToXor = toBeXord.length;

    for (int i = 0; i < numToXor; i++) {
        toBeXord[i] = (byte) (toBeXord[i] ^ passwordHashStage1[i]);
    }

    return toBeXord;
}

该方法的主要作用如下:

  1. 使用 SHA-1 算法对明文密码(password)进行哈希,生成 passwordHashStage1。

  2. 对生成的 passwordHashStage1 再次使用 SHA-1 算法进行哈希,生成 passwordHashStage2。

  3. 调用md.update方法将 seed(服务端发送的盐值)和 passwordHashStage2 添加到消息摘要中。

  4. 调用md.digest获取最终的摘要值。

  5. 将摘要值中的每个字节与 passwordHashStage1 对应位置的字节进行异或运算。

    这么做,主要为了增加密码处理的复杂性,使得密码在传输过程中较难被破解。

简单来说,就是客户端基于服务端发送的盐值对明文密码进行加密,最后将加密后的结果发送给服务端,并不会直接发送明文密码。

服务端是如何验证客户端密码是否正确的?

在 mysql_native_password 中,验证客户端密码是否正确是在check_scramble_sha1函数中实现的。

static bool check_scramble_sha1(const uchar *scramble_arg, const char *message,
                                const uint8 *hash_stage2) {
  uint8 buf[SHA1_HASH_SIZE];
  uint8 hash_stage2_reassured[SHA1_HASH_SIZE];

  /* create key to encrypt scramble */
  compute_sha1_hash_multi(buf, message, SCRAMBLE_LENGTH,
                          (const char *)hash_stage2, SHA1_HASH_SIZE);
  /* encrypt scramble */
  my_crypt((char *)buf, buf, scramble_arg, SCRAMBLE_LENGTH);

  /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */
  compute_sha1_hash(hash_stage2_reassured, (const char *)buf, SHA1_HASH_SIZE);

  return (memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE) != 0);
}

函数中的 scramble_arg 是客户端返回的加密密码,message 是盐值,hash_stage2 是 authentication_string 的二进制表示。

该函数的具体实现如下:

  1. 调用compute_sha1_hash_multi计算 message 和 hash_stage2 的 SHA-1 哈希值,对应客户端实现中的3、4步。

  1. 将步骤 1 中的结果与客户端返回的加密密码进行异或运算。

  1. 因为 XOR(s1, XOR(s1, s2)) == s2,所以最后得到的结果实际上就是客户端实现中的 passwordHashStage1。

  1. 调用compute_sha1_hash对 passwordHashStage1 进行一次 SHA-1 哈希,生成 hash_stage2_reassured。

  1. 判断 authentication_string 的二进制表示是否与 hash_stage2_reassured 相同。

  1. 如果相同,则意味着客户端输入的密码是正确的,否则是错误的。

看上去有点复杂,但实际上它跟客户端的实现类似。

为什么 MySQL 8.0 要将默认的密码认证插件调整为 caching_sha2_password

在 MySQL 8.0 中,默认的密码认证插件由 mysql_native_password 调整为了 caching_sha2_password。

官方为什么要做这个调整呢?

主要原因还是因为 mysql_native_password 不够安全。

不够安全主要体现在以下两点:

  1. SHA-1 自身不再安全。这主要是指 SHA-1 存在碰撞漏洞,即两个不同的输入可以产生相同的哈希值。

  1. 容易引起彩虹表攻击。

在 mysql_native_password 中,对于同一个明文密码,会生成一个确定的加密密码。

123456对应的加密密码永远是*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9,这就很容易引起彩虹表攻击。

彩虹表(Rainbow Table)是一种密码破解技术,其核心思想是事先计算并存储大量可能的密码和其对应的哈希值。这样,当攻击者获取到加密系统中存储的哈希值时,就可以直接查找对应的明文密码,而无需进行逐一尝试的破解。

所以只要获取到 mysql.user 表 authentication_string 字段的内容,再加上事先构建的彩虹表,破解出明文密码并不是一件难事。

这里顺便介绍个黑科技,在 MySQL 8.0 之前,因为 mysql.user 表使用的是 MyISAM 存储引擎,所以,只要有主机登陆权限,就能通过 vim 查看 authentication_string 字段的内容。

总结

1. mysql.user 中的 authentication_string 字段存储的是HEX(SHA1(SHA1(password)))

2. 服务端对客户端进行身份验证的流程图如下:

服务端在对客户端进行身份验证时,会首先发送一个 20 字节的盐值,客户端接受到这个盐值后,会返回一个通过以下公式计算的加密密码。

SHA1(password) XOR SHA1(seed <concat> SHA1(SHA1(password)))

3. 因为 mysql_native_password 容易引起彩虹表攻击,且 SHA-1 本身就不够安全,所以在 MySQL 8.0 中,默认的是身份验证插件由 mysql_native_password 调整为了 caching_sha2_password。

实际上,caching_sha2_password 底层使用的加密算法(SHA-256)早在 sha256_password 这个认证插件( MySQL 5.6 中引入的)中就使用了。虽然 sha256_password 足够安全,但因为认证速度比较慢,性能不理想,所以在线上用得并不多。

4. caching_sha2_password 在 sha256_password 的基础上,新增了一个内存缓存,用于存储哈希密码,以加快认证速度。

参考

  1. Native Authentication:MySQL: Native Authentication

  2. WL#9591: Caching sha2 authentication plugin:MySQL :: WL#9591: Caching sha2 authentication plugin

  3. WL#10774: Remove old_passwords, PASSWORD(), other deprecated auth features:MySQL :: WL#10774: Remove old_passwords, PASSWORD(), other deprecated auth features

文章转载自:iVictor

原文链接:https://www.cnblogs.com/ivictor/p/17953165

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/315878.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习基本算法:算法流程和算法分类

1、算法流程 机器学习的过程是一个完整的项目周期&#xff0c;其中包括数据的采集、数据的特征提取与分类&#xff0c;之后采用何种算法去创建机器学习模型从而获得预测数据。 算法流程 从上图可以看出一个完整的机器学习项目包含以下这些内容&#xff1a; 输入数据&#x…

小程序系列-5.WXML 模板语法

一、数据绑定 1、在 data 中定义页面的数据 动态绑定内容&#xff1a; 动态绑定属性&#xff1a; 2. Mustache 语法的格式 3. Mustache 语法的应用场景 4. 三元运算 5.算数运算 二、 事件绑定 1. 什么是事件&#xff1f; 2. 小程序中常用的事件 3. 事件对象的属性列表 4.…

玩转Mysql 六(MySQL数据存储结构)

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。如有侵权&#xff0c;请留言&#xff0c;我及时删除&#xff01; 一、MySQL数据存储结构解析 1、mysql数据存储结构的组成 ​ 从 InnoDB 逻辑存储结构来看&a…

将台式机变为服务器,服务器设置静态IP的方法

一.查看IP: 同时按winR&#xff0c;输入cmd&#xff0c;打开终端。输入 ifconfig查看IP地址 查看网关: route -n二、配置静态IP地址 进入root权限 sudo -i进入.yaml文件&#xff0c;开始配置静态IP地址 vim /etc/netplan /*.yaml文件地址是/etc/netplan/01-network-manager-…

Python基础语法汇总【保姆级小白教程】

文章目录 一&#xff1a;Python基础概念1.认识Python&#xff1a;2.Python的优势&#xff1a;3.Python的应用领域&#xff1a;4.Python的执行方式&#xff1a;5.文档&#xff1a; 二&#xff1a;变量与数据类型1.变量&#xff1a;2.id()函数&#xff1a;3.注释&#xff1a;4.基…

二叉树DFS

基础知识 二叉树遍历 二叉搜索树BST 二叉树三种深度遍历 LeetCode 94. 二叉树的中序遍历 class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> ans new ArrayList<>();inorder(root, ans);return ans;}public void in…

NVMe-oF 1.1规范:多路径、非对称命名空间和NVMe/TCP

提到NVMe over Fabric&#xff0c;我就会想到它的几种应用场景&#xff1a; 1、 存储阵列到主机的网络连接&#xff08;替代FC、iSCSI等&#xff09;&#xff1b; 2、 服务器、本地NVMe存储解耦&#xff08;跨机箱/JBOF&#xff09;&#xff0c;SSD存储资源池化共享&#xff…

【基于Java Swing设计药品信息管理系统】——界面美观、功能全,可直接上手使用

一、基本功能描述 药品信息管理系统的选题背景主要是因为现今医疗行业中,药品管理和库存管理都是非常重要而复杂的工作。传统的手动记录、查询等方式耗费人力物力较多,并且容易出错。因此,采用计算机技术来帮助药品信息管理和库存管理已成为必要的趋势。 该药品信息管理系统…

【MATLAB源码-第106期】基于matlab的SAR雷达系统仿真,实现雷达目标跟踪功能,使用卡尔曼滤波算法。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 雷达系统参数设定&#xff1a; - 工作频率&#xff1a;选择一个适合的工作频率&#xff0c;例如X波段&#xff08;8-12 GHz&#xff09;。 - 脉冲重复频率&#xff08;PRF&#xff09;&#xff1a;设定一个适当的PR…

BikeDNA(六)参考数据的内在分析2

BikeDNA&#xff08;六&#xff09;参考数据的内在分析2 1.数据完整性 见链接 2.网络拓扑结构 见链接 3.网络组件 断开连接的组件不共享任何元素&#xff08;节点/边&#xff09;。 换句话说&#xff0c;不存在可以从一个断开连接的组件通向另一组件的网络路径。 如上所述…

WPF实现右键选定TreeViewItem

在WPF中&#xff0c;TreeView默认情况是不支持右键选定的&#xff0c;也就是说&#xff0c;当右键点击某节点时&#xff0c;是无法选中该节点的。当我们想在TreeViewItem中实现右键菜单时&#xff0c;往往希望在弹出菜单的同时选中该节点&#xff0c;以使得菜单针对选中的节点生…

数据结构 模拟实现二叉树(孩子表示法)

目录 一、二叉树的简单概念 &#xff08;1&#xff09;关于树的一些概念 &#xff08;2&#xff09;二叉树的一些概念及性质 定义二叉树的代码&#xff1a; 二、二叉树的方法实现 &#xff08;1&#xff09;createTree &#xff08;2&#xff09;preOrder &#xff08;…

密码学(三)

文章目录 前言一、Software Attestation Overview二、Authenticated Key Agreement三、The Role of Software Measurement 前言 本文来自 Intel SGX Explained 请参考&#xff1a; 密码学&#xff08;一&#xff09; 密码学&#xff08;二&#xff09; 一、Software Attesta…

Javascript jQuery简介

✨前言✨ 1.如果代码对您有帮助 欢迎点赞&#x1f44d;收藏⭐哟 后面如有问题可以私信评论哟&#x1f5d2;️ 2.博主后面将持续更新哟&#x1f618;&#x1f389;本章目录&#x1f389; &#x1f95d;一.jQuery简介&#x1f965;二.JQeury常用API&#x1f347;1.jQeury选择…

Eclipse插件UCdetector清理无用JAVA代码

下载插件 UCDetector - Browse /ucdetector at SourceForge.net 目前最新版本是2017年的2.0.0 保存 Eclipse/dropins 重启 操作 在项目上右键

JavaScript Web Worker用法指南

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》 ​ ​ ✨ 前言 Web Worker可以将耗时任务放到后台执行,避免阻塞UI。本文将详细介绍Web Worker的用法,让你…

【AWS】使用亚马逊云服务器创建EC2实例

目录 前言为什么选择 Amazon EC2 云服务器搭建 Amazon EC2 云服务器注册亚马逊账号登录控制台服务器配置免费套餐预览使用 Amazon EC2 云服务器打开服务器管理界面设置服务器区域填写实例名称选择服务器系统镜像选择实例类型创建密钥对网络设置配置存储启动实例查看实例 总结 前…

基于SSM中小型医院管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

高质量训练数据助力大语言模型摆脱数据困境 | 景联文科技

目前&#xff0c;大语言模型的发展已经取得了显著的成果&#xff0c;如OpenAI的GPT系列模型、谷歌的BERT模型、百度的文心一言模型等。这些模型在文本生成、问答系统、对话生成、情感分析、摘要生成等方面都表现出了强大的能力&#xff0c;为自然语言处理领域带来了新的突破。 …

面向零信任架构的访问安全态势评估

伴随着“云大物移”等新兴 IT 技术的快速发展&#xff0c;企业数字化转型使得 IT 业务的网络环境更加复杂多样&#xff0c;企业数字资源的安全防护正面临着前所未有的压力与威胁。零信任安全架构放弃了传统基于“边界”的安全模型&#xff0c;以访问上下文的安全态势感知为基础…