public function hash_password(password, usersalt)
{
var result = hash('sha256', usersalt . SITE_SALT . password);
for (i = 0; i < 100; i++)
{
result = hash('sha256', usersalt . SITE_SALT . result);
}
return result;
}
注意:
SITE_SALT
是存储在文档根目录之外的配置文件中的值。 usersalt
是为每个用户生成的唯一盐,并与密码哈希一起存储在数据库中。#1 楼
是。像您一样迭代哈希会略微增加发生冲突的机会(因为哈希函数不是随机排列,而是随机函数的近似值):如果两个不同的密码在100个步骤中的任意一个步骤上产生相同的哈希,就足够了,产生相同的最终哈希值。 (但是正如Thomas在评论中指出的那样,发生冲突的可能性仍然很小。)通常,在以下迭代中输入原始密码也被认为更好,这也不利于这种效果:即使如果两个不同的密码在步骤39给出相同的哈希值,则在步骤40,它们的哈希值将再次不同。
result = hash('sha256', result . usersalt . SITE_SALT . password);
另一方面,迭代需要更多时间,并且因此,更多的计算能力(包括您和蛮力攻击者)都很好。
坏消息是,像SHA256这样的快速哈希很容易并行化,这意味着攻击者可以尝试数百种廉价硬件(例如GPU)上一次输入密码。在这里重复执行哈希100次并不多。
相反,请使用内置了一些工作因子参数的慢速哈希函数。一个示例是PBKDF-2(本质上就是您要做的事情,只是具有较高的重复计数),另一个示例是bcrypt(其基于河豚的关键调度算法,并且需要大约4 KB的内存,该内存会不断变化,从而使在标准GPU上的实现效率低下),或者是较新的scrypt,所需的内存也取决于工作参数(这意味着只有在有大量可用内存(并且也可以快速访问)的情况下,才能并行化它。)
设置工作因数这样您的用户就可以接受所需的时间。
评论
$ \ begingroup $
值得补充的是,碰撞的风险有所增加,但仍然可以忽略不计。对于256位哈希函数,多次迭代最终(在平均$ 2 ^ {128} $个操作之后...)最终形成一个平均长度为$ 2 ^ {128} $的大“循环”,其大小足以使碰撞不太可能发生。
$ \ endgroup $
–托马斯·波宁(Thomas Pornin)
2011-10-12 18:44
#2 楼
在问题的代码中,控制迭代次数的参数$ n = 100 $(几乎线性地)控制了评估hash_password
函数的成本。作为一阶近似,在已知结果和盐的攻击模型中,使用$ n $,对手通过蛮力在列表中找到正确密码的时间会增加(几乎线性)。有一个陷阱:增加$ n $也会由于散列冲突而导致随机密码与正确密码产生相同结果的几率增加,因此是错误但可以接受的密码;但是对于$ n $的实际值来说,这仍然不太可能达到$ n·2 ^ {-256} $的顺序,并且在实践中可以完全忽略。
因此,出于所有实际目的,增加$ n $不会削弱任何功能(当然也不是密码,因为SHA-256内部没有密码,因为没有密码);并且大大提高了对字典攻击的抵抗力。增加$ n $的唯一缺点是,对于合法方来说,这会有一定的成本(CPU使用率,能源,延迟)。
好的做法是,注意
hash_password
和hash
的优化;然后在可能出现的情况下增加$ n $的容忍度。最佳实践是使用经过精炼的PBKDF来提高攻击者与合法方之间的成本比率,例如scrypt ;并每隔几年修改$ n $等参数以说明技术进步。
评论
$ \ begingroup $
@PaŭloEbermann:对改进表示赞赏!
$ \ endgroup $
–fgrieu♦
11-10-13在19:16
评论
这里有更多讨论:Webapp密码存储:盐化哈希还是多个哈希?