##### 最简单的用户密码保存方式
用户访问网站,在注册时需要输入用户名和密码,在仅考虑功能的前提下,可以把用户名和密码以明文的方式存储在数据表中。
| Username | Password |
| --- | --- |
| foobar | foobar#123456 |
当用户登录时,直接使用用户提供的明文密码与数据库中的密码进行比对,如果相同则授权用户登录。
##### 面临的安全问题 : 数据库被窃取导致密码泄露
明文密码保存方式在实现上非常简单,但面临的问题也很明显:没有任何安全防护机制,任何有权读取数据库的人都可以获取所有用户的密码。因此我们需要一种即使数据文件被窃取,窃取者也不能获取用户密码的方式。
##### 解决方式:使用单向hash方式散列用户密码
通过hash的方式,可以比较有效的降低密码泄露的风险。 hash是把输入的数据流通过特定的算法处理后,输出是一个固定长度的字符串。如”foobar#123456”经过md5之后,值为”1276563ea8d2c0ef9b04979d59f5d93e”, 把这个字符串保存到数据库中替代密码明文。
| Username | Password |
| --- | --- |
| foobar | 1276563ea8d2c0ef9b04979d59f5d93e |
当用户登录时,把用户输入的密码经过md5之后与数据库中的字符串进行对比。
通过hash的处理,即使数据库文件被窃取,窃取者也不能直观的获取到foobar账号的密码是什么。
hash的特点是单向的:由输入流可以得到确定的输出字符串,但从输出的字符串反向获取输入流的代价极高,几乎是不可能做到的。
常见的hash处理方式是md5, sha1, sha256, sha512 等。
* md5的输出是16byte,128bit 的字符串;
* sha1的输出是20byte,160bit的字符串;
* sha256的输出是32byte,256bit的字符串;
* sha512的输出是64byte,512bit的字符串;输出结果的长度越大,hash方式的安全性就越高, 目前常用的是md5和sha1.
##### 简单hash方式面临的问题: Rainbow攻击
使用md5,sha1等hash防止直接对用户密码进行加密,是存在一定的隐患的。 窃取者可以通过Rainbow的方式进行暴力破解。
Rainbow的方式很简单, 首先根据密码所使用字符范围和密码长度构造出所有可能的密码序列(称为词典),然后对这些密码序列进行hash(如md5), 然后把hash后的字符串与数据库中的Password字段进行对比。 这样可以非常快速的比对到foobar账号的真实密码是什么。有测试表明,破解一个7位长度,有a-z,A-Z,0-9构造出来的密码,只需要不到40秒的时间就可以了, 非常快。
##### 改进的Hash处理方式:对抗Rainbow攻击
1. 让用户使用更长,含有更多特殊字符的密码
如让用户必须使用含有数字、大小写字母,甚至包括[!@#$%](mailto:!@#%24%)^&\*( 这种特殊字符的密码。这种方式能提高Rainbow攻击的hash词典的构造时间和比对时间,但由于以下两个原因,不建议过度使用该方式。
a. 由于现在CPU的计算能力极为强大,词典的构造时间并不是关键因素
b. 复杂的密码用户很难记忆,用户体验太差
3. 使用更高长位的hash方式
相比于md5和sha1,可以使用sha256或者sha512的方式,更长的hash串意味着攻击者需要花费更多的时间准备词典。但基于CPU计算能力的强大,这种方式的安全性仍然不足。
5. 使用salt进行hash
谓salt,其实是一个普通的字符串。在对密码进行hash之前,先把这个字符串与密码连接起来。如作为明文密码的前缀或者后缀,然后对新的字符串进行hash操作。 salt通常保存在代码中,这样即使数据库文件丢失,只要代码源文件不丢失,攻击者是没有办法获取原始密码的。
7. 使用速度较慢的hash方式
md5,shax系列的方式,都是通过优化的算法,每次的计算都极为迅速,攻击者准备攻击词典的时间也就大大的缩短。因此我们可以选择较慢的hash方式,如bcrypt。
bcrypt是一种基于blowfish算命的hash方式,其特点是设计者可以根据选择hash的时间消耗。 如设置高等级的时间消耗,一次bcrypt甚至可以需要300毫秒才能计算完毕,而通常的md5甚至不需要1毫秒。 这样可以让攻击者的词典准备时间延长300倍,极大的增加了攻击时间成本。
##### 完美的解决方案
1. 使用bcrypt慢hash方法
2. 为每个用户的hash过程提供salt
3. 使用全局salt
通过这三个步骤, 密码的生成过程可这样表示 :
$hashPasswd = hash( $global\_salt + hash(cleartext\_password + $user\_only\_salt));
##### 推荐的解决方案
基于时间成本、项目复杂度和安全读需求,常规Web类项目的推荐解决方案如下
1. 使用md5或者sha1
2. 为每个用户的hash过程提供salt
3. 使用全局salt
密码的生成过程为:
$hashPasswd = sha1( $global\_salt + sha1(cleartext\_password + $user\_only\_salt));