0

得物技术浅谈MySQL 8.0:新的身份验证插件(caching_sha2_password)

 2 years ago
source link: https://segmentfault.com/a/1190000040733952
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

得物技术浅谈MySQL 8.0:新的身份验证插件(caching_sha2_password)

从 MySQL 8.0.4 开始,默认身份验证插件从 mysql_native_password 更改为 caching_sha2_password。相应地,现在的 libmysqlclient 将使用 caching_sha2_password 作为默认的验证机制。

为什么这样做呢?

MySQL 5.6/5.7 的默认密码插件一直以来都是 mysql_native_password。其优点是它支持 challenge-response 机制,这是非常快的验证机制,无需在网络中发送实际密码,并且不需要加密的连接。然而,mysql_native_password 依赖于 SHA1 算法,但 NIST(美国国家标准与技术研究院)已建议停止使用 SHA1 算法,因为 SHA1 和其他哈希算法(例如 MD5)已被证明非常容易破解。

此外,由于 mysql_native_password 在 mysql.user 表中 authentication_string 字段存储的是两次哈希 SHA1(SHA1(password)) 计算的值 ,也就是说如果两个用户帐户使用相同的密码,那么经过 mysql_native_password 转换后在 mysql.user 表得到的哈希值相同。尽管有 hash 值也无法得到实际密码信息,但它仍然告诉这两个用户使用了相同的密码。为了避免这种情况,应该给密码加盐(salt),salt 基本上是被用作输入,用于转换用户密码的加密散列函数。由于 salt 是随机的,即使两个用户使用相同的密码,转换后的最终结果将发生较大的变化。

从 MySQL 5.6 开始支持 sha256_password 认证插件。它使用一个加盐密码(salted password)进行多轮 SHA256 哈希(数千轮哈希,暴力破解更难),以确保哈希值转换更安全。然而,它需要要么在安全连接或密码使用 RSA 秘钥对加密。所以,虽然密码的安全性更强,但安全连接和多轮 hash 转换需要在认证过程中的时间更长。

为了克服这些限制,从 MySQL 8.0.3 开始,引入了一个新的身份验证插件 caching_sha2_password。从 MySQL 8.0.4 开始,此插件成为 MySQL 服务器的新默认身份验证插件。caching_sha2_password 尝试一个两全其美的结合,既解决安全性问题又解决性能问题。

首先,是 caching_sha2_password 对用户密码的处理,其实主要是 sha256_password 的机制:

  • 使用 SHA2 算法来转换密码。具体来说,它使用 SHA256 算法。
  • 保存在 authentication_string 列中的哈希值为加盐后的值,由于盐是一个 20-byte 的随机数,即使两个用户使用相同的密码,转换后的最终结果也将完全不同。
  • 为了使使用暴力破解机制更难以猜测密码,在将最终转换存储在 mysql.user 表中之前,对密码和盐进行了 5000 轮 SHA2 散列。

为了实现加盐机制,列 authentication_string 需保存保存盐值,因此 authentication_string 值的长度变为了 70 个字节:

mysql> select user, host, authentication_string, length(authentication_string), plugin from mysql.user limit 1;
+------+------+------------------------------------------------------------------------+-------------------------------+-----------------------+
| user | host | authentication_string                                                  | length(authentication_string) | plugin                |
+------+------+------------------------------------------------------------------------+-------------------------------+-----------------------+
| root | %    | $A$005$1%h5f1OdZ0'46}M[uz5Di5wW2WWg8eeLWynsg2h3xnzHwQLmm39bEqLBxB0   |                            70 | caching_sha2_password |
+------+------+------------------------------------------------------------------------+-------------------------------+-----------------------+
1 row in set (0.00 sec)

新 caching_sha2_password 认证机制下,authentication_string 中的字节,例如上面的字符串$A$005$1%h5f1OdZ0'46}M[uz5Di5wW2WWg8eeLWynsg2h3xnzHwQLmm39bEqLBxB0,其中分别保存如下内容:

内容字节数说明哈希算法2字节目前仅为 $A,表示 SHA256 算法哈希轮转次数4字节目前仅为 $005,表示 5*1000=5000 次盐(salt)21字节用于解决相同密码相同哈希值问题哈希值43字节

