是否可以在 BCrypt 或 PBKDF2 已经计算且没有原始密码的情况下增加其成本?

信息安全 密码 哈希 密码管理 bcrypt pbkdf2
2021-08-09 00:41:16

我只是想知道你是否可以离线增加这两种算法的成本(迭代)。我想每年增加用户密码的成本。

一种解决方案是在用户登录时重新计算它们,但用户可能在那段时间没有登录,我不想等到他登录。

这可以通过密码拉伸(例如遍历 SHA-256 哈希)来完成,但我不知道这是否可以使用 BCrypt 和/或 PBKDF2。

4个回答

PBKDF2 和 Bcrypt 不支持在不知道密码的情况下从给定迭代次数的输出开始增加成本。没有内在的原因;密码散列过程可以允许这样的离线拉伸,同时仍然是“好的”。但这些算法碰巧不允许这样做。

可以做以下事情:正常的 bcrypt 或 PBKDF2 输出包括盐s、迭代计数i和散列输出v在 bcrypt 实现中,这三个值通常被编码为可打印的字符(使用类似 Base64 的编码);例如看这个答案假设您有sv,您可以存储以下内容:

  • s ;
  • 迭代次数i
  • 额外的迭代次数j
  • h(h(h(...h(h(v))...)))是使用安全哈希函数h重复哈希v的结果,完成j次。

对于密码验证,您必须从给定的密码(使用si)重新计算 bcrypt/PBKDF2,然后将结果值散列j次,以查看它是否与存储的值匹配。

如果您对h使用强大的哈希函数(例如 SHA-256),这主要是安全的。可以看出,重复哈希减少了可能值的空间,但如果哈希输出值在大小为 N 的空间中,则应该达到大小约为sqrt(N)的内部“循环” ;此外,如果该循环足够小,可以彻底探索,则可以计算哈希函数h上的冲突(请参阅此页面以获取漂亮的图表)。因此,如果h是抗碰撞的(如 SHA-256),那么上述方案是安全的。

重要的一点是,您可以在之后以离线方式增加j 。但是,请注意,这种拉伸依赖于进行许多额外哈希函数计算所涉及的计算成本。不幸的是,像 SHA-256 这样的常用散列函数非常适合 GPU 的功能。因此,以这种方式获得的对攻击者的优势可能没有最初希望的那么大。换句话说,使用上述拉伸会使 bcrypt 优于 PBKDF2 的任何优势(有关此主题的讨论,请参见此答案)。您可以将其作为临时措施应用,直到所述用户回来,以便可以使用更高的迭代次数(i)再次完成 bcrypt 步骤。

而且,我上面描述的方案是自制加密,这很糟糕。我可以侥幸逃脱,因为我太棒了,但这不能普遍推广。

bcrypt 依赖于 Eksblowfish 算法,其定义为:

Eksblowfish(cost, salt, key)
  state = InitState()
  state = ExpandKey(state, salt, key)
  repeat (2^cost)
    state = ExpandKey(state, 0, key)
    state = ExpandKey(state, 0, salt)
  return state

此代码显示迭代次数。由于 bcrypt 的用法是:bcrypt(cost, salt, key)cost变量是迭代的 log2。所以加一cost加倍时间。

每一轮都使用初始密码(密钥),没有它你不能添加额外的轮次。所以,对于 Bcrypt,答案是否定的。

正如 p____h 所提到的,Eksblowfish 算法在每一轮中都使用密钥(和盐),只更新并最终返回最终状态。* 因此,如果没有原始密钥,您将无法增加轮数。

PBKDF2 框架类似地定义了它的内部循环:

F(P,S,c,i) = U1 ^ U2 ^ ... ^ Uc
U1 = PRF(P,S || INT_msb(i))
U2 = PRF(P,U1)
...
Uc = PRF(P,Uc-1)

所以面临同样的问题。

一种可能的替代方法是,如果您的数据库中有旧哈希,则将其用作新的更强哈希的密钥,然后将其丢弃。您必须记录旧的盐和工作因子值(这些通常记录在最终输出中)。当用户登录时,您将获取旧盐和旧工作值,计算中间散列并将其用作辅助散列的键(它将存储辅助盐和工作因子)。如果密码已检出,您可以直接从密码短语中计算出一个新的更强的哈希值,并将用户标记为已更新。

NB 密码学有时可能是违反直觉的。我没有,也没有资格分析这种链接导致的难度增加——如果有的话。


在调用 InitState 以使用 的数字填充新的密钥计划后,EksBlowfishSetup 使用盐和密钥调用 ExpandKey。这确保了所有后续状态都依赖于两者,并且算法的任何部分都不能在没有盐和密钥的情况下被预先计算。此后,ExpandKey 与 salt 和 key 交替调用以进行迭代。除了第一次调用 ExpandKey 之外,第二个参数是一个 128 个 0 位的块。这更接近于原始的河豚密钥调度,并且还允许 EksBlowfishSetup 在寄存器很少的 CPU 架构上更有效地实现。

我有同样的要求,并以这种方式解决它:

  • 使用 PBKDF2 和当前为 16 的工作因子(即 2^16 次迭代)以通常的方式散列盐和密码。将盐、哈希、工作因子等作为一行存储在数据库表中。
  • 异步(即不影响最终用户)再执行 3 次,每次将工作因子增加 1。所以现在我有该用户的 4 个数据库行,工作因子分别为 16、17、18 和 19。
  • 我总是使用工作系数最低的行来验证密码,但每 2 年我会删除工作系数最低的行。