对 Dave 公平地说,就自制密码安全而言,这是更好的情况之一,因为它只是有点混淆(实际上并不多)掩盖了 MD5 哈希字节顺序的可逆交换在hash = SHA1(salt + MD5'(Password))
哪里。MD5'
现在用户名/时间/随机/加密部分只是用来生成盐,我们对盐的唯一要求是它们只需要具有很好的唯一性;所以虽然它过于复杂,但谈论它真的没有用。
再次hash = sha1($salt+md5'($password))
。重新排列 MD5 不会增加安全性(swap(00112233445566778899aabbccddeeff)
变成ccddeeff8899aabb0011223344556677
),交换不会阻止您在之后使用 md5 彩虹表(例如,查看代码并反转交换)。然而,独特盐的存在使得彩虹表不可行。
现在至关重要的是,这归结为应用了两次的简单加密哈希函数;这比存储明文密码(请参阅明文违规者)更好,并且比存储未加盐的哈希(请参阅linkedin泄漏)更好。然而,在廉价的大规模并行 GPU 时代,这对于现代使用来说太弱了。任何有一点通用 GPU 编程经验并以某种方式进入实时服务器(用他们的 salt 获取哈希)的人都可能会看到他的源代码,尝试他们自己的密码作为测试用例,然后可以暴力破解任何每个 GPU 每秒尝试数十亿次的特定密码。
因此,如果任何用户使用的密码列表包含大约一百万个以前泄露的密码(例如来自linkedin),攻击者几乎可以立即破解它。如果某个用户的密码是字符集 A-Za-z0-9 中的随机 8 个字母;每个 GPU 平均需要大约 60 小时才能中断(因此,如果您有 60 个 GPU;将需要 1 小时)。使用利用常见密码形式的常见破解技术可以显着加快速度。还值得注意的是,由于$password
通过 128 位 MD5 散列函数,在密码中使用超过 128 位的熵绝对没有任何好处(但公平地说,这是一个非常安全的密码;就像一个 10 字的 diceware 密码或随机 22 个字符的字母数字密码)。
确实,您应该使用迭代的加密哈希函数;这有点像 bcrypt 或 PBKDF,它可以通过一个很大的常数因子(比如 10 5 )来降低攻击者的暴力破解速度(因此从 62 字符集(A- Za-z0-9);使用单个 GPU 需要 600 万小时(大约 700 年可能会被破解),并且使用更强的密码会变得更好(例如,10 个字符的密码需要大约 300 万年;所以即使使用100 万个 GPU 需要 3 年)。因此,一点点密钥强化会将相对较弱的密码(62 个字符集中的 8 个随机字符)移出被攻击者破解的可行性范围。有关使用简单密码上限的更多信息keystrengthened password 看到这个答案。
碰撞攻击与图像前攻击(或为什么对哈希函数的碰撞攻击与密码哈希无关)
KeithS 的回答虽然给出了很好的建议(使用 bcrypt 而不是简单的加密散列函数来散列您的密码),但最初批评 MD5 和 SHA1 的理由是错误的(不要使用 MD5,因为它容易受到碰撞攻击)。原像攻击和碰撞攻击之间存在细微差别,并且评论中的争论过于浓缩,所以我在这里详细说明。我强烈建议阅读关于原像攻击的维基百科文章。
前映像攻击说给定一个用十六进制写的特定 128 位哈希: h=ad2baf26a87795b3c8a8366a08b44112
,一个特定的哈希函数H
,请找到任何消息m
,例如h=H(m)
; 请注意,有 N=2 128个不同的不同哈希值。现在,如果您的加密散列函数没有损坏,那么对于随机消息 m,散列中的每个位都有 50% 的机会为 0 或 1。然后,我平均需要为大约 N=2 128个散列生成散列,然后我才幸运地找到任何m
这样的h=H(m)
消息(此消息可能与最初用于生成散列的输入不同——但这仍然下降在“原像”攻击的类别下)。
碰撞攻击说找到我任意两条消息m1
等等m2
。H(m1) = H(m2)
请注意m1
, m2
, (和H(m1)
) 都可以自由更改。这种情况是一个更容易的问题,因为如果我为 M 个不同的消息生成 M 个散列,我不仅仅是将 M 个消息与一个特定的散列进行比较(因此有 M 个发现冲突的机会),现在我有 M*(M- 1)/2 对散列,因此大约有 M^2 发生冲突的机会。所以在这种情况下,我需要粗略地生成大约 sqrt(N)~2 64 个散列,然后它们中的一个可能会在理想的 128 位散列上与另一个发生冲突。
让我们看看这两种生日问题。碰撞问题转化为常见的生日“悖论”;在一个房间里需要多少人才能让两个人在一年中 N=365 天共享一个生日。答案是一个悖论,因为您只需要大约 sqrt(N) ~ 23 个人,就可能有两个人共享一个生日(就像一个房间里有 23 个人,您有 253 对不同的人可以匹配)。(我确实知道 sqrt(365) != 23:我使用的是近似数学,而不是关注无关紧要的常数因素。然后用 sqrt(365) 重新计算~房间里有 19 人P(two share birthday) = 19! * comb(365,19)/365**n = 37.9%
,虽然不是严格意义上的 50%,但仍然意味着它很有可能发生)。注意碰撞生日问题,一个房间里不能有 N+1=366 人,并且有可能没有碰撞(忽略闰日);充其量是前 365 个人的生日不同,最后一个人产生了碰撞。
原像问题是一个非常不同的问题,在一个人很可能有一个特定的生日(比如 B = 12 月 18 日)之前,我需要在一个房间里有多少人。在这种情况下,我需要大约 N ~ 365 人才能发生。例如,房间里有 365 个人,P(somebody has birthday B) = 1 - (1 - 1/365)^(# people)
所以# people = 365
你有 63% 的机会某人的生日是某个固定日期 B。在这种情况下,你可以很容易地想象房间里有任意数量的人,但没有在某个特定日期过生日的人。(假设您仅在生日不是给定日期的情况下才邀请人们进入房间;您可以邀请的人数没有限制)。
当像 MD5/SHA1 这样的哈希函数因碰撞攻击而被破坏时,这意味着您可以在比 sqrt(N) ~ 2 numbits/2的蛮力时间更少的工作中产生碰撞。对于 MD5,它只需要大约 2^24 时间来产生碰撞;对于 SHA1,它需要 ~ 2^61 时间。这意味着对 MD5 的碰撞攻击非常容易进行;但是对 SHA1 的实际攻击仍然很困难。但是碰撞攻击只有在你不关心你试图匹配什么哈希时才有意义。这些冲突攻击与某些应用程序非常相关,例如对消息进行加密签名以确保消息完整性,因此在这些情况下要小心使用 MD5/SHA1。然而,当你有一个唯一的盐,并且你试图匹配一个特定的哈希来进行身份验证时,碰撞攻击就无关紧要了。