浅谈用户密码保护与相关技术(上)
一、 全文涉及
上篇:哈希,彩虹表
下篇:加盐加密,慢哈希,非对称加密与HTTPS
二、 主题引入
2011年12月21日,CSDN后台数据库被黑客恶意发布到互联网上并提供下载,此数据库中包含了642万多个用户的帐号、密码等信息,严重威胁了相关用户的信息安全。此次事件之后,人人网、猫扑、嘟嘟牛、178游戏网等多家网站的部分用户数据库也纷纷被传到网上并提供下载,甚至有媒体曝光国内十几家大型门户级网站的数据库也已经被黑客“拖库”,因此将2011年末的密码危机推向了高潮。
令人啼笑皆非的是,csdn作为号称最大的中文在线同性程序员交友网站,主要用户均为技术人员,而其后台用户信息数据库竟然全部使用明文记录——这是一种非常低级的行为。或许单纯的csdn账号泄露不足以引起用户损失,然而很多用户通常习惯于多个账号使用相同的口令密码。一旦黑客掌握了用户的社交网络账号乃至银行卡账号等重要账号,用户的隐私及人身财产安全均有可能受到巨大威胁。
用户信息数据库加密保护的重要性如斯,本文将就相关技术进行探讨。
三、 正文部分
(一) 哈希
哈希函数又名散列函数,是一种不可逆的信息摘要函数
f(x)=y
“不可逆”即无法通过算法由函数值得到输入值;“信息摘要”是指由输入值的某些特征信息值得到最后结果,不同输入值得到的结果不同。尽管设计的目的是好的,但是由于输入内容的无限性,而输出值的有限性,总会出现两个不同的输入值出现相同的输出结果。
注意一点:用于密码加密的这一类哈希函数是所谓的“加密哈希函数”(cryptographic hash),它们是哈希函数的一个子集,对于安全性的要求比起数据结构中用于分配哈希桶的哈希函数要高出很多。下文中所述的“哈希函数”均指“加密哈希函数”。
一个好的哈希算法,对于不同的输入值,理应给出很难重复的哈希值。一旦发生这种情况,即出现了所谓的“撞库”。著名的信息摘要算法“MD5”算法近年来愈发受到诟病,其原因除了本身安全性较低外,还有MD5码以16进制记录时仅有的32位长度限制;相比于SHA-256结果足足256位的长度,在电脑的计算力越来越强大的今天,MD5加密已经禁不起黑客们的撞库破解。
除此之外,执行效率也是衡量哈希算法的一个重要因素。MD5最初受到广泛欢迎就是因其极快的执行速度,对于一段字符串仅需毫秒级别的执行时间。因而现在MD5算法虽然在密码学领域受到排挤,但是仍然被广泛用于文件校验等场景。
(二) 哈希在用户信息加密中的应用
哈希因为不可逆的特性,被广泛应用于信息加密领域。
一个记录用户登录信息的数据库,最起码的要求就是对用户密码进行一次加密(然而之前的csdn连这点都没有做到)。当用户输入账号密码时,服务器会再次对密码进行哈希,并将用户名和哈希值与数据库中的记录比对,从而确定用户是否可以获得授权。
这样做的好处是:一旦黑客“拖库”,即窃取了服务器数据库中的数据,他仍然无法获得用户的明文密码——他得到的只有哈希值。而完成登陆的正确途径是输入可以计算得到相应哈希值的明文密码,这样一来黑客就需要找出这样一个值。
(三) 常见的哈希破解方法
对于获取了哈希结果的黑客来说,下一步要做的就是得到满足哈希结果的原始口令了。只不过黑客不需要得到与原始值一模一样的密码,他的目标只是“碰撞”而已。由于哈希函数有限对应无限的特点,即总有x1≠x2,却能使hash(x1)=hash(x2)=y。黑客只要能找到与原文密码生成相同结果的一个字符串就能够通过服务器的验证。
而这一过程显然并不简单,在拥有彩虹表之前,可以进行的两种简单的破解方法是暴力破解法与字典破解法。
1. 暴力破解
虽然我的算法成绩不怎么样,但是这不代表我不知道穷举法是一种糟糕的方法。尽管如此,有时事情就是“知其不可而为之”。借助于现代计算机强劲的计算力,黑客将目标定位于全体字符组合的变幻莫测的星辰大海,穷尽天荒地老的时间来追寻那似乎遥不可及的最后的光点。
2. 字典破解
事实证明暴力破解的效率真的不高,于是其进阶版本的字典破解出现了。所谓的字典破解,原理就是利用了人类想象力贫瘠和懒得记忆复杂密码这两个普遍缺点,将暴力破解的范围限定在相对小了很多的字符组合中。
事实上,字典破解中收录了极多的可能出现的密码组合,并记录了相应的哈希值——显然对于不同的加密算法,需要不同版本的字典来记录。总之,发挥了共享精神的黑客们不断发展壮大自己的字典,在简化破解流程的道路上越走越远。
只不过美中不足的是,由于字典的体积越来越庞大,查表本身消耗的时间也在快速增长,如何加快查表速度成为了技术爱好者们的一大难题。该问题引出了下一个重要概念——彩虹表。
(四) 彩虹表
所谓rainbow table,通常被错误地描述成一个巨大的原文密码与其哈希值的对应集合(体积达到可怕的几百G)。然而事实上彩虹表不是简单的字典破解的plus版,它所谓的time-memory trade-off,并不是简单地“以空间换时间”,而是双向的“交易”,在二者之间达到平衡。
1. 哈希链集
在彩虹表的概念之前,就已经出现了针对哈希加密的破解手法——“预计算的哈希链集”(Precomputed hash chains)。
对于需要破解的哈希函数H,定义一个定义域和值域与H刚好相反的函数R(reduction function)。很显然,R不会是H的反函数,但是我们只需要将R的值域限定在H的定义域就好。
定义这样的一个过程HR:对输入值首先进行一次H运算,再进行一次 R运算。该过程重复k次后,得到一个哈希链:
以上就是进行了2次HR运算的一个哈希链。然而,需要储存的只有头部的“123”和尾部的“789”而已。数据库中记录的就是海量的哈希链头尾形成的元组。
使用时,已经获取到了哈希结果“def”,尝试对其进行一次R运算,得到了结果“789”,查表发现“789”存在于表中,这意味着有很大的可能碰撞成功。此时获知哈希链的头部是“123”,于是使用“123”进行HR过程的哈希链计算,很快就发现了“def”对应的原文密码是“456”。
那么如果从数据库中获取的哈希结果是“abc”呢?事实上,对于一次R计算没有在表中获得对应结果的值,就可以开始进行k-1次HR过程,每次完整的HR过程都能得到一个值,用这个值进行查表,一旦得到匹配的哈希链尾部结果,就可以使用该链的头部推知原文密码。选用次数k-1,是因为更长的HR运算已经超出了一个哈希链最大的长度,更多的迭代是没有意义的。
2. 时间与空间的平衡
事实上,尽管哈希链集的思想非常不错,但是它的效率受到一个重大的制约:R函数的选取。R函数首先要能够成功地把原哈希函数的值域与定义域交换,此外R函数的碰撞也要尽可能小。因为这两个要求的存在,想要找到一个优秀的R函数是非常困难的。然而一旦碰撞发生,就会出现下图所示的情况:
由于哈希链出现了部分重复,这两个哈希链能解密的明文数就比理论的2k要低。而且由于表中只记录了哈希链的头尾,这种重复并不会被轻易发现。随着表的增大,碰撞频发,大量的冗余和浪费就出现了。
2003年,彩虹表算法的提出对这个问题作出了很好的改进:在每一次HR过程中,不再使用相同的R函数,而是分别在k次运算中使用R1,R2,R3,…,Rk共k种不同的R函数。这种情况下即使发生碰撞,也通常是如下情况:
由于不同的R函数,使得碰撞带来的冗余大大减少。只不过这样产生的哈希链在用于解密时需要的步骤稍微复杂一些。
参考资料:
[1] 张耀辉.CSDN“拖库”事件后对密码保护的反思
[2] 维基百科. 散列函数
https://zh.wikipedia.org/wiki/%E6%95%A3%E5%88%97%E5%87%BD%E6%95%B8
[3] Wikipedia.MD5
https://en.wikipedia.org/wiki/MD5
[4] 撞库 拖库与洗库
http://blog.csdn.net/daliaojie/article/details/42171177
[5] What is the differencebetween a Hash Function and a Cryptographic Hash Function?——CodesInChaos’s answer
[6] 什么是彩虹表?——Smallay的回答
https://www.zhihu.com/question/19790488
浅谈用户密码保护与相关技术(下)
一、 全文涉及
上篇:哈希,彩虹表
下篇:加盐加密,慢哈希,非对称加密与HTTPS
二、 正文部分(接上篇)
(一) 加盐加密
在上一篇中介绍了常用的加密手段——哈希,以及黑客们常用的哈希解密方法——暴力破解、字典破解和彩虹表。其中彩虹表以高效快速著称,是哈希解密的不二选择。
只不过魔高一尺,道高一丈,彩虹表也不是战无不胜的。一个强有力的对抗方案就是使用加盐加密。
加盐加密是指在加密密码时,不只是对密码进行hash,而是对密码进行添油加醋,放点盐(salt)再加密。比如原本的密码是“123456”,不加盐的加密方法是hash(“123456”),很容易被彩虹表攻破;而如果使用字符串拼接,将“盐”加到原文密码的后面,可能效果就是这样的:hash(“123456”+”abcdef”)。
服务器验证时,也是先将原文密码加盐再进行哈希,此时黑客使用彩虹表破解出来的结果便无法通过验证了。假设黑客恰好得出了“正确的”原文密码,即“123456abcdef”,但是服务器真正验证的是hash(“123456abcdef”+”abcdef”),黑客提交的密码依然是无效的。
随之而来的问题就是盐的选取了。
每次哈希加密都使用相同的盐值是很容易犯的一个错误,这个盐值要么被硬编码到程序里,要么只在第一次使用时随机获得。这样加盐的方式是做无用功,因为两个相同的密码依然会得到相同的哈希值。攻击者仍然可以使用反向查表法对每个值进行字典攻击,只需要把盐值应用到每个猜测的密码上再进行哈希即可。如果盐值被硬编码到某个流行的软件里,可以专门为这个软件制作查询表和彩虹表,那么破解它生成的哈希值就变得很简单了。
用户创建账户或每次修改密码时,都应该重新生成新的盐值进行加密。
如果盐值太短,攻击者可以构造一个查询表包含所有可能的盐值。以只有3个ASCII字符的盐值为例,一共有95x95x95=857,375种可能。这看起来很多,但是如果对于每个盐值查询表只包含1MB最常见的密码,那么总共只需要837GB的储存空间。一个普通的移动硬盘就能解决问题。
为了使攻击者无法构造包含所有可能盐值的查询表,盐值必须足够长。一个好的做法是使用和哈希函数输出的字符串等长的盐值,比如SHA256算法的输出是256位(32bytes),那么盐值也至少应该是32个随机字节。
为了让黑客在破解密码的时候受到更大的阻力,技术人员可以说是八仙过海各显神通。为了提高密码破解的难度,网络上流行着各种组合哈希加密的方法——比如说md5(sha256(password)+md5(salt))之类。然而对于这些独创方法,各方褒贬不一。因此接下来将介绍另外一种思路:使用一种时间复杂度更高的加密方法。
(二) 慢哈希
天下武功,唯快不破。然而在密码学中则不同。算法越快,越容易被破。
加盐加密的方法并不是万无一失的,一旦黑客拥有了庞大的计算能力,他完全可以耐着性子自己制作字典或彩虹表,这样一来安全系统还是难逃毒手;另一种特殊情况是,一旦黑客是有目标的,只需要破解某个用户的账户密码,那么他的成功概率就更是大幅提升了。
慢哈希的思想就是把哈希函数变得很慢——准确地说是消耗更多的计算资源,于是即使有着超高性能的GPU或定制硬件,字典攻击和暴力攻击也会慢得让攻击者无法接受。最终的目标是把哈希函数的速度降到足以让攻击者望而却步。
例如使用快男md5进行加密,每次哈希计算的时间可能只需要1微秒,黑客每秒可以猜词1000000个;如果将哈希一次的时间提高到10毫秒,黑客每秒就只能猜词100个,破解速度慢了一万倍。
这种慢哈希的实现依赖一种叫做密钥【yuè】扩展的技术。密钥扩展的实现是依靠一种CPU密集型哈希函数。不要尝试自己设计简单的迭代哈希加密,如果迭代不够多,是可以被高效的硬件快速并行计算出来的,就和普通哈希一样。应该使用标准的成熟算法,比如PBKDF2或者bcrypt。
这类算法使用一个安全因子或迭代次数作为参数,这个值决定了哈希函数会有多慢。将安全因子调的越高黑客破解的难度也越高。
然而事情没有这么简单,使用了慢哈希对服务端的计算能力也提出了要求。并发登录过多或者出现恶意用户发起DoS(拒接服务攻击)时,系统资源可能出现被耗尽的风险,所以安全因子也不能一味设置得过高。
另外一种稍显巧妙的解决方案是:利用客户端的计算力。如果在客户端利用用户自己的设备完成初步加密,再对中间结果在服务端进行相对简单的加盐加密,两全其美,岂不美哉?
事实上,这种解决方案并不安全,尤其是对于网页源码公开的web应用来说。一旦黑客拿到数据库,他并没有必要非得获取用户的原文密码才行,直接使用中间值绕过前端慢加密直接调服务器的登录接口,是更加方便快捷的选择!所以服务端的再次加密是绝对必要的。这样看来,这种所谓“巧妙”的方案的的风险也相对较高,对于使用信息传递没有保障的HTTP协议的网站来说更是如此。
然而,以上所有的加密方法都不是万全的,因为黑客有可能在网络的任何位置拦截数据包,并获取到密码的明文或加密后的中间值,抑或伪装成用户骗取服务器的信任。要避免这种情况出现,最好的方式就是使用引入了非对称加密的HTTPS协议。
(三) 非对称加密与HTTPS
1. 非对称加密
非对称加密是相对于对称加密而言的。
传统的对称加密方式,必须要求收发信息的双方使用同样的密钥进行加密和解密。但是当数据在互联网上传递时,无法保证两方拥有这种配合默契的密钥。因此密钥不可避免地要被随着数据一同发送——在极易被截获数据包的网络传输中,这种行为是没有太大意义的。而即使对密钥进一步加密,黑客要获得加密算法也不是非常困难的事情。
非对称加密正是为了解决这种问题而出现的。
简单概括非对称加密的思想:这种加密方式使用公钥和私钥进行,但是公钥是用于完成加密的,并不能对已加密的信息解密,解密工作只能由相对应的私钥进行;反之,私钥加密的内容只能使用公钥进行解密。如果我们希望和别人通信,我们就将自己的公匙给他,他使用我们的公匙加密信息产生密文,而我们使用自己妥善保管的私匙对密文进行解密。由于上面提到的公匙和私匙产生密文的唯一对应的特性,任何使用你的公匙加密的信息,只有你私匙才可以解密。这样通过将加密和解密的密匙进行分离,我们不必将解密密匙告诉别人,从而提高加密的安全性。
2. HTTPS
先介绍“数字签名”的概念,每次对要发送的数据进行摘要(使用某种哈希),将摘要用私钥加密后附加在最后,这样对方就可以使用公钥验证信息的准确性。
但是考虑这种情况:黑客想欺骗用户,他偷偷用自己的公钥进行了替换。此时,用户实际拥有的是黑客的公钥,但是还以为这是服务器的公钥。因此,黑客就可以冒充服务器,用自己的私钥做成"数字签名",发送消息用户,让用户用假的服务器公钥进行解密。
为了确认公钥真正的所有者,需要借助第三方的帮助。服务器被要求在CA(certificate authority,证书中心)做公钥认证。CA利用自己的私钥,将请求认证的服务方的公钥和相关信息加密,生成“数字证书”。这样,服务方就可以在自己的信息后附加数字证书即可。获取了证书的用户,会用从CA得到公钥进行证书的解密验证,最终确定发送消息者的真实身份。
HTTPS协议正是使用了以上机制的安全版的HTTP。完整的过程如下:
首先,客户端向服务器发出加密请求。
服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。
客户端(浏览器)的"证书管理器",有"受信任的根证书颁发机构"列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。
如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。
如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告。
如果数字证书是可靠的,客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。
有了如此完备的安全机制的HTTPS,才是网站信息安全的理想保障。当然HTTPS也不是万无一失的,出于服务方和用户两方的安全考虑,服务方设计出好的加密措施、完备管理机制,用户增强自己的网络安全意识都是必不可少的。
参考资料:
[1] CoderZh Blog.设计安全的账号系统的正确姿势
https://blog.coderzh.com/2016/01/03/security-design/
[2] Defuse Security.SaltedPassword Hashing - Doing it Right
https://crackstation.net/hashing-security.htm
[3] EtherDrea.对抗明文口令泄露 —— Web 前端慢 Hash
https://www.cnblogs.com/index-html/archive/2015/11/27/4999897.html
[4] 百度百科.对称加密
http://baike.baidu.com/item/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86
[5] mconintet.非对称加密和数字证书
http://www.jianshu.com/p/da70e98b71f7
[6] 阮一峰的网络日志.数字签名是什么?
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
[7] 吾道永昌.HTTP与HTTPS的区别
http://www.cnblogs.com/wudaoyongchang/p/6253451.html