漫话密码存储

436 查看

背景

密码是用来进行鉴权(身份认证)一种手段,说白了就是证明你是谁。一般鉴权都可以总结为下面3种形式:

  1. 你知道什么? (如密码,密码提示问题等)

  2. 你有什么? (如信用卡,token卡等)

  3. 你是什么?(如指纹识别,瞳孔识别等)

常见攻击方式

常见的针对密码的攻击方式有:

  1. 暴力破解(Brute-force)

  2. 字典攻击(Dictionary Attack)

  3. 彩虹表攻击(Rainbow table attacks)

暴力破解

暴力破解指的是尝试密码空间中所有的可能情况。

字典攻击

上面说了暴力搜索空间巨大,然而大部分用户选择的密码都是有一定规律的:如使用生日,姓名,单词缩写,电话,以及以上各种方法的混合。如果把这

些常见的密码放到一个文件里,破解密码的时候就直接从这些常见的密码中尝试,这样尝试的空间和暴力破解相比就会小很多。这种存储了许多常见密码的
文件就像是一本用户常见密码的字典,字典攻击因此得名。

彩虹表

彩虹表:彩虹表是计算机科学时间/空间权衡的典型体现。起核心思想和查找表是类似的,只是相对于查找表,彩虹表需要的存储空间相对较小,查找速度

比查找表的O(1)要慢一点。通俗一点说就是:彩虹表牺牲了一点计算速度,换来的好处是较少的空间存储更多的密码数据。举个例子: 如果一个查找表里需要
存储所有的10个字符小写字母的字典(至少需要存储用户名,密码hash输出),需要的磁盘空间为 26^10 * (10+16) / (2^40) ~= 3338T,如果改用彩虹表存储,
那么这个彩虹表可能只需要约300G的磁盘空间就能涵盖99.9%的组合。

密码存储的方式

直接存储

最简单直观的做法是将密码明文存储到数据库。这种方式数据库的一条记录类似: <user, pwd>。
这种方式最简单但无安全可言。黑客获取了数据库就活得了所有用户信息。因此直接存储明文是不可取的,至少需要对明文做一些处理

hash存储

先对明文密码进行hash计算,将hash函数的输出存储到数据库。这里的hash是指密码学中具有单向性和不可逆性的hash函数(如MD5,SHA1等),不是数据结构中的hash算法。这种方式
的数据库中记录格式类似: <user, hash(pwd)>。这种方法比存明文要好很多,恶意用户获得了密码数据库以后不能直接得到用户的密码。这种方法的问题是:抵抗不了字典和
彩虹表攻击,一旦黑客针对数据库构造好彩虹表,就很容易破解了。因此,我们需要一种方法使恶意用户预先构造字典和彩虹表的代价变大。

密码hash加盐后存储

密码学中的盐是指一段随机字节串,通常和密码一起作为hash函数的输入。密码加盐后,数据库记录类似 <user, hash(pwd + salt)>,这种方法有一些变种如:
<user, hash(hash(pwd) + salt)>或 <user, hash(hash(pwd) + hash(salt))>,本质都是一样的。加盐后的数据库构造彩虹表需要的巨大存储空间,
使得构造查找表和彩虹表的攻击方式失效。

  1. 大大增加构成字典和彩虹表的成本。

  2. 降低相同密码hash值相同的概率。

关于盐的使用有一点需要说明:加盐的目的是为了增加提前构造字典和彩虹表的代价,并不是为了加密或增加密码hash的计算复杂性。因此,盐并不需要加密存储,
通常是明文和用户名一起存储于数据库的。

自适应加盐存储

前面说了,密码加盐能有效的对抗提前构造字典和彩虹表的攻击,但是对于暴力破解还是无能为力,对于单个用户的密码,如果黑客采用多台计算机并行计算或者
采用GPU等特殊硬件进行暴力破解,能很快的破解较弱的密码。
因此,比较理想的密码存储方法要具备2点:

  1. 单次密码存储的计算要尽可能的慢,慢到对单个普通用户的鉴权来说计算时间刚好可以接受,但是对于需要尝试很多密码组合的恶意用户来说,计算代价将会大大增加。

  2. 可以动态的控制密码计算过程的复杂性,这样就可以应对摩尔定律下计算机计算能力越来越强的趋势。

总结

密码的安全存储目前正确的做法是使用自适应加盐的方法进行存储,具体的说是可以使用pbkdf2, bcrypt, scrypt中的任何一种。本文主要讨论了如何安全的存储密码,
如果是保证用户账户的安全,那么除了保证密码存储安全之外,通常需要综合使用下面的各种方法:

  1. 引导选择强度较高的密码

  2. 使用验证码

  3. 密码验证错误以后短时间锁住账号

  4. 敏感操作需要二次鉴权

  5. 使用two-factor验证

参考链接

  1. 加盐密码哈希:如何正确使用

  2. 密码破解之王:Ophcrack彩虹表(Rainbow Tables)