加密储存 — 密码保存那些事
系列文章
讲完加密通信,再来说说加密保存,这两者最大的一个区别就是对 时效的要求,加密通信会对延迟有比较高的要求,但是储存相对来说就会宽松很多(用户并不会特别在意加密一个文件花费了1秒钟);还有一个区别就是,保存有时候只需要单向性,比如保存密码,我们只需要保存加密后的值,永远不需要保存密码原文(验证的时候只需要将用户输入同样加密一下,比较两个加密值即可)。
由于只需要单向性,所以我们不需要用到加密通信里面的种种加密方式,只需要使用Hash函数即可;假如需要双向性,如保存文件,大多使用的是对称加密,因为1)加密和解密是同一个人,不存在密钥分发问题;2)使用非对称加密的话,私钥的保存问题(假如加密文件和私钥放在一起,那么等于把钥匙放到了锁的旁边)。
那么我们这里就来聊聊“ 密码保存”的那些事,这里我会从最简单(最不安全)的方法开始,一步步的优化改善,最终得到一个可以实际应用的方案。首先要明确攻击者的最终目标,攻击者的最终目标就是“获得用户密码的明文信息”(注意是明文),而我们的目标便是阻止攻击者获得原文密码。下面来逐一看看各种方案:
方案一:明文储存
直接密码原文保存到数据库内,这种方式最简单,不需要任何额外的操作。不足:一旦数据库被攻破,数据泄露,攻击者就可以得到密码原文。
方案二:Hash
相比较于保存密码原文,我们改为保存密码Hash后的值,验证时通过比较Hash值而不是直接比较密码。相比较于第一种方式,该方式提高了一定的安全系数,攻击者无法直接得到密码原文,需要多做一步破解工作。不足:攻击者仍可以通过破解Hash函数来获得密码原文(经常会发生!)同时攻击者只要攻破了一个密码就能攻破所有相同的,比如:他计算得到"hello"的Hash值是avaggaghsgsfga,那么所有密码Hash值是avaggaghsgsfga的用户真实密码都是"hello"。
方案三:Hash + Salt
相比较于直接计算密码原文的Hash,该方案针对每一个用户,先在密码原文后面添加一段随机字符 (salt),然后计算整体的Hash(即 H ( p a s s w o r d + s a l t ) H(password+salt) H(password+salt));这样攻击者就需要针对每一个用户都破解一次,因为即使两个用户的密码都是"hello"但是他们的salt不一样,导致了最后的Hash值也不一样。不足:针对单个用户,并没有提高多少的安全性;同时假如攻击者的算力足够高,仍然有可能破解出所有的用户密码。
终极方案:Hash + Salt + Iteration count
相比较于方案三计算 H ( p a s s w o r d + s a l t ) H(password+salt) H(password+salt),该方案计算 H ( H ( . . . H ( p a s s w o r d + s a l t ) . . . ) ) H(H(...H(password+salt)...)) H(H(...H(password+salt)...)),重复计算N次(N应该是一个可配置的变量)Hash。该方案的核心思想就是让Hash计算变得“更慢”,这样的话攻击者的破解难度就会直线上升,但不会对正常使用造成过大影响。比如重复计算100次Hash使得验证一个Hash比原来慢了100倍 (0.1ms -> 10ms),但是这个改变对于正常使用来说完全可以接收,因为1)只需要计算一次就可得出结论(正确与否);2)使用的频次比较少(用户可能几天才需要登录一次)。但是对于攻击者来说就大大增加了难度,因为攻击者通常需要几万次乃至几十万次的尝试才能破解得到密码,假如单次速度变慢100倍,累计起来就是一个很大很大的延迟。
由于储存密码这个需求实在是太普遍了,所以现在有很多现成的库都实现了相关功能,甚至很多数据库本身就已经集成了储存密码相关的功能。
常见攻击种类
最后我们再来看看针对密码有哪些常见的攻击:
- 离线字典攻击(比如:破解hash后的密码)
- 防御:通过salt和iteration count增加其破解难度
- 特定账户攻击(使用字典破解某一个账户)
- 防御:最大尝试次数,增加密码复杂度
- 常规密码破解(使用一些常见的密码来进行尝试,如rockyou密码本)
- 防御:增加密码复杂度
- 针对单个用户猜测密码(先对目标进行一定的研究,然后据此进行猜测)
- 防御:用户训练(如不要使用太有规律的密码),增加密码复杂度
- 寻找用户漏洞(如不经意间分享了密码,使用了初始密码等)
- 防御:用户训练,使用一次性的初始密码(强制用户首次登录后立刻修改密码)
- 寻找相同的密码(在不同的系统上尝试相同的密码)
- 防御:用户训练,使用好的密码管理工具(不同系统不同密码)
- 网络漏洞(如监听你的网络流量等)
- 防御:加密通信
从上面可以看出,“增加密码复杂度”可以防御很多的攻击,因为随着复杂度的增加破解的难度是呈指数性增加的。比如我们规定密码只能是小写字母和数字(简单起见,实际复杂多了),那么每一位有 26 + 10 = 36 26 + 10 = 36 26+10=36种可能,一个N位的密码,就会有 3 6 N 36^{N} 36N种排列组合。