更新密码哈希而不强制现有用户使用新密码


32

您维护具有已建立用户群的现有应用程序。随着时间的流逝,目前的密码哈希技术已经过时,需要升级。此外,由于UX原因,您不希望强迫现有用户更新其密码。整个密码哈希更新需要在屏幕后面进行。

假设用户的“简单”数据库模型包含:

  1. ID
  2. 电子邮件
  3. 密码

如何解决这一要求?


我目前的想法是:

  • 在适当的类中创建一个新的哈希方法
  • 更新数据库中的用户表以容纳其他密码字段
  • 用户使用过时的密码哈希成功登录后,用更新的哈希填充第二个密码字段

这给我留下了一个问题,即我无法合理地区分拥有和未更新密码哈希的用户,因此将不得不同时检查两者。这似乎是可怕的缺陷。

此外,这基本上意味着,可以强制无限期地保留旧的哈希技术,直到每个用户都更新了密码。只有在那一刻,我才能开始删除旧的哈希检查并删除多余的数据库字段。

我主要是在这里寻找一些设计技巧,因为我当前的“解决方案”是肮脏,不完整的,不是什么,但是如果需要实际的代码来描述可能的解决方案,请随时使用任何语言。


4
为什么您无法区分已设置和未设置的辅助哈希?只需将数据库列设置为可空,然后检查是否为空。
Kilian Foth,

6
使用哈希类型作为新列,而不是新哈希的字段。更新哈希时,请更改类型字段。这样,当将来再次发生这种情况时,您已经可以处理,并且没有机会保留旧的(可能不太安全)哈希。
迈克尔·科恩

1
我认为您在正确的轨道上。从现在开始的6个月或一年后,您可以强制所有剩余的用户更改其密码。一年之后,无论如何,您都可以摆脱旧的领域。
GlenPeterson

3
为什么不仅仅哈希旧哈希?
任思远

因为您希望对密码进行散列(而不是旧的散列),以便对其进行散列(即,将其与用户提供的散列密码进行比较)会为您提供...密码-而不是另一个需要“去散”的值在旧方法下。可能但不干净。
Michael Durrant

Answers:


25

我建议添加一个新字段“ hash_method”,也许用1表示旧方法,用2表示新方法。

合理地说,如果您关心这种事情并且您的应用程序的寿命相对较长(显然已经存在),那么由于密码学和信息安全领域是一个不断发展的,不可预测的领域,这种情况很可能会再次发生。曾经有一次,如果根本不使用哈希,那么通过MD5进行简单运行是标准的!然后可能会认为他们应该使用SHA1,现在出现了加盐,全局盐+单个随机盐,SHA3,不同的加密就绪随机数生成方法...这不会只是“停止”,所以您可能会并以可扩展,可重复的方式解决此问题。

所以,现在假设您有类似的东西(为简单起见,我希望使用伪JavaScript):

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword());


if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword)
{
    // TODO: Learn what "hash" means
    return clearPassword + "H@$I-I";
}

现在意识到有一个更好的方法,您只需要进行少量重构即可:

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword(), user.getHashingMethod());

if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword, hashMethod)
{
    // Note: Hash doesn't mean what we thought it did. Oops...

    var hash;
    if (hashMethod == 1)
    {
        hash = clearPassword + "H@$I-I";
    }
    else if (hashMethod == 2)
    {
        // Totally gonna get it right this time.
        hash = SuperMethodTheNSASaidWasAwesome(clearPassword);
    }
    return hash;
}

在生成此答案时,没有秘密代理或程序员受到伤害。


+1,谢谢,这似乎是一个明智的抽象。虽然在实现此方法时最终可能会将hash和hashmethod字段移动到单独的表中。
威廉

1
这种技术的问题在于,对于不登录的帐户,您无法更新哈希。根据我的经验,大多数用户都不会登录多年(除非您删除不活动的用户)。您的抽象也不起作用,因为它没有考虑盐。标准抽象具有两个功能,一个用于验证,一个用于创建。
CodesInChaos

密码散列在过去15年中几乎没有发展。Bcrypt已于1999年发布,仍然属于推荐的哈希值。从那时起,仅发生了一项重大改进:由scrypt开创的顺序存储硬哈希。
CodesInChaos

