哈希电子邮件地址和联系用户

信息安全 哈希 电子邮件
2021-08-22 14:54:56

我正在考虑一个登录网络服务,它将存储用户的用户名、密码和(可选的)电子邮件地址。

如果他们忘记了用户名或密码,他们可以将用户名和重置密码链接发送到他们的电子邮件地址(如果他们在注册时输入了一个)。

我不想在任何其他时间联系用户。

我想存储加盐和散列的电子邮件,因此(希望)不可能从数据库中检索任何电子邮件地址。

因此,如果用户忘记了他们的用户名,或者想要重置他们的密码,他们输入他们的电子邮件地址。如果它与数据库中的散列匹配(当加盐和散列时),则它用于向用户发送用户名和重置电子邮件链接。

显然,这里不可避免的风险是哈希冲突,这会导致某人通过电子邮件发送给另一个人的用户名和密码重置链接。

  • 是否可以通过使用公共 PGP 密钥将电子邮件地址(加盐和)加密为哈希形式,并且从不存储私有 PGP 以使该过程(希望)是不可逆的,从而避免这种情况?

  • 你能看到这种方法的任何弱点或问题吗?

  • 您认为这会比在数据库中存储纯电子邮件地址有重大改进吗?

4个回答

如果您使用加密散列函数,涉及非敌对用户和真实世界数据的散列冲突将非常罕见。例如,如果您使用 SHA-1 作为您的哈希函数,您需要有 1,200,000,000,000,000,000,000,000 个用户,然后才能看到其中两个具有相同电子邮件哈希的概率为 50%。可能有理由使用散列以外的方法来保护电子邮件地址,但冲突不是其中之一。

我建议不要散列电子邮件 - 或者至少无法检索它们。事实上,您可能需要知道用户的电子邮件:

  1. 您的网站已被黑客入侵,您必须告诉您的用户更改密码。
  2. 执法部门会发出搜查令,并要求您提供用户信息。

由于您似乎关心保持他们的电子邮件“安全”,我建议使用两个数据库:一个用于对电子邮件进行哈希处理的常规使用;和一个“安全”的,对存储敏感数据的访问权限非常有限。进程只能写入但不能检索数据的“保管箱”存储,或者类似于cryptodb的东西。

也就是说,我将留下其他评论来评估散列和冲突,即您的原始问题。

您绝对不应该以明文形式存储电子邮件。

我认为最好的解决方案是对电子邮件进行哈希处理,但对你的盐保密(例如加密它并将其放入凭证存储中)。如果数据库丢失,这将减少密码被发现的漏洞。

至于 PGP,在 PGP 中,每次加密都会生成一个新的对称密钥。如果您正在生成一个新的对称密钥,那么同一封电子邮件的两次加密将不会产生相同的密文,因此您无法匹配。您将不得不解密。如果每次加密都没有生成新的对称密钥,那么使用 PGP 与仅使用非对称密钥加密电子邮件相比有什么优势?您可以为每封电子邮件使用具有不同 IV 的单个对称密钥,并且 IV 可以存储在数据库中 - 对称密钥保持秘密。可以使用非对称密钥对该对称密钥进行加密,以便您可以轮换非对称密钥。这是与 PGP 相同的算法,但在您的场景中实现方式不同。

如果您保留加密的电子邮件,好处是如果您需要向客户发送电子邮件,您可以解密并使用它。例如,如果您的网站存在安全漏洞,您可以联系用户告知问题并要求他们更改密码。

缺点是您如何保证没有其他人(尤其是公司员工)会查看电子邮件地址,尤其是。如果有权解密的某人的帐户被黑客入侵,将如何保护电子邮件地址。

没那么快。事实上,风险与碰撞无关。它更多的是关于第二个原像

冲突是当有人可以找到哈希函数的两个不同输入时,它们会哈希到相同的值。攻击者可以控制两个输入。在您的情况下,攻击者将计算两个特制的电子邮件地址,然后注册这两个地址,并且稍后,将能够从一个帐户获取发送到另一个帐户的数据。它不会向攻击者购买任何东西:他已经拥有两个帐户。

