序:HTTPS更安全,为什么?
因为HTTP协议本身毫无安全性可言。
当你访问一个纯HTTP的网站(以及与这个网站有任何网络交互)时,你发出去一个请求。在这个请求到达网站服务器的路途上,不管是你家的路由器、你楼层的路由器、你小区的路由器、你当地电信的机房里,再一直到那个网站的服务器机房之间的所有网络设备上,都有你请求的数据通过。只要中间有任何一个设备想要把数据记录下来,它可以没有任何阻力的做到,因为这些数据是完全可见、没有经过任何混淆和加密的。
如果你发出的是一个注册请求,那这条链路上的每个网络设备都能看见你输入的账号密码;如果你发出的是一个转账请求,每个设备都能看见转账金额和目标。
那是不是只要大家都不在HTTP协议的网站上输入敏感信息,网络就变得安全了呢?
不是。除了你的请求,还有网站的响应数据也完整地走了一遍这条链路,只是方向相反而已。从技术上说,你家买的电信宽带可以篡改网站响应给你的任何信息,假如知乎是个非HTTPS的站点,那运营商可以把上面一句话改成:「你家的电信宽带不能篡改网站的任何信息,他们超级安全」。当然,电信运营商们很良心的并没有篡改HTTP网站的全部内容,只是插了一些广告在网页里。
HTTPS则完全避免了以上的问题。
HTTPS相比HTTP,在请求前多了一个「握手」的环节。在握手时,你和你想访问的网站会交换一个密钥;握手完成后,你的请求先用密钥加密才会发出去,网站服务器的响应会先用密钥加密再传给你。由于整条链路上的节点拿到的数据都是加密过的,所以他们即无法分析出源数据的内容,也无法篡改这个加密过的数据(如果一个节点篡改了加密后的数据,你和服务器都没办法用密钥解密出来,会认为数据是无效的)。
大多数人对HTTPS的了解仅止于此。当我仅仅了解这些知识的时候,反而有了更多的困惑:握手环节交换的密钥难道不会被链路上的节点知道吗?既然我和网站都能用相同的密钥解密对方的数据,为什么链路上的节点不能用我们交换的密钥解密一下数据呢?
要解释这些问题,我们需要先知道一些数学和密码学的原理。
一:非对称加密
很难想象,数学作为所有学科的支撑理论,早已经发展到非常复杂的程度,但是目前HTTPS使用的加密方法的数学思想和数学原理,是直到1974年和1977年才出现的。这种思想就是非对称加密。
与非对称加密相对的是对称加密。我们平常能想到的加密方法基本都是对称加密:按一定的规则给数据的每个字节(甚至每个比特)进行替换;按一定的规则打乱数据的排列;按一定的规则循环和组合上面的步骤。
为什么都要按照一定的规则?为了记住规则,然后将规则逆向,才能解密出我们加密过的数据。
这个规则,就等于一个加密算法加上一个密钥。你用这种算法A加上这个密钥S加密出来的数据,可以被我使用同样的算法A(逆着跑一遍)加密钥S解密出来,反之同理。这种加密方式被叫做对称的。
当你想和我用对称加密来传输一段文字,我们首先要解决的就是密钥的交换问题,只有保证了密钥的安全性,后面的通信才是安全的。在互联网上,我和你素未谋面,你想要把密钥发给我,却发现这条网络链路上的每一个节点都不可信。这成为了一个无法解决的问题。
直到非对称加密思想的出现。
非对称加密思想描绘了这样的美好场景:你的手上有两个密钥(一对密钥),它们有一定的关联,但没有办法通过其中一个算出另外一个。你把一个密钥紧紧地攥在手里,永远不向别人公布(私钥);把另外一个发送给我,当然,发送给我的途中,所有的设备都知道了这个密钥(公钥)。之后我用公钥加密了数据,并发送给你,你却可以奇迹般地用私钥解密它。更神奇的是,中间所有的设备,居然都不能用公钥解开它!
这怎么可能呢?
二:数学魔术
小时候我的同学小明给我表演过这样的魔术:让我任意想一个三位数,然后把这个三位数乘以91,最后把乘积的末尾三位告诉他。我想到了123这个数,乘以91得到了11193,接着我去掉两位只保留后三位,把193告诉了他。结果他很快地说出我心里想的数是123。
这个魔术的解法其实很简单:小明知道我的结果193后,再用193乘以11,得到了2123。而2123的末尾三位数,就是我的想的123。
原理也很简单,因为91乘以11等于1001,而任意一个三位数乘以1001,乘积的末尾三位一定等于它本身。我在进行了123 * 91操作后,小明进行了乘以11操作,即整个步骤为123 * 91 * 11 == 123 * 1001。而中间有一次去掉前两位仅保留后三位的操作(除以1000取余),看似丢失了信息量,实际上对结果毫无影响。其中的数学原理是:如果最后要对乘积取余,那么在事先对乘数取余不会对结果造成影响。
而我的同学小张即使听到了小明告诉我:把心里想的三位数乘以91,和我告诉小明:结果末尾三位是193,也没办法解开我心里想的数。
到这里,这个魔术的步骤已经几乎满足了非对称加密的全部要素!
当然,如今HTTPS正在使用的非对称加密算法并不是上面这个魔术。
假如小张长大了,考上大学的小张会知道上面得到91和193后,要做的其实是解一个方程:91 * x mod 1000 = 193。有这样一个原理:ab互质,且a * x mod b = c,那么a * n * x mod b = n * c。这时他迅速地算出91 * 11 mod 1000 = 1,那么91 * 11 * 193 mod 1000 = 193。就是说11 * 193 mod 1000 = 123,一定等于我想的三位数。
上面这个魔术已经很接近想要的答案了。失败之处在于,通过91(公钥)还是可以很容易的得到11(私钥)和加密信息的解。
但是,只要这个解法的难度足够大,让全班同学都无法解出来,这个加密方法就可以很放心的使用了。
三:RSA算法
上面的数学魔术向我们展示了非对称加密的一些要素:
- 运算过程是单向的:我们对123做了两次乘法再取余操作(如果是对称加密,对应的运算则是加密是一次乘法,解密是一次除法)
- 运算结果有其周期性:一个三位数不管乘以多少次1001,最后结果的末尾三位一定是它本身
- 加密的结果无法用公钥解开:加密是123乘以91取1000的余数得193,这时丢失了信息且无法直接用91还原
- 破解难度大:全班同学都无法解开这段信息(如果不会辗转相除法)
在1977年出现,并在2002年让三位作者获得图灵奖的RSA算法就第一次真正做到了这些。我简单介绍一下其数学原理。
首先,我们得到两个质数,比如是13和17,并求得n和m。
通过两个质数得到的n和m,有这么一个结论,任意数a的i次方除以n取余数,会呈现一个长度为m的周期。
如上图,a的1, 193, 385…次方,除以221的余数结果都相同。
这个周期就是我们想要的。假如我们能得到一个e和一个d,使得e * d mod m = 1(就是说e * d等于193, 185, 577…),那么我们的单向运算过程就设计完成了:待加密的数据是a,则加密好的数据是a的e次方;解密时将a的e次方再做一个d次方运算,结果就是a的e * d次方,而它除以n取余就等于a本身。而且在算出a的e次方后,对它先进行一次除以221取余并不会对结果造成影响,所以实际上这次取余后的结果才是加密好的数据。
那问题就在于如何得到这样的e和d呢?
知道了m,要求e * d mod m = 1的一个解。只需要先找到一个e,使得e和m互质(这很容易,比如m是192,那显然比他小的第一个质数191就和它互质),那么这个方程一定有解。
现在,情况就变成了已知e和m,求e * d mod m = 1。而这个事情,上面上大学的小张已经做过了!
现在,假设我们找到了一个e是11,则d可以算出来35(e * d = 385)。这时我们把e, n公布出去(公钥),把d, n保留下来(私钥)。我们的待加密数据仍然是123,那么加密解密过程就是下图。
其安全性在于,知道公钥(e, n)的情况下,想要得到私钥(d, n),只需要解出d即可。而解出d需要e * d mod m = 1这个小张的方程。但这一次,小张不知道m是多少了。想要知道m,他必须通过n(也就是221)来分解出13和17。即这个的破解难度等同于因式分解一个整数的难度。
事实就是,现在人类的科技水平根本没有办法分解出一个2000位左右的大整数(整数分解难题)。所以,只要n够大,这套体系就足够安全。我们可以很快的生成两个1000位的质数,然后把它们乘起来,但是没有办法分解它。
四:握手
终于,我们实现了非对称的加密这个遥不可及的梦。
但是我们发现,它的安全性依赖了很大的整数。所以当n很大时,e和d也可能会很大。假如服务器要传输给我们的是一整张网页。使用非对称加密的方法加密时,这个包含数百k的数据被转换成一个巨大的整数,再对它做e次方的操作,这将是非常耗时的(或者把数据拆成很多小整数,但要做e次方操作都是很耗时的)。
所以HTTPS选择了握手时交换密钥的方案。
总的来说,握手过程中,服务器会发出一张证书(带着公钥),客户端用公钥加密了一段较短的数据S,并返回给服务器。服务器用私钥解开,拿到S。此时,握手步骤完成,S成为了一个被安全传输到对方手中的对称加密密钥。此后,服务器与我的请求响应,只需要用S作为密钥进行一次对称的加密就好。
到现在,我们的HTTPS即安全又快了。
五:中间人劫持
如果没有中间人劫持的话,这篇文章应该就结束了。
毕竟上面的方案看起来已经很完美了,但是事实就是,有这么一种攻击方案可以轻松的突破这一套体系:
- 你和服务器之间有一台邪恶的路由器M
- 当你给HTTPS网站的服务器发请求后,网站带着公钥P响应你
- 响应到达M,M拿到了P,但是并不把它交给你,而是自己伪造了一对公私钥MP和MS,并把MP给你
- 你拿到MP,以为是网站的公钥P,用它加密了S,再请求网站
- 请求到达M,M使用MS解开S,再用P加密S交给网站
没错,就这样,邪恶路由器得到了S。至此你和网站的通信不再安全。
六:证书体系
中间人能劫持成功的本质,还是因为链路是不安全的。所以没有被加密的握手过程一定会有这个问题。
而目前我们解决问题的思路,却是放在了公钥发放上,暂时绕开了这个问题。这就是证书体系(也是为什么你要去找证书签发机构花钱购买证书的原因)。
简单来说,一个HTTPS网站响应给我们的并不是一个公钥,而是证书。证书上包含了公钥,还包含了域名、签发机构、有效期、签名等等。
那证书的安全性怎么保证?为什么中间人不能做一个假证书?
因为这套证书体系已经根植于每一个操作系统里了。每一个操作系统里,都内置了数十张根证书,每个根证书都对应一个非常权威的证书签发机构。这些根证书上记录了各个机构的公钥。
当网站找证书机构购买了一份合法的证书时,网站申请的证书上的公钥、域名、有效期等信息会被计算一次hash,然后证书机构用它的私钥给这个hash加密一次。这个加密结果就是证书的签名。
当网站的合法HTTPS证书到达你的电脑上,这个证书上带有签发机构的信息(具体来说应该是一条证书链),你的浏览器会用这个签发机构对应的操作系统内置根证书上的公钥,去解开网站HTTPS证书的签名(还记得吗,由于运算结果有周期性,所以用私钥加密的信息可以用公钥解开)。发现签名解开的hash与证书信息内容的hash一致,就可以证明证书是合法的了。
那中间人能不能申请一个真的证书然后去做劫持呢?
通常来说,证书签发机构的审核非常严格,如果无法证明www.zhihu.com这个域名属于他,签发机构是不会给他签发一个知乎域名的证书的。而如果中间人用了其他域名的证书,浏览器会发现你请求的域名和返回的证书不一致,从而拒绝继续请求。除非你一定要点下面的「仍然继续」:
最后无奈地说,在破解了这么多数学上的难题后,HTTPS的安全性仍然保证在『证书签发机构一定都是很有良心的』这种脆弱的基础上。