从 MySQL 8.0.24 开始,提供了 caching_sha2_password_digest_rounds 系统变量,默认值和最小值是 5000,最大值 4095000;用于 caching_sha2_password 认证插件密码存储的哈希轮转次数。

其次,caching_sha2_password 是在服务器端通过缓存解决性能问题。caching_sha2_password 插件使用内存缓存来为曾经连接过的客户端进行快速验证。内存缓存条目由username/SHA256(SHA256(user_password))对组成。缓存的工作原理是这样的:

  1. 当客户端连接,caching_sha2_password 检查 username/SHA256(SHA256(user_password)) 是否匹配了缓存条目。如果匹配,验证成功。
  2. 如果没有匹配的缓存条目,插件会继续与客户端交换数据包,尝试使用 mysql.user 系统表的凭证验证客户端。如果成功,caching_sha2_password 增加对客户端的散列条目。否则,认证失败,连接被拒绝。

这样,当客户端第一次连接,使用 mysql.user 系统表的凭据进行认证。当客户端连接之后,使用缓存进行快速认证。

对于大多数的连接尝试,当内存缓存中存在于的密码哈希的副本时,它采用了基于 SHA256 的 challenge-response 机制认证客户端(mysql_native_password 是基于 SHA1 的 challenge-response 机制)。这样会更快,并且允许通过未加密的通道进行安全认证。

下面总结基于 challenge-response 的认证模式(也称之为 Fast authentication 模式):

  1. 客户端连接服务端
  2. 服务端给客户端发送 Nonce(20 字节长的随机数据)
  3. 客户端使用 XOR(SHA256(password), SHA256(SHA256(SHA256(password)), nonce)) 生成 Scramble 发送给服务端
  4. 服务端检查 username/SHA256(SHA256(user_password)) 是否在内存缓存条目中存在,存在则证明合法;发送 fast_auth_success 包到客户端
  5. 服务端发送 OK 包到客户端
  6. 进入命令阶段

在信息安全中,Nonce 是一个在加密通信只能使用一次的数字。在认证协议中,它往往是一个随机或伪随机数(salt),以避免暴力攻击。

由于 caching_sha2_password 插件在使用缓存的情况下可以快速认证,但在以下情况下是无效的,对于某些或所有用户:

  • 当用户的密码被更改时,用户缓存的密码哈希值都被从内存中删除。密码可以通过 ALTER USER/SET PASSWORD/GRANT 改变。
  • 当用户被删除时,或证书、或认证插件改变;用户缓存的密码哈希值都被从内存中删除。
  • 当用户使用 RENAME USER 重命名时,用户缓存的密码哈希值都被从内存中删除。
  • 当执行 FLUSH PRIVILEGES 时,所有缓存的密码哈希值都被从内存中删除,影响所有用户。\
    服务器关闭时会清空缓存。

在缓存失效的情况下会影响后续的客户端连接验证要求。caching_sha2_password 需要用户第一客户端连接必须使用安全连接(TCP 连接使用 TLS、Unix 套接字文件、或共享内存)或使用 RSA 加密密码进行交换。

考虑到用户的密码变化和 FLUSH PRIVILEGES 是不经常执行的操作,所以在大多数情况下,基于 challenge-response 认证就足够了。下面总结了基于完整认证模式(perform_full_authentication)的机制(也称之为 Complete authentication 模式)。

  1. 客户端连接服务端
  2. 服务端给客户端发送 Nonce(20 字节长的随机数据)
  3. 客户端使用 XOR(SHA256(password), SHA256(SHA256(SHA256(password)), nonce)) 生成 Scramble 发送给服务端
  4. 服务端检查 username/SHA256(SHA256(user_password)) 是否在内存缓存条目中存在,不存在则发送 perform_full_authentication 包到客户端继续认证
  5. 客户端收到 perform_full_authentication 包,可以进行如下处理
  6. 如果连接已经建立基于 SSL 的安全通道,则可以直接发送明文密码到服务端\
    向服务端发起获取公钥的请求(或者指定服务端公钥文件),使用公钥+Nonce加密密码,发送加密后的密码到服务端\
    服务器通过 SHA256 算法计算得到哈希值,判断是否用户认证通过,通过则发送 OK 包到客户端\
    进入命令阶段

总结来说 caching_sha2_password 插件工作机制分为两个阶段,Complete authentication 和 Fast authentication。各自认证机制上面已经阐述了。更加详细的过程,以及服务端和客户端状态的转换,官方在源码文档里面提供有一张图和说明可以参考,地址在文末。

