用于用户身份验证的 PGP 是个好主意吗?

信息安全 验证 密码 Web应用程序 pgp
2021-09-07 13:35:37

我正在构建一个提供用户登录的网站。为此,我目前正在研究处理身份验证的好策略。

我现在是怎么做的

我目前的概念是以目前似乎已达成的共识为蓝本的。密码用 64 字节/dev/urandom的 SHA-512加盐,然后用 100 轮 SHA-512 散列。每轮结束后,原始密码与结果连接,然后输入下一轮。当用户想要登录时,他们将他们的凭据发送到服务器,然后重复描述的过程(显然使用相同的盐)并与数据库中的哈希值进行比较。

这个策略对我来说似乎足够安全(如果我错了,请纠正我,它基本上只是阅读大量在线指南和观看 YouTube 视频的结果)。但是,我认为它有一个重大缺陷,即客户端必须以纯文本形式将密码发送到服务器。是的,我很自然地使用 HTTPS,但是,如果连接因某种原因被破坏,密码也是如此。

替代概念

所以我想到了一个完全不同的方法:使用 PGP 密钥。当用户注册时,他们会生成一个 PGP 密钥对,使用他们选择的密码加密私钥并将其发送到服务器。当他们想再次登录时,服务器会生成一串随机字符并使用公钥对其进行加密。然后将加密的随机字符串和密钥对发送给客户端,客户端需要再次解密以证明他们拥有私钥的密码。

这种方法可以防止密码通过网络传输,甚至允许用户之间进行端到端加密聊天等很酷的东西。我能找到的唯一缺点是服务器必须向基本上任何请求它们的人提供加密的私钥,这使得暴力攻击更容易。我可以通过在客户端运行计算成本高昂的密钥扩展算法并使用其结果来加密私钥来缓解这种情况。

但我仍然不完全相信整个事情,所以我很想听听你的反馈,看看这是否是一个好主意,或者我是否应该坚持我现在的做法。

编辑:

根据一些答案,我认为我的问题有点误导。我的要求是,无论用户使用什么设备或之前是否登录过该设备,用户都需要为成功的身份验证提供唯一的东西是他们的用户名/密码,而不是其他任何东西。

4个回答

您永远不应该尝试使用您自己发明的方案来保护“真实”的 Web 应用程序。因此,我们不应该讨论如何实际实施或使用这种方法的实用性。

它会起作用吗?

您的方案不会通过网络发送密码。立即跳出来的是,您将私钥发送到服务器,服务器永远不会对它做任何事情,无缘无故。是的,据说它仍然受密码保护,但为什么呢?

正如其他人指出的那样,私钥应该由所有者保留。

即使考虑私钥的密码加密,也存在以下问题(至少):

  • 在你的方案中分发私钥本质上与公开密码哈希相同:现在任何人都可以对密钥的密码进行离线字典攻击;没有人能阻止它。
  • 更糟糕的是,他们可以获得给定登录的密钥,这意味着他们可以检查用户是否存在,然后攻击用户名的密码。

稍作修改,您的方案将是一种非常简单的公钥身份验证形式:

  • 客户端创建一个密钥对,并将公钥发送给服务器
  • 要登录,服务器会发送一个随机字符串。
  • 客户端对字符串进行签名并将其发送回服务器。
  • 服务器验证签名以确保客户端拥有私钥

在 https 中使用客户端证书实际上已经可以实现这样的事情同样的东西也用于 ssh。

ThoriumBR 提到的挑战-响应机制是另一种身份验证方式,尽管其中描述的实现假设服务器以明文形式存储密码——这比通过加密连接发送密码危险得多。

会“更好”吗?

。您要防御的情况应该是有人进入加密的 https 连接。如果您认为是这种情况,他们不仅可以读取密码,还可以窃取您的会话 cookie 以及会话中显示的所有数据。在这种情况下,攻击者已经获胜,无论他们是否读取密码。

