你是谁?MySQL 和 MariaDB 认证协议从 1997 年到 2017 年的历史
MySQL 3.20 至 4.0
在过去美好的日子里,当 32MB 内存足以配得上 my-huge.cnf
这个名字时,当没人知道 Google、Facebook 甚至还没出现时,安全……怎么说呢……有点可爱。计算机病毒不会窃取数百万财产,也不会扰乱选举——它们会播放 Yankee Doodle 或告诉你不要玩电脑。人们使用 telnet 和 ftp,尽管一些有安全意识的管理员已经知道 ssh 了。
大约在这个时候,前后几年,MySQL 诞生了。它有了用户,这些用户的数据需要与他人隔离,但允许使用自己的数据。这就需要认证。
Michael Widenius(或 Monty)显然意识到有些用户对安全性偏执。即使在早期 MySQL 版本中也能找到证据
/* Paranoid settings. Define I_AM_PARANOID if you are paranoid */ #ifdef I_AM_PARANOID #define DONT_ALLOW_USER_CHANGE 1 #define DONT_USE_MYSQL_PWD 1 #endif
这摘自 MySQL–3.21 的 global.h
。毫不意外的是,MySQL 从未将密码从客户端明文发送到服务器。它发送的是从密码哈希生成的随机字节。从技术上讲,第一个 MySQL 认证协议(如 MySQL–3.20,1996 年)的工作方式如下
- 服务器在
mysql.user
表中存储了密码哈希。不过,哈希函数相当简单for (; *password ; password++) { tmp1 = *password; hash ^= (((hash & 63) + tmp2) * tmp1) + (hash << 8); tmp2 += tmp1; }
注意,哈希值只有 32 位!
- 在认证过程中,服务器以一个由8 个随机字母组成的字符串(称为混淆字符串)开始握手。
- 客户端计算了此混淆字符串的哈希(如上所述)以及密码的哈希。这两个数字的 XOR 运算产生了一个 32 位的种子,用于初始化一个伪随机数生成器。该生成器生成了“随机的”8 字节并发送到服务器。
- 服务器基本上重复了同样的操作——它知道混淆字符串,并且从
mysql.user
表中获取了密码的哈希。因此,它也初始化了一个随机数生成器,生成了 8 个字节,并将其与客户端发送的进行比较。
这并不是一个糟糕的协议。它有明显的优点,例如,密码从未明文发送。也从未明文存储。但是,说真的,32 位?即使在 1996 年也不够。这就是为什么下一个主要的 MySQL 版本——3.21——使用了 64 位哈希。除此之外,协议保持不变。并且它仍然存在于(尽管不是默认的)MySQL–5.6 和 MariaDB–10.2 中。幸运的是,它已从 MySQL–5.7 中移除。我真心希望现在没有人使用它了。
MySQL 4.1 至 5.7
这个协议的主要缺陷,正如我们在 2000 年代初突然意识到的那样,是它将密码存储为明文。不开玩笑。它确实存储了哈希,但客户端进行认证时只需要哈希,而不是密码。也就是说,如果有人能够从 mysql.user
表中读取密码哈希,只需要对客户端库稍加修改,就可以直接使用这些哈希进行登录,冒充任何人。
此外,这个哈希函数也相当可疑。不是说我们知道如何反转它,但我们仍然不完全信任它。事实证明,它后来确实被破解了,但那时我们已经有了替代方案。
对于新协议,我们(即我、Konstantin Osipov、Peter Zaitsev 和其他几个人)设定了以下目标
- 嗅探认证过程不应允许攻击者冒充任何人进行认证
- 窃取整个
mysql.user
表不应允许攻击者冒充任何人进行认证 - 额外目标:使用经过验证的知名加密哈希函数
这就是双 SHA1(或称为新协议或 mysql_native_password)协议的创建方式。它首次在 MySQL–4.1 中引入,至今仍是使用最广泛的 MySQL 认证协议。每个 MySQL 和 MariaDB 版本都支持它。其工作方式如下
- 服务器在
mysql.user
表中存储SHA1(SHA1(password))
。 - 对于认证,服务器发送一个随机的 20 字母混淆字符串。
- 客户端计算以下内容
SHA1( scramble || SHA1( SHA1( password ) ) ) ⊕ SHA1( password )
其中
⊕
是异或,||
是字符串连接。并将其发送到服务器。 - 服务器不知道
SHA1(password)
,但它知道混淆字符串和SHA1(SHA1(password))
,因此可以计算表达式的第一部分,并通过异或得到SHA1(password)
。 - 现在它只需要计算上一步得到的
SHA1(password)
的 SHA1 值,然后将其与存储在mysql.user
表中的值进行比较。任务完成。
该协议实现了所有目标——嗅探认证握手或窃取 mysql.user
表都无法帮助攻击者冒充用户。尽管如此,它并非完美无缺。服务器收到了 SHA1(password)
(因此它可能出现在核心转储中、从服务器内存中提取等),这足以冒充用户。此外,如果有人能够嗅探认证握手并且窃取 mysql.user
表,他将能够重复服务器所做的所有步骤,提取 SHA1(password)
并冒充合法用户。虽然这是一个缺陷,但并非主要问题——当一个人拥有密码哈希时,通常更容易直接暴力破解它们。而且我总觉得这个缺陷无论如何都无法修复,除非我们诉诸公钥加密技术。
MySQL 5.7 及更高版本
我们的用户依赖于双 SHA1 认证。生活继续。MySQL 被 Sun 收购,Sun 被 Oracle 收购。MariaDB 兴起并越来越受欢迎。而与这一切无关的是,SHA1 逐渐被认为越来越不安全。必须有所行动了。MySQL 是第一个提供替代方案的。实现新认证插件的任务被委托给了 Kristofer Pettersson。那时我不在 MySQL,所以我无从得知他与谁讨论以及他的目标是什么。但基本思路很清楚——摆脱 SHA1 并消除这最后一个缺陷。他正确地决定使用公钥加密技术。因此,新的 MySQL 5.7 认证协议(sha256_password 插件)使用了 SHA256(SHA2 的 256 位版本)和 RSA。它一起工作的方式如下
- 服务器存储
SHA256(password)
,太棒了! - 认证期间,它向客户端发送一个 20 字母的混淆字符串,就像以前一样。
- 客户端从提前分发到客户端的文件中读取服务器的 RSA 公钥。
- 客户端计算密码与混淆字符串的异或(根据需要重复混淆字符串以覆盖整个密码),使用服务器的公钥对其进行加密,然后发送到服务器。
- 服务器使用其秘密 RSA 密钥解密数据,将结果与混淆字符串进行异或以提取客户端的明文密码,计算其 SHA2 值,并将其与
mysql.user
表中存储的值进行比较。任务完成。
相当直接。只有一个麻烦之处。必须将服务器的公钥分发给所有客户端。而且每个客户端都需要拥有它希望连接的所有服务器的公钥,并根据需要进行管理。我能预见到这可能会变得相当烦人,这大概就是为什么如果客户端没有服务器的公钥,服务器会在认证期间“乐于”提供它,代价是一次往返。当然,任何有安全意识的人都不应该依赖这种方式,客户端如何知道公钥是真实的?中间人攻击者可以用他自己的公钥替换它,然后就能获取客户端的明文密码!再说一次,这倒也不是特别糟糕,但服务器仍然会获取明文密码。就像它在所有先前的认证协议中所做的那样。
MariaDB 10.1 及更高版本
但 MariaDB 甚至没有那个。我们仍然依赖于久经考验的双 SHA1 协议。银行虽然抱怨,但它仍然足够安全。尽管如此,鉴于最近的新闻,我们也开始寻找替代方案。请注意,即使考虑到最近的研究,双 SHA1 仍然是安全的。研究表明,现在可以生成 SHA1 碰撞,这意味着可以生成两个具有相同 SHA1 哈希的密码。这对 SHA1 本身来说是坏消息,但并不会真正削弱我们的认证。尽管如此……这表明 SHA1 的寿命不多了,最终可能会有人找到方法来破解它。
我也构建了使用公钥加密的新认证协议。目标是即使是流量嗅探、mysql.user
表,或者两者兼有,甚至是一个完全被攻陷的服务器,也无法恢复密码。而且其行为应该保持不变——无需分发文件,协议中也没有新增的往返。结果是使用了 ed25519 的协议——这是一种椭圆曲线数字签名算法,在有限域 order 2255–19 上使用 Edwards 曲线(这就是该算法被称为 Ed-255-19 的原因)。曲线和算法本身都由著名的 Daniel J. Bernstein(或简称 djb)发明。他和他的团队还创建了一些 ed25519 的公共领域参考实现,其中一个在 OpenSSH 中使用。新的 MariaDB 认证协议也使用了这些参考 ed25519 实现之一,其工作方式如下
- 用户的密码是秘密密钥。我们计算
SHA512(password)
并应用一些数学“魔法”将其转换为公钥。这个公钥由服务器存储在mysql.user
表中。 - 对于认证,服务器向客户端发送一个随机的 32 字节混淆字符串。
- 客户端使用其秘密密钥对其进行签名。
- 服务器使用用户的公钥验证签名。
就这些!比双 SHA1 简单得多。而且它尽可能地安全,密码不存储在服务器上,不发送到任何地方,服务器甚至在任何时候都看不到它,也无法从其掌握的信息中恢复它。这里也没有中间人攻击的机会。无需文件。相同的往返次数。拥有 mysql.user
表的情况下仍然可以暴力破解密码,但对此我们无能为力。
这个新协议首先作为插件在 MariaDB–10.1.22 中引入,并将会在未来版本中更方便地集成到服务器中。
“它有了用户,这些用户的数据需要与他人隔离,但允许使用自己的数据。这就需要认证。” 从技术上讲,这是授权。
既是又不是。这是授权,你说得对。
但博客里说的是它“需要”认证,而不是说它“就是”认证。
MariaDB 的客户端库会兼容 MySQL 基于 sha256 的认证吗?
到目前为止,MariaDB 的客户端库登录 MySQL 都没有问题。但随着 SHA1 即将淘汰,我的团队担心我们是否会遇到兼容性问题。
是的。 https://jira.mariadb.org/browse/CONC-229 是关于向 Connector/C 添加 SHA256 支持的——它已经关闭了,所以该功能应该已经实现。https://jira.mariadb.org/browse/CONJ-327 是关于向 Connector/J 添加 SHA256 支持的——它还没有关闭,所以这是计划中的,但尚未实现。
‘public key(sha512(guess))’ 的计算成本仍然非常低廉,这使得暴力破解人类生成的密码变得容易。为什么不使用一些参数化的哈希函数,并将这些参数与挑战一起传递(假设客户端能够防御使用简单参数的情况)?例如 PBKDF2 或 Argon2?
没错。我在博客中列出了新认证的目标,其中并没有包含强大的防暴力破解能力。
原因有两个,一个是可以轻松生成一个复杂的密码(因为它存储在客户端,不是手动输入的,所以记不住也无关紧要)。
另一个原因是我们在 MySQL 4.0 中尝试使用加盐密码,结果非常痛苦,不得不在下一个小版本中撤销了——用户无法接受 PASSWORD(“foo”) 变成了非确定性的,多次调用会返回不同的哈希值。尽管我希望时代已经改变,现在我们可以再尝试一下加盐密码。
谢谢!
是否有任何形式的降级保护?我看到 --secure-auth 选项,但这似乎只针对非常旧的版本。默认客户端是否支持将允许的认证方式限制为 ed25519?(也许可以通过一个只填充了所需插件的自定义插件目录来实现)
没有。而且 mysql_native_password 是内置的,你无法真正删除“mysql_native_password.so”来防止降级。
这是一个好问题,对此我们目前还没有好的答案…
请随时在以下地址建议新功能:https://jira.mariadb.org
已完成:https://jira.mariadb.org/browse/MDEV-16256