改变了什么呢?

在 MySQL 8.0.4 之后创建的所有新用户将默认使用 caching_sha2_password 作为他们的身份验证插件。

mysql> CREATE USER 'sha2user'@'localhost' IDENTIFIED BY '42';
Query OK, 0 rows affected (0.02 sec)

mysql> SHOW CREATE USER 'arthurdent'@'localhost'\G
CREATE USER for sha2user@localhost: CREATE USER 'sha2user'@'localhost' IDENTIFIED WITH 'caching_sha2_password' AS '$Afnka//BGe\d3h\n<:MTEFNZ3U40FRyPrdT5V14x526MHPENmY5Tn0RbjwA16' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT
1 row in set (0.01 sec)

libmysqlclient 使用 caching_sha2_password 作为默认选择连接到 MySQL 服务器。请注意,这只是在默认值的变化,libmysqlclient 能够支持所有现有的身份验证插件。 MySQL 的 server-client 协议会负责切换每个用户的帐户所需要的认证插件。

如果你不想使用默认的 caching_sha2_password 插件,也可以使用一些其他的插件创建帐户,你必须明确指定插件。例如,使用 mysql_native_password 插件,使用此语句:

CREATE USER 'nativeuser'@'localhost'
IDENTIFIED WITH mysql_native_password BY 'password';

caching_sha2_password 支持安全连接传输,如果你按照下面给出的 RSA 配置过程,它同样也支持在未加密的连接上使用 RSA 加密密码进行交换。RSA 支持以下特性:

  • 在服务器端,两个系统变量命名 RSA 私钥和公钥对的文件:caching_sha2_password_private_key_path 和 caching_sha2_password_public_key_path。如果想改变其默认值,则必须在服务器启动时设置变量。
  • 服务器使用 auto_generate_certs、sha256_password_auto_generate_rsa_keys 和 caching_sha2_password_auto_generate_rsa_keys 系统变量,以确定是否自动生成 RSA 密钥对文件。这些变量默认情况下启用。他们可以在服务器启动时启用和检查,但不是在运行时设置。详情参见“Creating SSL and RSA Certificates and Keys”
  • 状态变量 Caching_sha2_password_rsa_public_key 显示由 caching_sha2_password 认证插件使用的 RSA 公钥值。
  • 客户端持有 RSA 公钥时可以在连接过程中执行与服务器 RSA 密钥对进行密码交换,如后所述。
  • 对于使用 caching_sha2_password 和 RSA 密钥进行身份验证的帐户的连接,服务器默认是不会发送 RSA 公钥给客户端。客户端可以使用所需的公钥的副本,或从服务端发起请求公钥。需要说明的是,本地使用受信任的公钥的副本,使得客户端能够避免在 client/server 协议的往返,比从服务器请求的公钥更安全。在另一方面,从服务器请求公钥更方便(它不需要在客户端管理文件),在安全的网络环境是可以接受的。

对于使用 caching_sha2_password 插件的客户端,连接到服务器时,密码不会暴露为明文。密码传输是如何进行的取决于是否使用安全连接或 RSA 对密码加密:

  • 如果连接是安全的,RSA 密钥对是不必要的。这适用于使用 TLS 加密的 TCP 连接,以及 Unix 套接字文件和共享内存连接。密码以明文格式发送,但不能被窃听,因为连接是安全的。
  • 如果连接不是安全的,使用了 RSA 密钥对。这适用于未使用 TLS 加密的 TCP 连接和 named-pipe 连接。RSA 仅用于客户端和服务器之间的密码交换,防止密码被截取。当服务器接收到使用公钥加密的密码后,它使用私钥解密。一个随机字符串用在加密中,防止重放攻击(repeat attacks)。

要让客户端在连接过程中能够使用 RSA 密钥对进行密码交换,请使用以下步骤(MySQL 8.0.3 以上版本默认自动完成):

  1. 创建 RSA 私钥和公钥对文件
  2. 如果私钥和公钥文件都位于数据目录中,名为 private_key.pem 和 public_key.pem(是 caching_sha2_password_private_key_path 和 caching_sha2_password_public_key_path 系统变量的默认值),服务器在启动时自动使用它们。否则需要在配置文件中指定私钥和公钥文件的位置。
  3. 重新启动服务器后,检查 Caching_sha2_password_rsa_public_key 状态变量的值。该值将与这里所示的不同,但应非空:
