虽然业界已经达成共识,在传输用户密码等需要保密的信息时,尽可能采用HTTPS/SSL协议传输。但我们还是可以看到少数没有用HTTPS/SSL加密的网站或应用。新浪微博的登录页面和MySQL是两个例子。接下来我们详细分析它们的密码传输和保存机制。
新浪微博的登录页面的URL是http://www.weibo.com/login。从这可以看出新浪微博的登录页面没有采用HTTPS来传输用户的密码。
如果我们对登录相关的代码感兴趣,我们可以用浏览器的调试功能看到如下图所示的代码:
从这段代码我们可以猜出新浪微博的登录过程如下:
新浪微博的客户端往服务器端发送一个连接请求;
服务器端响应客户端的请求,返回的信息里包含当前时间(图中的servertime)和一个随机数(图中的nonce);
客户端对用户的密码进行加密,然后再发送给服务器端验证。加密的算法是SHA1(SHA1(SHA1(Password))+ salt),其中的salt是从服务器返回的时间和随机数。
我们看不到服务器端的代码,但大致可以猜出数据库里存的是明文密码的一次或者两次SHA1加密之后的哈希值。为了防止被彩虹表法破解,存两次SHA1加密的哈希值的可能性要高一些。
服务器端在验证密码的时候先从数据库里读出密码的哈希值。服务器端知道自己发给客户端的时间和随机数,于是它把密码的哈希值和时间以及随机数拼接起来再用SHA1算法计算新的哈希值。最后它比较计算出来的哈希值和从客户端接收到的用户密码加密后的哈希值。如果两个哈希值相同,则登录验证成功。
新浪微博这种密码保护机制还是存在很大风险。一旦存储用户密码的数据库被黑客侵入,那么黑客很容易就能用他人的密码登录。
黑客侵入数据库之后,他就知道他人密码的两次SHA1算法的哈希值。接着他可以写一个假的客户端,然后通过假的客户端欺骗服务器端返回当前时间和一个随机数。接下来把服务器端返回的信息当作盐和数据库里读取的密码的哈希值拼接到一起再调用SHA1算法算出新的哈希值,最后把这个哈希值发送给服务器端。由于这样生成的哈希值符合新浪微博密码验证的协议,服务器端会认为密码有效。
因此黑客尽管没有破解他人的密码,但他却在侵入数据库之后用他人的密码登录。
MySQL的密码保护机制和前面的新浪微博的机制有类似之处。在数据库表格mysql.user的Password列中,密码存的是用SHA1算法两次加密之后的哈希值。客户端和服务器端的通讯协议包含如下几个步骤:
MySQL客户端(比如mysql.exe)发起连接请求;
MySQL服务器端(mysqld.exe)返回一个随机字符串scramble;
MySQL客户端接收到scramble后,进行如下计算并把token发给服务器端:
stage1_hash = SHA1(password),其中password是用户输入的明文密码;
token = SHA1(scramble +SHA1(stage1_hash)) XOR stage1_hash
服务器端在接收到加密之后的密码token之后,采取如下步骤验证密码是否有效:
stage1_hash' = token XOR SHA1(scramble +mysql.user.Password);
计算SHA1(stage1_hash')的值,并和mysql.user.Password作比较。如果两者相同则密码有效。
如果对MySQL的源代码感兴趣,相应的代码在.\mysql-5.5.37\sql\password.c可以找到。
我们可以看出MySQL的密码在传输过程中客户端与服务器端的协议比新浪微博的要复杂。这样的好处是如果黑客入侵了mysql.user表拿到了两次SHA1加密的哈希值,他也没有办法伪装他人。这是因为在通讯协议的第3步在计算token的过程中,需要对密码作一次SHA1加密的哈希值。而在mysql.user表中,只有对密码作两次SHA1的哈希值。
虽然MySQL的机制比新浪微博的要安全一些,但它仍然存在安全隐患。这个机制的短板在于密码的存储。存储密码的两次SHA1算法的哈希值并不能有效防止彩虹表法破解。很多黑客手上都有SHA1算法哈希值的彩虹表,他们只要把这样的彩虹表里的哈希值再作一次SHA1加密就可以了。
MySQL采用这个机制也可以理解。如果黑客能够侵入mysql.user表格,那么他自然也能侵入MySQL数据库中的其他表格。通常黑客破解的密码的目的是盗取他人的数据。也就是黑客一旦侵入了mysql.user表格,那么他无需破解密码也就能盗取数据了。
MySQL采用上述密码保护机制有它的理由。其他系统如果也采用同样的机制,就要仔细考虑其中的安全隐患了。
一个系统的密码保护机制是否安全取决于两个方面,即密码传输的安全性和密码存储的安全性。新浪微博和MySQL都没有采用HTTPS/SSL传输密码。为了防止黑客在密码传输过程中窃听密码,它们只能在传输过程中加盐然后用SHA1算法加密。由于密码在传输过程中需要加盐,为了能够正常验证密码,因此在存储密码时只能存储没有加盐的SHA1算法的哈希值。因此密码的存储成为整个机制的短板。
笔者曾写过一篇博客讨论如何安全地存储密码。感兴趣的读者请参考http://blog.csdn.net/cadcisdhht/article/details/19282407。
我们没有必要在抛弃HTTPS/SSL的前提下试图去设计更加复杂的加密算法或者通讯协议。上述提到的两个方案是新浪微博和MySQL的程序员们花了大量精力设计出来的机制,尚且还有明显的漏洞。我不觉得每个程序员都有自信说自己比新浪微博或者MySQL的程序员更加优秀。
HTTPS/SSL在传输过程中用证书加密,不需要加盐来提高传输的安全性。这样在存储密码的时候就可以采用加盐的机制,比如简单地采用加盐的SHA1算法,也可以采用Bcrypt或者PBKDF2。如此以来,在密码的传输和存储两个环节都取得很好的安全性。
如果安全性对一个系统是至关重要的因素,那么就采用HTTPS/SSL吧。虽然部署HTTPS/SSL的系统有些麻烦,申请可信赖的CA的证书还要花钱,但和安全漏洞的潜在风险相比这些代价还是值得的。