6.1.2.4 MySQL 中的密码哈希

Note

本节中的信息仅在 MySQL 5.7.5 之前完全适用,并且仅适用于使用mysql_native_passwordmysql_old_password身份验证插件的帐户。在 MySQL 5.7.5 中删除了对 4.1 之前版本的密码哈希的支持。这包括删除mysql_old_password身份验证插件和OLD_PASSWORD()功能。另外,不能禁用secure_auth,并且不能将old_passwords设置为 1.

从 MySQL 5.7.5 开始,只有有关 4.1 密码哈希和mysql_native_password身份验证插件的信息仍然相关。

MySQL 在mysql数据库的usertable 中列出了用户帐户。可以为每个 MySQL 帐户分配一个密码,尽管usertable 不存储密码的明文版本,而是从中计算出的哈希值。

MySQL 在 Client 端/服务器通信的两个阶段使用密码:

换句话说,当 Client 端首次尝试连接时,服务器会在身份验证期间“检查”哈希值。如果连接的 Client 端调用PASSWORD()函数或使用密码生成语句设置或更改密码,则服务器“生成”哈希值。

MySQL 中的密码哈希方法具有以下描述的历史记录。这些变化通过计算密码哈希值的PASSWORD()函数的结果以及存储密码的usertable 的结构的变化来说明。

原始(4.1 之前的)哈希方法

原始的哈希方法产生了一个 16 字节的字符串。这样的哈希看起来像这样:

mysql> SELECT PASSWORD('mypass');
+--------------------+
| PASSWORD('mypass') |
+--------------------+
| 6f8c114b58f2ce9e   |
+--------------------+

要存储帐户密码,此时usertable 的Password列的长度为 16 个字节。

4.1 哈希方法

MySQL 4.1 引入了密码哈希,该哈希提供了更好的安全性并降低了密码被拦截的风险。此更改有几个方面:

MySQL 4.1 的更改分为两个阶段:

mysql> SELECT PASSWORD('mypass');
+-------------------------------------------+
| PASSWORD('mypass')                        |
+-------------------------------------------+
| *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 |
+-------------------------------------------+

较长的密码哈希格式具有更好的加密属性,并且基于长哈希的 Client 端身份验证比基于较早的短哈希的 Client 端身份验证更安全。

为了容纳更长的密码哈希,此时将usertable 中的Password列更改为其当前长度 41 字节。

加宽的Password列可以存储 4.1 之前和 4.1 格式的密码哈希。任何给定哈希值的格式可以通过两种方法确定:

为了允许显式生成 4.1 之前的密码哈希,进行了两个附加更改:

为了允许 DBA 控制如何允许 Client 端连接,添加了secure_auth系统变量。在禁用或启用此变量的情况下启动服务器将允许或禁止 Client 端使用旧的 4.1 之前版本的密码哈希方法进行连接。在 MySQL 5.6.5 之前,默认情况下禁用secure_auth。从 5.6.5 开始,默认情况下已启用secure_auth以促进更安全的默认配置,DBA 可以自行决定禁用该配置,但是不建议这样做,并且不建议使用 4.1 之前的密码哈希。 (有关帐户升级的说明,请参见第 6.4.1.3 节“迁移到 4.1 版之前的密码哈希和 mysql_old_password 插件”。)

此外,mysqlClient 端支持--secure-auth选项,该选项类似于secure_auth,但来自 Client 端。它可用于防止连接到使用 4.1 之前的密码哈希的安全性较低的帐户。默认情况下,此选项在 MySQL 5.6.7 之前是禁用的,此后才启用。

与散列方法有关的兼容性问题

MySQL 4.1 中的Password列从 16 字节扩展到 41 字节会影响安装或升级操作,如下所示:

只有 MySQL 4.1(及更高版本)的服务器和 Client 端才理解 4.1 哈希方法,这可能会导致一些兼容性问题。 4.1 或更高版本的 Client 端可以连接到 4.1 之前的服务器,因为该 Client 端理解 4.1 之前的密码和 4.1 密码哈希方法。但是,尝试连接到 4.1 或更高版本服务器的 4.1 之前版本的 Client 端可能会遇到困难。例如,一个 4.0 mysqlClient 端可能会失败,并显示以下错误消息:

shell> mysql -h localhost -u root
Client does not support authentication protocol requested
by server; consider upgrading MySQL client

升级到 MySQL 4.1 或更高版本后,尝试使用较旧的 PHP mysqlextensions 也会发生这种现象。 (请参阅MySQL 和 PHP 的常见问题。)

以下讨论描述了 4.1 之前版本和 4.1 之前的哈希方法之间的区别,以及升级服务器但需要保持与 4.1 之前版本的 Client 端的向后兼容性时应采取的措施。 (但是,不建议允许旧 Client 端进行连接,如果可能的话,应避免使用该信息.)此信息对于 PHP 程序员将 MySQL 数据库从 4.1 之前的版本迁移到 4.1 或更高版本特别重要。

短密码散列和长密码散列之间的差异与服务器在身份验证期间如何使用密码以及如何为执行密码更改操作的已连接 Client 端生成密码散列有关。

验证期间服务器使用密码哈希的方式受Password列的宽度影响:

即使对于短哈希帐户,对于 4.1 和更高版本的 Client 端,身份验证过程实际上也比较旧的 Client 端更安全。在安全性方面,从最小到最安全的梯度是:

服务器为连接的 Client 端生成密码哈希的方式受Password列的宽度和old_passwords系统变量的影响。仅当满足某些条件时,4.1 或更高版本的服务器才会生成长哈希:Password列必须足够宽以容纳长值,并且old_passwords不得设置为 1.

这些条件如下:

old_passwords系统变量的目的是在服务器否则会生成长密码哈希的情况下,允许与 4.1 之前版本的 Client 端向后兼容。该选项不会影响身份验证(4.1 和更高版本的 Client 端仍可以使用具有长密码散列的帐户),但是它确实可以防止由于密码更改操作而在usertable 中创建长密码散列。如果允许发生该帐户,那么 4.1 之前的 Client 将无法再使用该帐户。禁用old_passwords时,可能发生以下不良情况:

此方案说明,如果必须支持旧版 4.1 之前的 Client 端,则在不将old_passwords设置为 1 的情况下运行 4.1 或更高版本的服务器是有问题的。通过使用old_passwords=1运行服务器,密码更改操作不会生成长密码哈希,因此不会导致老 Client 无法访问帐户。 (这些 Client 端不能通过更改密码并以长密码哈希结尾而无意中将自己锁定.)

old_passwords=1的缺点是,即使对于 4.1 或更高版本的 Client 端,创建或更改的所有密码都使用短哈希。因此,您将失去长密码哈希提供的额外安全性。若要创建具有长哈希值的帐户(例如,供 4.1Client 端使用)或将现有帐户更改为使用长密码哈希值,Management 员可以将会话值old_passwords设置为 0,同时保留全局值至 1:

mysql> SET @@SESSION.old_passwords = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@SESSION.old_passwords, @@GLOBAL.old_passwords;
+-------------------------+------------------------+
| @@SESSION.old_passwords | @@GLOBAL.old_passwords |
+-------------------------+------------------------+
|                       0 |                      1 |
+-------------------------+------------------------+
1 row in set (0.00 sec)

mysql> CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'newpass';
Query OK, 0 rows affected (0.03 sec)

mysql> SET PASSWORD FOR 'existinguser'@'localhost' = PASSWORD('existingpass');
Query OK, 0 rows affected (0.00 sec)

在 MySQL 4.1 或更高版本中,可能出现以下情况。这些因素包括Password列是短还是长,以及如果长,则服务器是在启用或禁用old_passwords的情况下启动的。

方案 1: 用户 table 中的Password短栏:

当 4.1 之前的 MySQL 安装已升级到 4.1 或更高版本,但尚未运行mysql_upgrade来升级mysql数据库中的系统 table 时,就会发生这种情况。 (这不是建议的配置,因为它不允许使用更安全的 4.1 密码哈希.)

方案 2:Password列;服务器以old_passwords=1开头:

在这种情况下,新创建的帐户具有较短的密码哈希,因为old_passwords=1可以防止生成较长的哈希。同样,如果您在将old_passwords设置为 1 之前创建具有长哈希值的帐户,则在old_passwords=1时更改帐户密码会导致为该帐户提供短密码,从而使它失去了较长哈希值的安全性。

若要创建一个具有长密码哈希的新帐户,或将任何现有帐户的密码更改为使用长哈希,请首先将会话值old_passwords设置为 0,同时将全局值设置为 1,如上所述。

在这种情况下,服务器具有最新的Password列,但是在运行时将默认密码哈希方法设置为生成 4.1 之前的哈希值。这不是建议的配置,但是在过渡期间(4.1 之前的 Client 端和密码升级到 4.1 或更高版本)可能有用。完成此操作后,最好使用old_passwords=0secure_auth=1运行服务器。

方案 3:Password列;服务器以old_passwords=0开头:

如前所述,这种情况下的危险是 4.1 之前的 Client 端可能无法访问具有短密码哈希的帐户。使用PASSWORD()函数或生成密码的语句更改此帐户的密码会导致为该帐户提供较长的密码哈希。从那时起,任何 4.1 之前的 Client 端都无法使用该帐户连接到服务器。Client 端必须升级到 4.1 或更高版本。

如果这是一个问题,则可以通过特殊方式更改密码。例如,通常您按以下方式使用SET PASSWORD来更改帐户密码:

SET PASSWORD FOR 'some_user'@'some_host' = PASSWORD('password');

要更改密码但创建短哈希,请改用OLD_PASSWORD()函数:

SET PASSWORD FOR 'some_user'@'some_host' = OLD_PASSWORD('password');

OLD_PASSWORD()在您明确希望生成短哈希的情况下很有用。

上述每种情况的缺点可以总结如下:

在方案 1 中,您不能利用较长的哈希来提供更安全的身份验证。

在方案 2 中,old_passwords=1防止具有短哈希的帐户变得不可访问,但是密码更改操作会导致具有长哈希的帐户恢复为短哈希,除非您首先将old_passwords的会话值更改为 0.

在方案 3 中,如果您在未显式使用OLD_PASSWORD()的情况下更改其密码,则 4.1 之前版本的 Client 端将无法访问具有短哈希值的帐户。

避免与短密码哈希相关的兼容性问题的最佳方法是不使用它们:

首页