mysql> SHOW STATUS LIKE 'Caching_sha2_password_rsa_public_key'\G
Variable_name: Caching_sha2_password_rsa_public_key
Value: -----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDO9nRUDd+KvSZgY7cNBZMNpwX6
MvE1PbJFXO7u18nJ9lwc99Du/E7lw6CVXw7VKrXPeHbVQUzGyUNkf45Nz/ckaaJa
aLgJOBCIDmNVnyU54OT/1lcs2xiyfaDMe8fCJ64ZwTnKbY2gkt1IMjUAB5Ogd5kJ
g8aV7EtKwyhHb0c30QIDAQAB
-----END PUBLIC KEY-----

如果该值为空,检查错误日志中的诊断信息。

服务器已经配置了 RSA 密钥文件之后,用户在使用 caching_sha2_password 插件进行身份验证,这些密钥文件连接到服务器的选项。如前面提到的,这时候用户可以使用安全连接(在这种情况下不使用 RSA)或者在未加密的连接下使用 RSA 执行密码交换。假设使用未加密的连接时,例如:

shell> mysql --ssl-mode=DISABLED -u sha2user -p
Enter password: password

尝试通过 sha2user 用户连接,服务器确认 caching_sha2_password 认证插件对 sha2user 是合适的并调用它。插件查找到该连接未被加密,因此需要使用 RSA 加密被发送的密码。但是,服务端不会发送公钥给客户端,由于客户端没有提供公钥,所以它不能加密密码,连接失败:

ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password'
reported error: Authentication requires secure connection.

为了从服务器请求的 RSA 公共密钥,指定--get-server-public-key选项:

shell> mysql --ssl-mode=DISABLED -u sha2user -p --get-server-public-key
Enter password: password

在这种情况下,服务器发送的 RSA 公钥给客户端,客户端使用它来加密密码,并将结果返回到服务器。该插件使用服务器端的 RSA 私钥来解密密码,并根据密码是否正确来决定接受或拒绝连接。

另外,如果客户端具有服务器所需的 RSA 公钥文件,可以使用--server-public-key-path选项指定文件:

shell> mysql --ssl-mode=DISABLED -u sha2user -p --server-public-key-path=file_name
Enter password: password

在这种情况下,客户端使用公钥来加密密码并将结果返回到服务器。该插件使用服务器端的 RSA 私钥来解密密码,并根据密码是否正确来决定接受或拒绝连接。

由--server-public-key-path选项指定的文件中的公钥值应该与caching_sha2_password_public_key_path系统变量命名的服务器端文件的键值相同。如果密钥文件包含一个有效的公钥值,但该值不正确,会出现拒绝访问错误。但如果密钥文件不包含一个有效的公钥值,客户端程序不能使用它(这是因为客户端做过公钥正确性校验)。

客户端用户可以得到 RSA 公钥的两种方式:

  • 数据库管理员可以提供公钥文件的副本。
  • 可以连接到服务器的客户端用户,可以使用SHOW STATUS LIKE "Caching_sha2_password_rsa_public_key"语句返回公钥值,并保存在一个文件中。

对复制的影响?

复制本身是支持加密的连接。在 MySQL 8.0.4,复制也进行了 RSA 密钥对的支持。

  • CHANGE MASTER 现在支持了两个参数来启用基于 caching_sha2_password RSA 密钥来交换密码

    • MASTER_PUBLIC_KEY_PATH ="key_file_path",指定 RSA 公钥路径
    • GET_MASTER_PUBLIC_KEY = {0 | 1},从服务端获取 RSA 公钥
  • Group Replication 现在支持了两个参数来启用基于 caching_sha2_password RSA 密钥来交换密码

    • ––group-replication-recovery-public-key-path,指定 RSA 公钥路径
    • ––group-replication-recovery-get-public-key,从服务端获取 RSA 公钥

如果复制通道是安全的,那么自然也就不需要使用 RSA 公钥来交换密码。

<参考>

https://mp.weixin.qq.com/s/He...

https://mp.weixin.qq.com/s/jf...

https://dev.mysql.com/doc/ref...

https://dev.mysql.com/doc/dev...

https://mysqlserverteam.com/m...

关注得物技术,做最潮技术人!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK