验证思路的本质
验证思路的本质无非是对比当前用户输入的账号密码与正确的(且存储于数据库中的)账号密码是否一致,若一致则认为身份验证通过。
密码存储策略
store passwords as plain-text
考虑到安全性因素,原始密码当然不能明文直接存储于数据库。
Hash(password)
经过散列处理(如MD5),并将散列后的字符串进行存储,是一种存储密码的方法。
因为,散列后的值一般是无法通过特定算法得到原始字段(对应于原始密码)。
Lookup Table Attack(字典法攻击)
然而,通过一个很大的哈希映射表,并在表中搜索该MD5值,很有可能就在极短的时间内找到该散列值对应的原始字段。
这种攻击方法本质上是利用给特定的一个哈希函数一个特定的 input ,都一定会得到一个不会改变的 output。因此,攻击者可以提前构造好一个原始字符串到 hash 值的映射表。
最终,如果在这个映射表中找到了一个哈希值时(能不能找到取决于这个映射表有多大),就可以将 hash 值还原成原始密码了。
Rainbow Table Attack(彩虹表攻击)
Rainbow Table Attack(彩虹表攻击)是在Lookup Table Attack(字典法攻击)的基础之上,以时间来换空间。
Hash with salt
进而,我们采用加盐(salt) + 散列处理的方式。即,在进行散列处理之前,在原始字段的任意固定位置插入特定的字符串(salt值),其作用是让加盐后的散列结果和没有加盐的散列结果不相同,最终使得依赖彩虹表的破解方式以还原出原始密码的概率大大降低。
注意,这里的salt值,应该是为每个密码都生成一个对应的salt值(并将这个salt值明文存储于数据库中)。
一个常见的错误,是所有用户的密码使用同样的一个salt值(硬编码(Hard Code),作为一个静态变量存储于执行的程序集中),这样称为盐值复用(Salt Reuse)。
从代码实现的角度来说,在每个用户注册时,用户都需要设置自己的账号和密码。
- 这时,我们为这个用户生成一个高长度的随机数作为salt值。而不同的用户注册时,都会分别进行一次随机数生成。因此每个用户都对应一个特定的salt值;
- 将用户设置的原始明文密码+salt值作为一个整体,并进行散列处理,这个结果暂时称为“加盐后的密码散列值”;
- 将用户账号名、”加盐后的密码散列值“和盐值存储于数据库中;
- 此后每次用户登录时,将“此时用户尝试进行登录的密码”+“数据库中读取的对应该用户的盐值” 看做一个整体,并进行散列处理计算,若这个结果与“数据库中存储的加盐后的密码散列值”相同,则验证通过。
这样我们就能保证,即使我们的数据库被入侵者获取并全表下载后(拖库),黑客也几乎不可能将用户的登录密码还原出来。
加盐后的密码破解
经过加盐后,并不意味着被黑客拖库后就不能还原出原始密码,只是破解的成本被大大提高。
假设加盐值有1000种可能,一张彩虹表100G,如要暴力破解以测试这1000种可能,则需要100GB * 1000 = 10TB 的空间。
更何况我们还可以将salt值设置为一个高长度的随机数,最终使得通过暴力破解或者原始密码的可能性变为微乎其微。