跳转到主要内容
Chinese, Simplified

假设您想在客户机/服务器协议中实现一个密码身份验证方法。你会怎么做,可能会有什么问题?下面是PostgreSQL是如何做到这一点的。

密码

一开始,PostgreSQL在pg_hba.conf中只有现在称为“password”的方法。这是你能想到的最简单的事情:

  • 客户端对服务器说:“你好,我是Peter,我想连接。”
  • 服务器回答:“你的密码是什么?”
  • 客户端提示输入密码,或者从其他地方获取密码,然后回答:“它是‘123456’。”
  • 现在服务器寻找实际的密码。它存储在系统目录pg_authid列rolpassword中。服务器基本上执行strcmp(pg_authid.rolpassword, "123456"),如果它等于,它向客户端说“OK”,会话启动继续。

这种方法存在一些明显的问题:

  • 密码在网络上以明文形式传输。有一些外部方法可以解决这个问题,比如使用SSL或其他加密包装器。
  • 密码以明文形式存储在系统目录中,因此最终存储在磁盘上。这很糟糕,因为它允许数据库和系统管理员查看其他用户的密码。当然,人们不应该重复使用密码,但人们会这样做。它还可以让管理员通过使用其他用户的密码登录来绕过审计。一般来说,使用明文密码是不好的,因为它可能会被复制或被意外发现。最好不要那样做。
  • 更微妙的是,密码以明文形式存在于服务器进程的内存中。这有什么不好呢?同样,管理员也可以在那里访问它。另外,如果服务器核心转储或交换,明文密码可能会保存在磁盘上的某个地方。这和明文密码存储在磁盘上一样糟糕。

crypt

于是,他们又进行了一次尝试。pg_hba.conf中不再支持" crypt "方法:

  • 客户端再次开始,“你好,我是Peter,我想要连接。”
  • 配置为使用crypt方法的服务器回应道:“您的密码crypt()是什么,使用salt ' ab ' ?“每次连接尝试的盐是随机选择的。
  • 客户端得到用户的输入并计算 crypt("123456", "ab"),然后回答:“它是‘ab01FAX.bQRSU’。”
  • 服务器检查crypt(pg_authid.rolpassword, "ab")是否等于“ab01FAX.bQRSU”,如果是,回答“OK”。

crypt()是一种常用的、可随时使用的Unix函数,用于加密,因此它显然是在这里投入使用的候选函数。它修复了在电线上有明文密码的问题,但仍然有一些现有的和新的问题:

  • 密码仍然以明文形式存在于系统目录和存储中。
  • 原来crypt()使用的加密方法现在已经过时了。同样,salt长度(2字节)也过时了。
  • 因此,不同的类unix操作系统供应商扩展了他们的crypt()调用,以使用不同的加密算法,但这是以不兼容的方式实现的。只要crypt()仅用于为本地使用加密密码(就像最初的用途一样),这就没有问题,但当您希望通过不同系统之间的网络进行通信时,它就会中断。
  • crypt()在非unix系统上可能不可用。可以提供一个替代品,但如果这是必需的,那么它就会对使用操作系统中现成的工具的最初前提产生疑问。

md5

此时,PostgreSQL支持SSL,所以线上的明文问题不再那么重要了。真正让人失望的是系统目录中的明文密码。因此设计了一个新的系统,在pg_hba.conf中称为“md5”。它是这样工作的:

  • 客户:“你好,我是彼得,我要连接。”
  • 服务器:“你的md5密码是什么,用的是盐‘abcd’?”同样,每次连接尝试都会随机选择盐值。
  • 客户端获取用户输入并计算:md5(md5("123456" + "peter") + "abcd")。(我在这里使用+字符串连接。)这里,“123456”是用户输入的密码,“peter”是用户名,“abcd”是salt。然后客户机回答:“它是‘301eddd34d997f72bd43ba678e36a5ba’。
  • 服务器检查md5(pg_authid.rolpassword + "abcd")等于" 301eddd34d997f72bd43ba678e36a5ba ",如果是,则回答" OK "。

那么这有什么问题呢?

  • 现在读这篇文章,使用MD5显然是一个危险信号。散列方法过时了。
  • salt长度(4字节)也过时了。
  • 用户名用作存储的散列密码的盐。(这意味着两个恰好拥有相同密码的用户不会有相同的存储散列。)因此,重命名用户会使存储的散列密码失效,并需要分配一个新密码。这可能不常见,但当它发生时仍然令人恼火。
  • 如果某人碰巧从系统编目(或者备份转储)获得了存储的散列密码的副本,他们可以使用这些副本登录。您不需要实际的密码。例如,在上面的步骤3中,客户端可以在不知道“123456”的情况下发送md5(“我找到的哈希”+“abcd”)。(你不能用libpq的普通版本做到这一点,但是对于专门的攻击者来说,定制一个可以做到这一点的版本并不难。)

这里的教训是:不要设计自己的密码。

scram

因此,所有这些都必须重新考虑,目前的解决方案是在PostgreSQL 10中引入的,使用的是公共标准:SASL (RFC 4422)和SCRAM (RFC 5802和RFC 7677)。

SASL是一种协议框架,允许客户机和服务器协商一种身份验证机制。例如,这在电子邮件中广泛使用:SMTP或IMAP服务器可能会提供具有名称的身份验证机制,如PLAIN、LOGIN、CRAM-MD5或DIGEST-MD5,也可能是SCRAM,尽管这种情况似乎比较少见。PostgreSQL使用SASL的原因主要是因为SCRAM是在SASL上定义的,所以遵循它是有意义的。否则,SASL功能不会向用户公开。

