简介
HTTPS协议 = HTTP协议 + SSL/TLS协议。主要目的是在不安全的传输层协议之上,建立一个可靠的传输信道,需要解决的问题有防止信息被窃听,认证通信对象。
具体而言,TCP+TLS解决的问题包括
-
消息的可靠传输
- 消息一定送达(送不到我应该会知道)
- 消息一定完整(到了一定是完整的)
- 消息一定有序(一定以我预期的顺序到达)
-
消息的安全传输
- 避免窃听(知道你们在聊什么)
- 避免篡改(就算我不知道你们在聊什么,我也要改动某些信息,让你们沟通产生歧义)
- 避免中间人攻击(不知道你们在聊什么,但是我当传话筒,却传递着假的消息)
- 避免重放攻击(不知道你们在聊什么,但是我一遍一遍重复某方的话)
接下来看看具体的实现。
主流程
- 客户端和服务器协商好对称加密算法、非对称加密算法、MAC算法、不重数(nonce) ;(完全明文)
- 客户端得到了服务器公钥;(服务器需要证明这个公钥是服务器的公钥)
- 客户端生成随机的,仅仅针对这次连接的主密钥(Master Secret) ,并用服务器公钥加密,传输给服务器;
- 服务器用服务器私钥对信息解密,安全地得到了主密钥(Master Secret) ;(不会被窃听,可能被修改,但被修改了客户端就会无法解密)
- 双方重新进行一次步骤1的通信,但是用主密钥(Master Secret) 进行加密。
- 双方使用主密钥(Master Secret) 对消息进行对称加密,并传输消息。
因为非对称加密计算量较大,整个通信过程只会用到一次非对称加密算法(主要是用来传输客户端生成的用于对称加密的随机数密钥)。后续内容的加密解密都是通过一开始约定好的对称加密算法进行的。
上述是概述,事实上对于每一步,都有那么点漏洞,在具体实现上有些补充的细节。我们下面一点点来讲:
认证服务器公钥
第一个小细节在于客户端得到服务器公钥这里。
服务器公钥的传输在原理上来说,明文也没问题,但是担心有人在中间当了服务器中介差不多就下面这种情况,同时获得了服务器公钥和主密钥(Master Secret) 。
对于客户端来说,它需要确认这个公钥是不是来自这个服务器的,确定之后再给出自己的密钥。这一步认证就交给一个认证中心(CA) 来完成。
-
认证中心签发数字签名
认证中心自己有一把私钥和一把公钥,每个游览器都会拥有各个认证中心的公钥。
开发者需要向认证中心提交自己的域名,认证中心先把服务器的域名、公钥、证书有效期等信息进行Hash,形成信息摘要(不可逆向),再用自己的密钥进行加密,形成数字签名。因为数字签名是由认证中心私钥生成的,其他人都无法生成数字签名,只能解析数字签名。
-
服务器发送数字证书
服务器的域名、公钥、证书有效期等信息+数字签名=数字证书
-
客户端验证数字证书
3.1 用认证中心的公钥解密数字签名得到信息摘要
3.2 对服务器的域名、公钥、证书有效期等信息进行Hash,形成信息摘要
3.3 对比两个信息摘要,如果一致,则说明这个公钥确实来自我要访问的服务器。
中间人攻击,可以修改明文的域名、公钥、证书有效期等信息,但是生成不了信息摘要,因此无法伪装目标服务器。
客户端除了验证信息摘要,还要验证数字证书中的域名是否就是访问的域名、域名是否过期等信息。
主密钥
第二个小细节是通过非对称加密交换主密钥(Master Secret) 这里。
事实上,在第三步中,客户端没有直接生成主密钥(Master Secret) ,而是生成了前主密钥(Pre-Master Secret) 。然后双方按照SSL/TLS的标准,根据前主密钥(Pre-Master Secret) 和不重数(nonce) (第一步中提到的),使用相同的算法生成主密钥(Master Secret) 。
然后双方之后的加密也不由主密钥(Master Secret) 进行加密。而是双方再按照SSl/TLS的标准,根据主密钥(Master Secret) ,使用相同的算法生成Ec、Es、Mc、Ms。
- Ec:加密客户端的报文的密钥。
- Es:加密服务器的报文的密钥。
- Mc:验证客户端报文的MAC密钥。
- Mc:验证服务器报文的MAC密钥。
MAC算法
到这里会产生另一个疑问,步骤1中的MAC算法,和刚刚提到的MAC密钥又是啥?有什么用?
首先解释一下几个概念:
- MAC(Message Authentication Code):用于验证数据完整性的code(一小段文本)
- MAC算法:生成MAC值的算法,本质上时某种散列函数(比如MD5、SHA1、SHA2、SHA3)的结果。
- MAC密钥:MAC算法中,散列的内容是 报文本身 + MAC密钥。
其次讲一讲为什么在有了可靠的密钥(Ec、Es)之后,还需要去保障数据的完整性(TCP和UDP不是都有16字节的校验值字段,会保证数据的完整性嘛?)
因为在https协议中,http建立在TLS协议之上,TLS协议建议在TCP协议之上;而TCP协议是一个面向连接的协议,在一个TCP协议中可以发起多次http通信。因此出现了一种攻击方式——中间人通过监听一次TCP连接中的http请求,然后重放这些http请求,达成重放攻击。当然不只是重放,还可以对这些http请求进行重新排序再发放。
因为这些http请求都是客户端加密的,其正确性没有问题,我们需要额外的手段来预防重放攻击。借鉴TCP的思路,客户端维护一个从0开始的序列号,对于每个http请求都+1。然后MAC算法的输入包括 数据 + 序列号 + MAC密钥。由此服务器可以通过MAC来验证发送数据的顺序。避免了过去发送的报文的重传。
这里有两个有意思的点
- 为什么客户端的序列号仅仅需要从0开始,不需要像TCP一样随机初始化?(和TCP初始化序列号的目的有关)
- 事实上服务器没有维护这个序列号,而且报文中也没有序列号,这是为什么?(和TCP提供的可靠传输有关)
不重数nonce
上面讲到了,我们使用序列号和MAC算法,来避免了在一个TCP+TLS连接中,通过重放报文来实现重放攻击。
那么我能不能重放整个TCP连接?既然你不让我以(0 0 1 1 2 2)的形式进行重放攻击,那么我按(0 1 2)(0 1 2)进行重放攻击,又如何?
事实上完全可以,而不重数就是为了解决这个问题而诞生的。不重数由客户端和服务器各自生成的不重数拼接而成。并通过不重数的参与来生成主密钥(Master Secret) 。由此避免了多次重放攻击。
验证握手
第三个细节是第5步,这一步的目的是什么?
因为第1步中,客户端和服务器的信息交流全是明文,中间人可以通过监听和替换,迫使双方使用安全性较弱的对称加密算法、非对称加密算法等;也可以篡改不重数。为了防止这种篡改,我们通过第5步来验证第1步是否被篡改。如果第5步得到的结果和第1步不同,则终止本次连接(有内鬼,终止交易!)。
分割报文
最后一个细节发生在第6步,传输消息的过程中。
由于我们对数据进行了加密,所以另一端需要对数据进行解密。如果一个报文(比如http响应)特别大,那么另一端就需要在获得完整报文之后才可以对数据进行解密。这带来了不可忽视的性能问题。TLS的解决方案是,TLS再对报文做了分割,分割成了多个记录。以记录为单位发送数据。
回顾我们在MAC算法中提到的序列号,事实上,这个序列号不是http报文的序列号,而是记录的序列号。
可能的攻击
重放攻击
上面讨论到了,一共有两种重放攻击。比如正常在一次TCP连接中,数据以(0 1 2)进行传输。中间人可以 以(0 0 1 1 2 2)进行重放攻击;也可以分成两次TCP连接,以(0 1 2)(0 1 2)进行重放攻击。
对于同一次TCP连接中,报文级别的重放,我们通过序列号来解决问题。
对于TCP级别的重放,我们通过不重数nonce来解决问题。
CSRF
很遗憾,https对于CSRF毫无办法。
关掉证书验证
比如一些计算机木马,或者某些抓包软件,会关掉计算机的认证公钥的过程。更多内容可以尝试阅读一下这里。
还有一些软件,甚至是网站,会伪造CA的证书,这也能达成一些攻击。
参考资料
《计算机网络自顶向下方法》第7版
Does HTTPS protect against CSRF attacks?