您当然可以推出自己的加密和身份验证,但问题是为什么您认为这会比 https 连接更好?

现实情况不是有人破坏了 https 连接,而是有人闯入服务器的数据库并试图在离线攻击中“恢复”密码。这是大多数身份验证方案试图防御的。

密码的危险不在于它们可以被拦截,而在于它们可以被猜到。

现实生活中应该怎么做?

对于“真正的”Web 应用程序,请使用已建立的框架和知名的身份验证库。不要自己写,因为很容易出错,很难做对。

不要使用 SHA-anything 对密码进行散列,使用专用的密码散列函数(例如bcryptPBKDF2)使攻击更加困难(另请参阅此答案)。您还可以为您的应用程序使用或添加一些TOTP机制,这比密码更好。

附录

在我写完答案后,您添加了以下内容:

我的要求是,无论用户使用什么设备或之前是否登录过该设备,用户都需要为成功的身份验证提供唯一的东西是他们的用户名/密码,而不是其他任何东西。

这解释了为什么要将私钥发送回服务器,尽管它不会改变它是一个坏主意的原因。

你仍然可以使用挑战-响应机制,就像 ThoriumBR 建议的那样。甚至还有高级挑战-响应机制,例如SRP,它使用巧妙的数学运算来避免将密码存储在服务器上。

但是,所有这些机制以及客户端哈希(请参阅此答案)都有一个共同点:

他们需要在客户端执行代码。

在 Web 应用程序中,这只能是服务器发送的 Javascript。实现客户端不是问题(甚至还有一个OpenPGP 库),但是由于您担心 https 连接的安全性,您还有另一个问题:

因此,中间的任何人都可以简单地注入恶意客户端代码来窃取密码,从而使您的整个聪明机制过时。

正如其他人所说,私钥不共享。句号。否则,它们被称为公钥。

但是:加密签名是相当标准的。HTTPS 支持:

使用 HTTPS 客户端证书进行身份验证并且所有现代浏览器都具有在被要求时生成必要密钥的内置方法。

你的问题已经解决了;用户生成一个密钥对,给你它的公钥,保留私钥,并且在连接时,服务器要求客户端证明他们拥有私钥(本质上,通过让他们签署一些东西)。

完毕!

其他加密建立的方法包括至少一种我知道的方法:

通过 Kerberos 票证单点登录——这在使用 Active Directory 的 Intranet 中非常普遍。同样,现代浏览器(至少 Edge、Internet Explorer 和 Firefox)开箱即用地支持这一点——在 Windows 和 Linux 上(尚未在 OS X 或 FreeBSD 上尝试过)

虽然我喜欢 PGP 登录的想法,但这不是它应该工作的方式。私钥应始终保留在客户端,并且永远不要与任何人共享。此设置鼓励不良做法并提供有关 PGP 的错误教育。

您可以做的是,让用户将他们的公钥上传服务器,然后在登录时向他们展示一个挑战(基本上是一个加密字符串),这需要私钥,当然还有相关的密码。

问题是很少有人知道 PGP 或有认真使用它的倾向。如果你能做到,那就去做吧。

正如匿名者所说,私钥必须始终是私有的。将私钥发送给任何实体都会否定您的登录提供的任何安全性。

基本上,您正在设计的内容称为Challenge Response Authentication,即使没有密钥对也可以完成:

  • 一方(服务器)生成一个随机令牌并将其发送给另一方

  • 另一个(客户端)将他自己的密码与令牌连接起来,对其进行哈希处理,然后发回

  • 服务器知道令牌和密码,连接这些值,散列,并与客户端发送的内容进行比较

如果要使用密钥对,可以使用密钥对进行签名:

  1. 生成随机值R1并发送给用户

  2. 用户收到 R1,并使用其私钥对其进行签名

  3. 服务器根据用户的公钥检查签名

这在技术上很容易做到,但我怀疑大多数用户是否具备必要的知识来生成密钥对,并使用该密钥对进行签名。