第二个原像是当攻击者看到一个输入时,并面临寻找另一个哈希到相同值的挑战。这与碰撞攻击的设置不同:那时,攻击者只能控制一个输入,而不是两者。这使它变得更加困难。这映射到您的情况:攻击者想要注册一个与目标帐户的电子邮件地址哈希值相同的电子邮件地址,以便攻击者可以声称该目标帐户的遗忘,并将用户名和密码重置链接邮寄回他的地址。

如果你使用一个像样的散列函数,那么就不必担心冲突,因为它们是完全不可能的;而第二个原像甚至更不可行。对于具有n位输出的强哈希函数,发现碰撞的成本(结合运气和原始功率)为2 n/2,如果n ≥ 200左右,这在技术上已经不可行;对于第二个原像,成本上升到2 n,高出数十亿倍。有关碰撞可能性的更多信息,请参阅此答案。


正如您所说,您将希望您的哈希函数成为密码哈希函数(即类似 bcrypt,带有盐和多次迭代)以阻止完全不同类型的攻击,即攻击者窃取电子邮件哈希并破解它们能够向他们发送垃圾邮件。必须注意的是,一个给定的函数理论上可能被认为是“良好的密码散列函数”(即在存储密码散列时是安全的),但实际上并不能抵抗冲突甚至第二原像。密码散列的要求不包括安全散列的所有要求。

一个典型的例子是PBKDF2:就密码散列函数而言,它被认为是相当不错的。但是,它不耐冲突(这是因为它使用 HMAC,而 HMAC 使用密钥K,在 PBKDF2 中,它是密码;并且当K的长度超过底层哈希函数,然后K被替换为h(K);所以一个大的K产生与h(K)完全相同的输出幸运的是,你不介意碰撞你只需要抵抗第二个原像,PBKDF2就可以了。

这一点说明了在处理密码学时需要使用精确的术语。如果你不了解细节,那么这更能说明问题:密码学是微妙的


总结:使用 bcrypt 或 PBKDF2,你担心的风险是不存在的。在实践中不会发生;攻击者将无法强迫它。你不应该担心它,因为还有其他“风险”,它们的可能性要大几十亿倍,而且你不用担心(或者去买一把霰弹枪!)。

作为旁注,您需要在散列之前规范化电子邮件地址(例如强制小写),因为至少部分电子邮件地址不区分大小写,并且您不能期望用户总是对自己的电子邮件地址使用相同的大小写。


作为另一个方面说明,由于 bcrypt/PBKDF2 是昂贵的函数,您将只想对提交的电子邮件地址进行一次哈希处理——这意味着您必须知道要使用什么盐;如果您有 1000 个存储的电子邮件地址,您将无法承受将其哈希一千次的代价。因此,必须假设忘记密码的用户实际上记得他的用户名,以便您的服务器使用正确的盐计算正确的哈希值。这是我上面使用的假设。

或者,不要使用加盐散列,以便您可以对电子邮件地址进行一般散列,并将生成的散列值用作数据库中的索引。但是,这会削弱您的哈希对第二种攻击类型的抵抗力:当攻击者设法窃取您的数据库副本(SQL 注入、丢失备份磁带......)时,他会发现“反转”哈希更容易并恢复电子邮件地址。你必须选择你的毒药...

在那个 email-hash-as-index 的情况下,你再次需要担心冲突,因为发现冲突的攻击者将能够强制你的服务器尝试记录一个 email-hash-indexed 条目并找到一个现有的具有相同哈希的条目——取决于你如何实现它,这可能是也可能不是问题。事实上,“共享盐”模型意味着密码散列函数并不适合。我们回到了“加密设计”阶段,这需要更多的思考。如果你真的想走那条路,那么你可以期待困难。

(作为一个起点,我会设想一个自定义嵌套哈希为h(h(h(...h(email)...))),其中h = SHA-256,以及数千或数百万次迭代,但是在将其部署到生产环境之前需要更多的思考时间。)