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 端必须提供一个密码,该密码的哈希值与该 Client 端要使用的帐户的usertable 中存储的哈希值匹配。

  • Client 端连接后,它可以(如果具有足够的特权)设置或更改usertable 中列出的帐户的密码哈希。Client 端可以通过使用PASSWORD()函数来生成密码哈希或通过使用密码生成语句(CREATE USERGRANTSET PASSWORD)来执行此操作。

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

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

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

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

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

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

4.1 哈希方法

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

  • PASSWORD()函数生成的密码值的格式不同

  • Password列的扩大

  • 控制默认的哈希方法

  • 控制 Client 端尝试连接到服务器的允许的哈希方法

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

  • MySQL 4.1.0 使用了 4.1 哈希方法的初步版本。这种方法是短暂的,下面的讨论仅说明这一点。

  • 在 MySQL 4.1.1 中,修改了哈希方法以产生更长的 41 字节哈希值:

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

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

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

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

  • 长度:4.1 和 4.1 之前的哈希分别为 41 和 16 个字节。

    • 4.1 格式的密码散列总是以*字符开头,而 4.1 之前的格式则永远不会。

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

  • 添加了OLD_PASSWORD()函数,该函数以 16 字节格式返回哈希值。

    • 出于兼容性目的,添加了old_passwords系统变量,以使 DBA 和应用程序可以控制散列方法。默认的old_passwords值 0 导致哈希使用 4.1 方法(41 字节哈希值),但是设置old_passwords=1导致哈希使用 4.1 之前的方法。在这种情况下,PASSWORD()产生 16 个字节的值,并且等效于OLD_PASSWORD()

为了允许 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 的新安装,则Password列会自动设置为 41 个字节长。

  • 从 MySQL 4.1 或更高版本升级到当前版本的 MySQL 不会引起有关Password列的任何问题,因为两个版本都使用相同的列长和密码哈希方法。

  • 要从 4.1 之前的版本升级到 4.1 或更高版本,必须在升级后升级系统 table。 (请参阅第 4.4.7 节“ mysql_upgrade-检查和升级 MySQLtable”。)

只有 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 端可以连接,但是由于它们仅了解 4.1 之前的哈希方法,因此它们只能使用具有短哈希值的帐户进行身份验证。

    • 4 .1 及更高版本的 Client 端可以使用具有短哈希值或长哈希值的帐户进行身份验证。

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

  • 4.1 版之前的 Client 端通过短密码哈希进行身份验证

  • 4 .1 或更高版本的 Client 端通过短密码哈希进行身份验证

  • 4 .1 或更高版本的 Client 端通过长密码哈希进行身份验证

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

这些条件如下:

  • Password列必须足够宽以容纳较长的哈希(41 个字节)。如果该列尚未更新,并且仍具有 4.1 之前的宽度(16 字节),则服务器会注意到长哈希不能容纳该列,并且当 Client 端使用PASSWORD()函数或密码-生成语句。如果您已经从 MySQL 的 4.1 之前的版本升级到 4.1 或更高版本,但是还没有运行mysql_upgrade程序来加宽Password列,则会出现这种情况。

  • 如果Password列较宽,则可以存储短密码哈希值或长密码哈希值。在这种情况下,除非服务器在old_passwords系统变量设置为 1 的情况下启动服务器,否则PASSWORD()函数和密码生成语句会生成长哈希,以强制服务器生成短密码哈希。

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

  • 旧的 4.1 之前的 Client 端连接到具有短密码哈希的帐户。

  • Client 端更改其自己的密码。在禁用old_passwords的情况下,这将导致该帐户具有较长的密码哈希。

  • 旧 Client 端下次尝试连接到该帐户时,它将无法连接,因为该帐户具有较长的密码哈希,需要在身份验证期间使用 4.1 哈希方法。 (一旦帐户在用户 table 中具有长密码哈希,由于 4.1 之前的 Client 端不了解长哈希,因此只有 4.1 和更高版本的 Client 端可以对其进行身份验证.)

此方案说明,如果必须支持旧版 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短栏:

  • 只能将短哈希存储在Password列中。

  • 服务器在 Client 端身份验证期间仅使用短哈希。

  • 对于连接的 Client 端,涉及PASSWORD()函数的密码哈希生成操作或密码生成语句仅使用短哈希。帐户密码的任何更改都会导致该帐户具有较短的密码哈希。

  • old_passwords的值无关紧要,因为对于短Password列,服务器始终只生成短密码哈希。

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

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

  • 短哈希或长哈希可以存储在Password列中。

  • 4 .1 及更高版本的 Client 端可以验证具有短哈希值或长哈希值的帐户。

  • 4.1 之前的 Client 只能对具有短哈希值的帐户进行身份验证。

  • 对于连接的 Client 端,涉及PASSWORD()函数的密码哈希生成操作或密码生成语句仅使用短哈希。帐户密码的任何更改都会导致该帐户具有较短的密码哈希。

在这种情况下,新创建的帐户具有较短的密码哈希,因为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开头:

  • 短哈希或长哈希可以存储在Password列中。

  • 4 .1 及更高版本的 Client 端可以使用具有短哈希值或长哈希值的帐户进行身份验证。

  • 4.1 之前的 Client 端只能使用具有短哈希值的帐户进行身份验证。

  • 对于已连接的 Client 端,涉及PASSWORD()函数或密码生成语句的密码哈希生成操作仅使用长哈希。更改帐户密码会导致该帐户具有较长的密码哈希。

如前所述,这种情况下的危险是 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 端将无法访问具有短哈希值的帐户。

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

  • 将所有 Client 端程序升级到 MySQL 4.1 或更高版本。

  • 使用old_passwords=0运行服务器。

  • 为任何使用短密码哈希的帐户重置密码,以使用长密码哈希。

  • 为了提高安全性,请使用secure_auth=1运行服务器。