SCRAM是一种认证机制。它实际上是一系列身份验证机制,具有不同的散列算法。当人们最初考虑在PostgreSQL中实现SCRAM时,以前对SCRAM的大多数使用都是使用SHA-1,但那时SHA-1已经过时了,在撰写本文时也已弃用,就像MD5一样。因此,PostgreSQL目前使用的算法是SHA-256,认证方法的全名是hyper -SHA-256

整个东西在线上看起来大致是这样的:

  • 客户:“你好,我是彼得,我要接电话。”
  • 服务器:“我们将进行SASL身份验证。选择其中一种方法:SCRAM-SHA-256”。(目前只有一种方法,除非提供了通道绑定,但为了简单起见,我在这篇博文中将忽略它。)
  • 客户:“我选择了SCRAM-SHA-256。这是第一个SASL数据:n,,n=peter,r=rOprNGfwEbeRWgbNEkqO”。该数据根据SCRAM规范进行组装,并包装在SASL协议消息中。这里,“n=”字段包含用户名,“r=”字段包含一个base64编码的随机字符串。最开始的东西与通道绑定有关,这里我们会忽略它。
  • 服务器:“这里是一些SASL数据:r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==, i=4096”。“r=”字段包含客户端随机数据和服务器附加的随机数据。此外,服务器发送一个salt (s=)和一个interation count (i=),它们是从所讨论用户的存储密码中获取的。
  • 客户端:“这是一些SASL数据:c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0, p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=”。“r=”字段与前面相同。客户端和服务器只是来回发送这个,以检查他们仍然在与相反的右侧对话。“p=”字段是客户端用户提供的密码,然后使用提供的盐和迭代计数以特定的方式对其进行散列。“c=”字段用于通道绑定。
  • 服务器现在会根据本地存储的数据来检查该数据。这里省略了其中的细节。如果满意,则返回:“这是最终的SASL数据:v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=”。这被称为验证器,它允许客户端检查服务器是否确实检查了密码,而不仅仅是示意每个人通过。
  • 客户机然后检查验证程序,如果验证程序满意,那么会话就可以继续进行。

这里发生了很多事。这解决了我们到目前为止所讨论的所有问题,以及一些我们还没有想到的问题:

  • 密码在网络上不是明文的。
  • 在系统目录或底层存储中,密码不是明文。
  • 该密码在服务器进程中不存在。事实上,明文密码永远不会离开客户端。客户端以某种方式对其进行哈希,服务器将其与已有的哈希信息进行比较,但服务器永远不会看到实际的密码。
  • 客户端发送的任何信息都不能被任何人用于登录,即使整个交换被捕获。这是因为客户机和服务器在每次连接尝试中都使用不同的随机数据。
  • 每个存储的密码都用不同的salt散列,因此实际上不存在存储的密码对不同用户来说是相同的风险。此外,salt是独立于用户名或用户的其他属性的。
  • 盐的长度可以很容易地改变。用户可以用不同的salt创建新密码。
  • 算法可以以一种系统的方式添加到这个设计中。请注意,这仍然需要软件更改,并不是完全没有痛苦的,但至少有一个确定的方法来做它。
  • 正如上面最后一点所提到的,客户机可以验证服务器是否确实检查了密码。在客户机/服务器身份验证中,我们通常主要考虑服务器试图阻止未授权的客户机连接。这里的想法是,服务器拥有未经授权的客户机可能想要获取的有价值的数据。但反过来也可能发生:客户端连接到一个假服务器,并向它发送服务器不应该获得的有价值的数据。这样的服务器只会很高兴地让恰好连接的任何客户机进入,而不需要实际检查密码。紧急行动可以防止这种情况发生。显然,SSL/TLS是检查网络中一个节点是否值得信任的更详细和完整的解决方案,而SCRAM并不意味着消除这种需要。

这就是PostgreSQL现在的情况。

ldap et al.

PostgreSQL中还有另外一组与密码相关的身份验证方法:

  • ldap
  • radius
  • pam
  • bsd

就客户端和协议而言,这些等同于明文认证方法“password”。唯一的区别是服务器不将密码与存储在pg_authid中的密码进行比较,而是与各自的外部服务进行比较。例如,LDAP身份验证是这样工作的:

  • 客户:“你好,我是彼得,我要接电话。”
  • 服务器:你的密码是多少?
  • 客户:“这是‘123456’。”
  • 现在服务器与LDAP服务器检查密码“123456”。这本身就可能涉及很多细节。如果检查成功,服务器说“OK”。

这样就避免了密码以明文形式存储在数据库中,但它仍然存在与此方法相关的所有其他问题。为PostgreSQL连接使用SSL,并配置LDAP服务器和连接安全地缓解了许多问题,但它不会像SCRAM那样防弹。(另一条建议是,如果目标是在组织范围内实现集中的密码存储,则考虑使用Kerberos而不是LDAP,但这完全是另一个主题了。)

结论

安全和密码学很难。有了SCRAM, PostgreSQL使用公认的公共标准,现在处于有利地位,可以在未来适应。

PostgreSQL wiki包含一个驱动程序列表,并显示哪些驱动程序支持SCRAM: https://wiki.postgresql.org/wiki/List_of_drivers.在PostgreSQL 10最初发布两年多之后,现在对它的支持似乎非常普遍。如果你还在使用md5哈希密码,现在绝对是时候升级了。

 

原文:https://www.2ndquadrant.com/en/blog/password-authentication-methods-in-postgresql/

本文:http://jiagoushi.pro/node/1399

讨论:请加入只是星球【首席架构师圈】或者小号【jiagoushi_pro】或者QQ群【11107777】

Tags
 
Article
知识星球
 
微信公众号
 
视频号