这使得旧的哈希值容易受到攻击(例如,如果有人偷走数据库)。使用新的更安全的方法对每个旧哈希进行哈希处理可以在某种程度上缓解这种情况(您仍然需要哈希类型来区分newHash(oldHash, salt)newHash(password, salt)
dbkk 2013年

42

如果您足够在意尽快向所有用户推出新的哈希方案(例如,因为旧的哈希表确实不安全),则实际上有一种方法可以即时“迁移” 每个密码。

这个想法基本上是对哈希进行哈希处理。您无需等待用户p在下次登录时提供现有密码(),而是立即对旧算法()已产生H2现有哈希使用新的哈希算法(H1):

hash = H2(hash)  # where hash was previously equal to H1(p) 

完成转换后,您仍然可以完美地执行密码验证。只要用户尝试使用密码登录,您只需要计算H2(H1(p'))而不是以前的计算。H1(p')p'

从理论上讲,这种技术可以被应用到多个迁移(H3H4等)。实际上,出于性能和可读性的原因,您将希望摆脱旧的哈希函数。幸运的是,这很容易:在下一次成功登录后,只需计算用户密码的新哈希并用它替换现有的哈希哈希即可:

hash = H2(p)

您还需要另外一列来记住要存储的哈希:密码或旧哈希之一。在不幸的数据库泄漏事件中,此专栏不应使破解者的工作变得更容易。无论其价值如何,攻击者仍将不得不逆转安全H2算法,而不是旧算法H1


3
巨人+1:即使H1很糟糕,H2(H1(p))通常也像直接使用H2(p)一样安全,因为(1)H2负责盐和拉伸,以及(2)问题带有MD5之类的“断”散列不会影响密码哈希。相关:crypto.stackexchange.com/questions/2945/…–
orip

有趣的是,我认为对旧哈希进行哈希运算会产生某种安全性“问题”。似乎我弄错了。
威廉

4
如果H1 真的很糟糕,那将是不安全的。在这种情况下,可怕的是关于从输入中损失很多熵。就这一点而言,甚至MD4和MD5都几乎是完美的,因此,这种方法仅对于某些自制哈希或具有真正短输出(低于80位)的哈希是不安全的。
CodesInChaos

1
在这种情况下,可能是你唯一的选择就是承认你搞砸了,只是继续和重置所有用户的密码。此时,与迁移无关,而与损害控制有关。
Xion

8

您的解决方案(数据库中的其他列)完全可以接受。唯一的问题是您已经提到的问题:旧的哈希仍用于自更改以来未进行身份验证的用户。

为了避免这种情况,您可以等待最活跃的用户切换到新的哈希算法,然后:

  1. 删除未通过身份验证太长时间的用户的帐户。删除三年来从未访问过该网站的用户的帐户没有错。

  2. 向未通过身份验证的用户中的其余用户发送电子邮件,告知他们可能对新内容感兴趣。这将有助于进一步减少使用较旧哈希的帐户数量。

    请注意:如果您有禁止垃圾邮件的选项,则用户可以在您的网站上进行检查,请不要将电子邮件发送给检查过该网站的用户。

  3. 最后,在第2步之后几周,销毁仍在使用旧技术的用户的密码。当他们尝试进行身份验证时,他们会看到密码无效;如果他们仍然对您的服务感兴趣,可以重置它。


1

您可能永远不会有几个哈希类型,因此您可以在不扩展数据库的情况下解决此问题。

如果这些方法具有不同的哈希长度,并且每个方法的哈希长度是恒定的(例如,从md5转换为hmac-sha1),则可以从哈希长度中分辨出该方法。如果它们的长度相同,则可以先使用新方法计算哈希,如果第一次测试失败,则可以使用旧方法计算哈希。如果您有一个字段(未显示)告诉用户上次更新/创建的时间,则可以将该字段用作使用哪种方法的指示。


1

我做了这样的事情,并且有一种方法可以判断它是否是新的哈希,并使其更加安全。我想如果您花时间更新哈希值,则更安全对您来说是一件好事;-)

在您的数据库表中添加一个名为“ salt”的新列,然后将其用作每个用户随机生成的盐。(可以防止彩虹表攻击)

这样,如果我的密码是“ pass123”,随机盐是3333,则新密码将是“ pass1233333”的哈希。

如果用户有盐,您就会知道这是新的哈希值。如果用户的salt为null,则说明它是旧的哈希。


0

无论您的迁移策略有多好,如果数据库已经被盗(并且您不能否定性地证明这种情况从未发生过),那么使用不安全的哈希函数存储的密码可能已经遭到破坏。唯一的缓解方法是要求用户更改密码。

这不是(只能)通过编写代码解决的问题。解决方案是告知用户和客户所面临的风险。一旦您愿意开放它,就可以通过简单地重置每个用户的密码来进行迁移,因为这是最简单,最安全的解决方案。所有替代方案实际上都是关于隐藏您搞砸了的事实。


1
一个妥协,我在过去所做的那样,是保持旧密码一段时间后,再次提出他们作为人登录,但随后时间已经过去之后,你强迫任何人密码重置谁在没有登录那时。因此,您确实有一段时期仍会使用较不安全的密码,但这是受时间限制的,而且还可以最大程度地减少对定期登录用户的干扰。
肖恩·伯顿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.