近日, CSDN
社区网站数据库泄露 ,近
600 万用户真实账号密码外泄。该事件横扫整个中文互联网,并且随后又爆出
多玩游戏 800 万用户资料被泄露
,另有传言人人网、开心网、天涯社区、世纪佳缘、百合网等社区都有可能成为黑客下一个目标。一时间人人自危,更换密码者无数。

过去一段时间以来, 许多的网站遭遇用户密码数据泄露事件,
这其中包括顶级的互联网企业–Linkedin,
国内诸如CSDN,该事件横扫整个国内互联网,随后又爆出多玩游戏800万用户资料被泄露,另有传言人人网、开心网、天涯社区、世纪佳缘、百合网等社区都有可能成为黑客下一个目标。层出不穷的类似事件给用户的网上生活造成巨大的影响,人人自危,因为人们往往习惯在不同网站使用相同的密码,所以一家“暴库”,全部遭殃。

这到底是怎么回事?用户信息是如何泄漏的?为什么一瞬间就有这么多密码被轻易获取,而一般的网站又是如何保护用户密码的?黑客和网站管理者之间,展开过怎样的密码攻防战?死理性派就此做了一番详细的介绍。

那么我们作为一个Web应用开发者,在选择密码存储方案时, 容易掉入哪些陷阱,
以及如何避免这些陷阱?

黑客的目标:你的密码存在哪儿?

很多人对这次事件都有一个疑问:网站把我的密码存在哪?

对于大部分网站,密码的存储和验证过程简单来说就是:用户输入密码,密码被传输到服务器,服务器将密码存储起来(注册)或和已经存储的密码比对(登录)。不论在哪个步骤,都有可能遭到黑客的攻击和入侵。本次事件的数据库泄露,就和第
3 个步骤相关。

一般来说,每个网站都有一个或多个专门存储密码的数据库。这些数据库通常并不能下载到。那黑客们是如何得到数据库的呢?

比较技术的方法是通过一些服务器返回的错误信息推断数据库的位置,例如“%5c大法”。它的原理是,故意进行一个错误的数据库访问,从错误信息中获取数据库真实地址,再通过这个地址下载数据库。但修补这个漏洞的方法也很简单,只要更改一下数据库出错时返回的错误信息就可以了。

有经验的软件管理员自然会对这种技术手段有所防范。但密码泄漏的途径其实不像想象中的那么“技术”:可能某个辞职员工偷偷带走了数据库;可能某台存储数据库的服务器染上了病毒;可能有人偷偷潜入了机房拷贝走了数据……总之,黑客会通过各种可能的手段获得存有用户名和密码的数据库,数据库泄露的危险总是存在的。

美高梅在线登录网址 1

图片来源:XKCD

普通方案

目前用的最多的密码存储方案是将明文密码做单向哈希后存储,单向哈希算法有一个特征:无法通过哈希后的摘要(digest)恢复原始数据,这也是“单向”二字的来源。常用的单向哈希算法包括SHA-256,
SHA-1, MD5等。

//import “crypto/sha256”

h := sha256.New()

io.WriteString(h, “His money is twice tainted: ‘taint yours and ‘taint
mine.”)

fmt.Printf(“% x”, h.Sum(nil))

//import “crypto/sha1”

h := sha1.New()

io.WriteString(h, “His money is twice tainted: ‘taint yours and ‘taint
mine.”)

fmt.Printf(“% x”, h.Sum(nil))

//import “crypto/md5”

h := md5.New()

io.WriteString(h, “需要加密的密码”)

fmt.Printf(“%x”, h.Sum(nil))

单向哈希有两个特性:

1)同一个密码进行单向哈希,得到的总是唯一确定的摘要。

2)计算速度快。随着技术进步,一秒钟能够完成数十亿次单向哈希计算。

结合上面两个特点,考虑到多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合,
然后与数据库中的摘要进行比对即可获得对应的密码。这个摘要组合也被称为rainbow
table。

因此通过单向加密之后存储的数据,和明文存储没有多大区别。因此,一旦网站的数据库泄露,所有用户的密码本身就大白于天下。

道高一尺:加密让数据库更安全

数据库总有被黑客攻击的危险,那网站如何应对?众所周知,在本次事件当中,被泄露的
CSDN
数据库,用的是明文储存密码。顾名思义,明文密码就是直接将用户输入的密码存到数据库中。这种方式的安全性不言自明。

