PBKDF2和哈希比较

4

我使用mitsuhiko的pbkdf2实现进行密码哈希:

def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
    """Returns a binary digest for the PBKDF2 hash algorithm of `data`
    with the given `salt`.  It iterates `iterations` time and produces a
    key of `keylen` bytes.  By default SHA-1 is used as hash function,
    a different hashlib `hashfunc` can be provided.
    """
    hashfunc = hashfunc or hashlib.sha1
    mac = hmac.new(data, None, hashfunc)
    def _pseudorandom(x, mac=mac):
        h = mac.copy()
        h.update(x)
        return map(ord, h.digest())
    buf = []
    for block in xrange(1, -(-keylen // mac.digest_size) + 1):
        rv = u = _pseudorandom(salt + _pack_int(block))
        for i in xrange(iterations - 1):
            u = _pseudorandom(''.join(map(chr, u)))
            rv = starmap(xor, izip(rv, u))
        buf.extend(rv)
    return ''.join(map(chr, buf))[:keylen]

这个函数返回二进制摘要,然后将其编码为Base64并保存到数据库中。当用户登录时,此Base64字符串也会被设置为cookie。

此函数用于比较密码哈希值:

def comparePasswords(password1, password2):
    if len(password1) != len(password2):
        return False
    diff = 0
    for x, y in izip(password1, password2):
        diff |= ord(x) ^ ord(y)
    return diff == 0

我想知道在安全方面,比较二进制哈希和base64字符串是否有差别?例如,当用户登录时,我计算所提交的密码的二进制摘要,从数据库中解码base64字符串,然后将两个二进制哈希进行比较。但如果用户具有包含base64字符串的cookie,则直接将其与数据库中的字符串进行比较。
第二个问题是关于盐:
os.urandom返回二进制字符串,但在用于哈希生成之前,我还将其编码为base64。我不应该使用二进制形式的盐吗?

对于任何阅读此内容的人:这是一个旧问题,PBKDF2不再是推荐的密码哈希算法(尽管它比许多实际使用的破损方案要好得多)。现在,Argon2i成为了密码哈希竞赛的获胜者。 - Erwan Legrand
1个回答

10
回答问题1:比较字节和比较base64编码的字符串在安全方面没有主要区别...你只是在比较nn*4/3个元素。使用base64的运行时间会增加4/3,但时间量仍然微不足道 :)
那么,有一个关于类似“恒定时间”比较函数的discussion中,一些Python开发人员遇到了一些VM级别的陷阱——如果您的输入是unicode字符串而不是bytes,特别是如果unicode包含非ASCII字符,则可能仍然存在一些微妙的计时攻击(比短路相等攻击低几个数量级)。因此,如果可能的话,我会坚持使用字节(无论是二进制数据还是ASCII编码的base64数据)。
但是,在PBKDF2的情况下,我不会过于担心,因为timing attack该比较函数旨在打败的适用于HMAC签名,而不是密码哈希验证...但最好保险起见。

回答问题2:对于不安全的构造,例如md5(salt+password),先编码盐值会使攻击者能够使用现有的ASCII md5表来攻击整个摘要,而原始二进制盐则会使这些表无用。但是,PBKDF2-HMAC进行了足够的混淆,唯一重要的事情是盐包含n位熵 - 无论它是以n/8原始字节的形式还是n/6 base64字符的形式,都不影响安全性。

其他注意事项:我只想补充一下与您发布内容相关的几点...

  1. 出于安全考虑,我强烈建议使用SHA256/512代替SHA1作为PBKDF2-HMAC哈希函数,并且在安全性方面应该使用>=10,000轮(截至2012年)。

  2. 将摘要以cookie的形式发送(即使没有盐),我认为可能存在潜在的不安全因素……如果有人窃取了那个cookie(例如跨站脚本攻击),他们可能会冒充用户登录(尽管我不知道您的应用程序的其他安全设置)。使用具有随机生成的会话ID(例如Beaker)的会话层可能是一个很好的替代方案。

  3. 我建议使用Passlib PBKDF2consteq实现,它的PBKDF2例程大约比mitsuhiko的快5倍,并且可以利用M2Crypto(如果存在)。 (声明:我是Passlib的作者)。它还拥有一个现成的pbkdf2-sha256密码哈希函数,但如果您将摘要发送到cookie中,则不会太有用。


非常感谢Eli抽出时间分享您的知识!我对stackoverflow上的人们的反应速度印象深刻。我已经使用SHA256并将迭代次数增加到10000轮。此外,我曾考虑过使用会话,但最终决定保持简单并使用cookie,因为我的应用程序是一个简单的单用户博客,具有匿名读者和1个必须进行身份验证的管理员。 - wombatonfire
Unicode字符串存在一个有趣的问题。从您提到的讨论中,我可以理解到,如果我们在比较两个Unicode字符串时需要对它们进行编码,那么问题就会出现。我想知道这会对我产生什么影响?我的输入是来自POST的密码。它是一个Unicode字符串,我对其进行编码以使HMAC正常工作,然后将其保存为哈希值: formData = web.input() password = pbkdf2_bin(formData.password.encode('utf-8'), user.salt, hashfunc=hashlib.sha256) 我不直接比较Unicode字符串,而是比较二进制或base64形式的哈希值。 - wombatonfire
Unicode 时间问题并不适用于您的情况(或大多数密码哈希比较),因为它要求攻击者对 comparePasswords() 的两个输入具有一定程度的控制。回过头来看,我并不是想把太多的文字都用在这个问题上……我只是想提高人们对这个漏洞的认识,但我怀疑我写得太多了 :) - Eli Collins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接