Documentation Home
MySQL 8.0 参考手册  / 第 6 章 安全  / 6.1 一般安全问题  / 6.1.2 保证密码安全  /  6.1.2.4 MySQL 中的密码散列

6.1.2.4 MySQL 中的密码散列

笔记

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

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

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

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

  • 当客户端尝试连接到服务器时,有一个初始身份验证步骤,客户端必须提供一个密码,该密码的哈希值与存储在user客户端要使用的帐户的表中的哈希值相匹配。

  • 客户端连接后,它可以(如果它有足够的权限)设置或更改user表中列出的帐户的密码哈希。客户端可以通过使用 PASSWORD()生成密码哈希的函数,或使用密码生成语句(CREATE USERGRANTSET PASSWORD)来实现这一点。

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

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

原始的(Pre-4.1)哈希方法

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

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

为了存储帐户密码,表的Passworduser此时为 16 字节长。

4.1 散列法

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

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

  • Password列 的加宽

  • 控制默认的散列方法

  • 控制尝试连接到服务器的客户端允许的散列方法

MySQL 4.1 的变化分两个阶段进行:

  • MySQL 4.1.0 使用了 4.1 哈希方法的初步版本。这种方法是短暂的,下面的讨论没有更多关于它的内容。

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

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

    较长的密码哈希格式具有更好的加密特性,基于长哈希的客户端身份验证比基于较旧的短哈希的客户端身份验证更安全。

    为了容纳更长的密码哈希值,此时表中的 Passworduser更改为 41 字节,即当前长度。

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

    • 长度:4.1 和 pre-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 控制允许客户端连接的方式,secure_auth 添加了系统变量。在禁用或启用此变量的情况下启动服务器允许或禁止客户端使用较旧的 pre-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 插件迁移”。)

    此外,mysql客户端支持一个 --secure-auth类似于 的选项secure_auth,但是来自客户端。它可用于防止连接到使用 pre-4.1 密码散列的安全性较低的帐户。该选项在 MySQL 5.6.7 之前默认禁用,之后启用。

与哈希方法相关的兼容性问题

MySQL 4.1中的列从 16 字节加宽Password到 41 字节对安装或升级操作的影响如下:

  • 如果您执行 MySQL 的全新安装,该 Password列会自动变为 41 字节长。

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

  • 对于从 4.1 之前的版本升级到 4.1 或更高版本,您必须在升级后升级系统表。(参见 第 4.4.7 节,“mysql_upgrade — 检查和升级 MySQL 表”。)

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

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

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

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

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

  • 如果列很短,则仅使用短散列验证。

  • 如果列很长,它可以包含短散列或长散列,并且服务器可以使用任一格式:

    • 4.1 之前的客户端可以连接,但是因为它们只知道 4.1 之前的散列方法,所以它们只能使用具有短散列的帐户进行身份验证。

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

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

  • 4.1 之前的客户端使用短密码哈希进行身份验证

  • 4.1 或更高版本的客户端使用短密码哈希进行身份验证

  • 4.1 或更高版本的客户端使用长密码哈希进行身份验证

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

这些条件适用如下:

  • Password列必须足够宽以容纳长散列(41 字节)。如果该列尚未更新且仍具有 4.1 之前的 16 字节宽度,服务器会注意到长哈希值无法放入其中,并且在客户端使用 PASSWORD()函数或密码生成器执行密码更改操作时仅生成短哈希值陈述。如果您从 4.1 之前的 MySQL 版本升级到 4.1 或更高版本但尚未运行 mysql_upgrade程序来加宽 Password列,就会出现这种情况。

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

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

  • 一个旧的 pre-4.1 客户端连接到一个具有短密码哈希的帐户。

  • 客户端更改自己的密码。禁用后,这 old_passwords会导致帐户的密码哈希值很长。

  • 下次旧客户端尝试连接到该帐户时,它不能,因为该帐户的密码哈希很长,在身份验证期间需要 4.1 哈希方法。(一旦一个帐户在用户表中有一个长密码散列,只有 4.1 和更高版本的客户端可以对其进行身份验证,因为 4.1 之前的客户端不理解长散列。)

此场景说明,如果您必须支持较旧的 4.1 之前的客户端,则在未old_passwords设置为 1 的情况下运行 4.1 或更高版本的服务器会出现问题。通过使用 运行服务器 old_passwords=1,密码更改操作不会生成长密码哈希,因此不会导致老客户无法访问帐户。(这些客户不会无意中通过更改密码并以长密码散列结束而将自己锁在门外。)

的缺点old_passwords=1 是任何创建或更改的密码都使用短散列,即使对于 4.1 或更高版本的客户端也是如此。因此,您失去了长密码哈希提供的额外安全性。要创建具有长哈希的帐户(例如,供 4.1 客户端使用)或更改现有帐户以使用长密码哈希,管理员可以将会话值 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启用还是禁用状态下启动的。

场景一:用户表中的短 Password列:

  • 列中只能存储短散列 Password

  • 服务器在客户端身份验证期间仅使用短散列。

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

  • 的值old_passwords 无关紧要,因为对于短 Password列,服务器无论如何都只会生成短密码哈希。

当 4.1 之前的 MySQL 安装已升级到 4.1 或更高版本但 尚未运行 mysql_upgrademysql以升级数据库中的系统表时,会发生这种情况。(这不是推荐的配置,因为它不允许使用更安全的 4.1 密码散列。)

场景二:Password列;服务器开始于 old_passwords=1

  • 列中可以存储短散列或长散列 Password

  • 4.1 及更高版本的客户端可以对具有短哈希或长哈希的帐户进行身份验证。

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

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

在这种情况下,新创建的帐户具有短密码哈希值,因为old_passwords=1 可以防止生成长哈希值。此外,如果您在设置 old_passwords为 1 之前创建了一个具有长散列的帐户,则更改帐户的密码会 old_passwords=1导致为该帐户提供一个短密码,从而使其失去较长散列的安全优势。

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

在这种情况下,服务器有一个最新的 Password列,但运行时使用默认密码散列方法设置以生成 pre-4.1 散列值。这不是推荐的配置,但在 4.1 之前的客户端和密码升级到 4.1 或更高版本的过渡期间可能会有用。完成后,最好使用 old_passwords=0和 运行服务器secure_auth=1

场景三:Password列;服务器开始于 old_passwords=0

  • 列中可以存储短散列或长散列 Password

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

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

  • PASSWORD() 对于连接的客户端,涉及函数或密码生成语句 的密码哈希生成操作专门使用长哈希。更改帐户密码会导致该帐户的密码哈希值很长。

如前所述,这种情况下的一个危险是,4.1 之前的客户端可能无法访问具有短密码哈希的帐户。使用 PASSWORD()函数或密码生成语句更改此类帐户的密码会导致帐户获得长密码哈希。从那时起,没有 pre-4.1 客户端可以使用该帐户连接到服务器。客户端必须升级到 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().

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

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

  • 使用 运行服务器 old_passwords=0

  • 将具有短密码哈希的任何帐户的密码重置为使用长密码哈希。

  • 为了提高安全性,请使用 secure_auth=1.