既然明文密码太危险,那不妨用加密密码。但怎么加密呢?这就要说到大名鼎鼎的哈希函数了。

哈希是一种从从多到一对应的函数,它的作用是将一个很大的集合映射到一个很小的集合。例如,我们可以设计一个哈希函数
h(x) = x % 100 (其中 % 表示取余数,如 4 % 3 = 1,8 % 2 =
0),这样我们就把所有的整数 x 映射到了一个 0 到 99 的有限集合中。而像
MD5 和 SHA-1 这些著名的算法,都是哈希函数。当然,和前面这个 h(x)
不同,它们的计算方法非常复杂,常常需要经过很多轮计算。

哈希一个很重要的作用是帮助查找。很容易看到,如果两个数 x 1
x 2 对应的哈希值 h( x 1 ) 和 h( x 2 )
不相等,那么 x 1 和 x 2
也一定不相等。这样一来,我们就可以用两个对象的哈希值进行过滤。因为哈希值的集合比原始值的集合更小,所以比较哈希值的速度是很快的。

美高梅在线登录网址 2

典型的哈希算法。 图片来源:维基百科

而这个想法,也被应用于密码的保护上。实际上,无论是避免数据库泄露后的危险,还是出于对用户隐私保护的需要,现在大部分网站数据库存储的密码都不是明文,而是用户密码的哈希值,也就是所谓的加密密码。如果输入了一个错误的密码,它的哈希值就会有很大可能性与正确密码的哈希值不同,那么用哈希值代替密码进行比较也没有什么问题。另外,哈希的作用是将一个大集合映射到一个小集合,所以它的结果一定是不可逆的(因为从小集合到大集合的映射是一对多的映射)。这种不可逆性,使得理论上从哈希值找出原始密码成了一个不可能的任务,从而保护了密码。

但是,也会出现这种情况:两个不同的原始密码,它们的哈希值一样。这在密码学上被称作“碰撞”。碰撞是一个设计良好的哈希函数需要极力避免的,正因为如此,很多哈希函数的运算结果都很长,例如知名的
MD5 算法,它的运算结果包括了 128
位二进制数。试想一下,如果使用之前提到的哈希函数 h(x) = x %
100,随便输入一个密码,就有 1/100
的可能性验证通过,那还了得!好在现在流行的哈希函数,在这一点上做的还都还不错。

进阶方案

通过上面介绍我们知道黑客可以用rainbow
table来破解哈希后的密码,很大程度上是因为加密时使用的哈希算法是公开的。如果黑客不知道加密的哈希算法是什么,那他也就无从下手了。

一个直接的解决办法是,自己设计一个哈希算法。然而,一个好的哈希算法是很难设计的——既要避免碰撞,又不能有明显的规律,做到这两点要比想象中的要困难很多。因此实际应用中更多的是利用已有的哈希算法进行多次哈希。

但是单纯的多次哈希,依然阻挡不住黑客。两次 MD5、三次
MD5之类的方法,我们能想到,黑客自然也能想到。特别是对于一些开源代码,这样哈希更是相当于直接把算法告诉了黑客。

没有攻不破的盾,但也没有折不断的矛。现在安全性比较好的网站,都会用一种叫做“加盐”的方式来存储密码,也就是常说的
“salt”。他们通常的做法是,先将用户输入的密码进行一次MD5(或其它哈希算法)加密;将得到的
MD5
值前后加上一些只有管理员自己知道的随机串,再进行一次MD5加密。这个随机串中可以包括某些固定的串,也可以包括用户名(用来保证每个用户加密使用的密钥都不一样)。

//import “crypto/md5”

//假设用户名abc,密码123456

h := md5.New()

io.WriteString(h, “需要加密的密码”)

//pwmd5等于e10adc3949ba59abbe56e057f20f883e

pwmd5 :=fmt.Sprintf(“%x”, h.Sum(nil))

//指定两个 salt: salt1 = @#$%  salt2 = ^&*()

salt1 := “@#$%”

salt2 := “^&*()”

//salt1+用户名+salt2+MD5拼接

io.WriteString(h, salt1)

io.WriteString(h, “abc”)

美高梅在线登录网址 ,io.WriteString(h, salt2)

io.WriteString(h, pwmd5)

last :=fmt.Sprintf(“%x”, h.Sum(nil))

在两个salt没有泄露的情况下,黑客如果拿到的是最后这个加密串,就几乎不可能推算出原始的密码是什么了。