HTTP系列-HTTPS篇
2022-11-16·15min
type
Post
summary
status
Published
category
tags
slug
date
Nov 16, 2022
password
icon
通常认为,一个通信过程具有以下四个特征,就可以被认为是安全的,分别是:
- 机密性
- 完整性
- 身份认证
- 不可否认
由于HTTP使用明文传输,上面四个特征一个都不符合,所以使用HTTP进行通信是十分不安全的,非常容易被窃听和篡改数据。
为了弥补HTTP不安全的缺点,HTTPS应运而生。
HTTPS是什么?
HTTPS它并非是一种新的协议,只是通信接口部分用SSL/TLS协议替代(在HTTP和TCP之间建立中间层)。
简单来说HTTPS就是HTTP + SSL/TLS的组合,利用SSL/TLS来实现加密通信,HTTP本身并不关注“加密”这件事。
SSL/TLS如何实现机密性?
对于一份数据,为它增加机密性最好的方式就是使用密钥将其加密,并只有持有对应密钥的人才能将其解密。
按照使用密钥的方式,加密方式可以分为两类:
- 对称加密
- 非对称加密
对称加密
简单来说,对称加密就是加密和解密时使用的密钥是相同的,是“对称”的。
所以只要保证了密钥只有通信双方持有,那么整个通信就是可以说是安全的。
但很可惜的是,由于无法保证建立通信时传递密钥不会被恶意捕获,客户端也不可能事先保管所有服务端的密钥,所以单纯使用对称加密来进行安全通信是行不通的。
非对称加密
非对称加密有两个密钥,一个叫“公钥”,一个叫“私钥”,两个密钥是不同的,是“不对称的”。公钥一般公开给任何人使用,私钥自己严格保管。
非对称加密具有单向性,两个密钥虽然都可以加密解密,但公钥加密后只能用私钥解密,私钥加密后也只能用公钥解密。
对于客户端/服务端而言,只要将私钥自己保管,交换各自的公钥,在发送数据前使用对方的公钥进行加密,接受数据时使用自己的密钥进行解密,就可以保证通信的机密性。
但可惜的是,对于用户来说,发出请求肯定是希望越早看到响应数据越好,而非对称加密非常耗时,虽然安全,但响应速度慢如蜗牛,所以也不能单独使用。
混合加密
那么,是不是能够结合对称加密和非对称加密,两者互相取长补短,既能高效加密解密,又能保证通信的安全。
这就是现在SSL/TLS里使用的混合加密方式,其实说起来也很简单:
在通信刚开始的时候,先使用非对称加密解决密钥交换的问题,然后就可以使用这把相同的密钥进行对称加密,建立安全的通信。
整个过程可以这么理解:
- 客户端向服务端发起请求;
- 服务端将自己的公钥A+ 返回给客户端;
- 客户端在本地生成一个密钥B,然后利用公钥A+ 对密钥B进行加密,加密之后传输给服务端;
- 服务端收到数据,利用自己的私钥A-对数据进行解密,拿到客户端生成的密钥B;
- 之后双方的通信,都用密钥B进行。
这样混合加密就解决了对称加密的密钥交换问题,而且安全和性能兼顾,完美地实现了通信的机密性。
SSL/TLS如何实现完整性?
混合加密完美地实现了通信的机密性,但仅有机密性,离安全还差的很远。
黑客虽然无法获取密钥,但可以通过捕获足够多的密文,再尝试对密文进行重组、修改,再发送给服务端。由于没有完整性保证,服务端只能“照单全收”,并返回响应数据。
黑客就可以通过分析响应数据,获取进一步的线索,最终就会破解出明文。
所以,在机密性的基础上还必须加上完整性,才能更加安全。
摘要算法
实现完整性的手段主要是摘要算法(Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)。
摘要算法可以简单理解为一种特殊的压缩算法,可以把任意长的数据压缩成固定长度,且独一无二的“摘要”字符串。
并且这个摘要字符串不可以恢复成原本的数据。如同现实中的指纹,相同的数据会被压缩成相同的摘要,如果数据被篡改过,摘要就会不同。
在机密性的基础上实现完整性
摘要算法保证了“数字摘要”和原数据是等价的。
在安全通信过程中,会在原数据后附上这个原数据的摘要,一起使用密钥进行加密。
收到数据后,接受方会先用密钥将整个数据进行解密,取出原数据和摘要。然后用摘要算法对原数据进行压缩,把压缩后的结果对比摘要,如果是一样的摘要,就证明数据是完整的。
SSL/TLS如何实现身份认证和不可否认
在前面如何实现机密性的章节里,简单介绍了一下通信的过程,如下:
- 客户端向服务端发起请求;
- 服务端将自己的公钥A+ 返回给客户端;
- 客户端在本地生成一个密钥B,然后利用公钥A+ 对密钥B进行加密,加密之后传输给服务端;
- 服务端收到数据,利用自己的私钥A-对数据进行解密,拿到客户端生成的密钥B;
- 之后双方的通信,都用密钥B进行。
那么这个过程有没有漏洞呢?其实是有的,下面来展示一招偷梁换柱:
- 客户端向服务端发起请求;
- 服务端响应请求,将自己的公钥A+返回;
- 中间人挟持到服务端的公钥A+,并给客户端返回了自己公钥B+。
- 客户端在本地生成密钥,并用公钥B+进行加密,之后发送出去。
- 中间人挟持到客户端发送出去的数据,由于这份数据是用公钥B+加密的,所以中间人可以用自己的私钥B-解密,取得客户端生成的密钥。
- 中间人伪造一份密钥,并用劫持到的公钥A+加密,发送给服务端。
- 服务端接受到数据,用自己的私钥A-解密,取得中间人伪造的密钥。
中间人通过挟持公钥A+的方式,”神不知鬼不觉“的拿到了密钥。也就是说,客户端和服务端之后的所有秘文,在中间人眼里都是明文了,因为它拿到了密钥。
在这整个过程中,最大的安全漏洞就是客户端没有确认公钥发送者的身份,“傻傻”的交出了自己的密钥。
为了防止这种情况,就需要关键的身份认证。
数字签名
在现实生活中,解决身份认证的手段是签名和印章,只要在文件上写下签名或者盖上印章,就能够证明这份文件是由本人而不是其他人发出的。
在SSL/TLS中,有一种东西和现实生活中的签名和印章很像,只能由本人持有,那就是非对称加密中的私钥。
使用私钥和摘要算法就可以实现独一无二的数字签名,同时实现“身份认证”和“不可否认”。
数字签名的原理其实很简单,就是发送者使用自己的私钥进行加密,接收方使用对应的公钥进行解密。
由于非对称算法加密效率低,所以私钥只对原文的摘要部分进行加密,这样运算量就小了很多,并且得到的数字签名也小,便于运输和解密。
数字签名和公钥一样完全公开,任何人都可以获取,但这个签名只有用私钥对应的公钥才能解开,拿到摘要后,再对比原文验证完整性,就可以证明消息确实是对方发的。
只要客户端和服务端互相交换公钥,就可以用“签名”和“验签”来确认对方的身份,通过之后,就可以使用混合加密来进行安全通信。
数字证书和CA
前面说到,客户端和服务端互相交换公钥,然后通过“签名”和“验签”的方式就可以确认对方的身份。
但这时新的问题产生了,由于谁都可以发布公钥,如何保证对方给的公钥不是伪造的呢?
信任问题真是一环套一环,永无止尽了。为了跳出这种“套娃”,只能引入“外力”了,找一个公认的可信任第三方,让它做担保,保证公钥是可信任的。
这个第三方就是所谓的CA(证书认证机构),CA会对公钥进行打包签名认证,包括该公钥的所属人,有效期,用途序列号等等,完整证明该公钥关联的各种信息,形成“数字证书”。
那么CA怎么证明自己呢?小一点的CA可以由大CA签名认证,Root CA只能自己证明自己了,这就是所谓的“自签名证书”或“根证书”。
操作系统和浏览器都内置了各大CA的根证书,上网的时候只要服务器发过来它的证书,就可以验证证书里的签名,顺着证书链一层层验证,直到找到根证书,就可以证明该证书是可信的,从而里面的公钥也是可信的。
SSL/TLS
SSL由网景公司发明,当发展到3.0时被标准化,改名为TLS,但由于历史的原因还是有很多人称之为SSL/TLS,或者直接简称为SSL。
当前主流版本是TLS1.2,2018年推出了更优秀的TSL1.3。
在HTTP协议里,建立TCP连接后会立即开始收发报文。但在HTTPS协议里,还需要在TCP上进行TLS握手,才会开始收发报文。
TLS握手有两个不同的过程,一个是传统的RSA握手,一个是现在主流的TLS1.2版本的ECDHE握手。
ECDHE握手过程
ECDHE是目前主流的TLS握手过程,具体过程如下:
- 建立TCP连接。
- 客户端发送“Client Hello”信息。包括TLS版本号、支持的加密套件列表以及一个随机数(Client Random)。
- 服务端回复“Server Hello”信息。包括确认的TLS版本号、选定的加密套件以及一个随机数(Server Random)。
- 服务端发送证书,里面包括公钥。
- 由于这里服务端选定了ECDHE这种加密算法,所以还会发送一个“Server Key Exchange”信息。包括ECDHE椭圆曲线的参数(Server Params),用来实现密钥加密。再加上自己的私钥签名认证。
- 服务器发送“Server Hello Done”信息,确认消息发送完毕。
- 客户端验证服务端证书。开始走证书链逐层验证,确认证书的真实性。确认有效后,再用证书里的公钥验证服务端的私钥签名,确认服务端的身份。
- 确认无误后,根据ECDHE加密算法的要求,客户端会生成ECDHE椭圆曲线的参数(Client Params)发送给服务端。
- 此时服务端和客户端都拿到了ECDHE加密算法需要的两个参数(Server Params、Client Params),就用ECDHE算法运算得到“Pre-Master”参数,其实也是一个随机数。这个运算过程很复杂,能保证就算之前的参数被截获,别人也算不出来。
- 此时客户端和服务端都有了三个随机数:Client Random、Server Random、Pre-Master,用这三个作为原材料来生成会话加密的主密钥(Master Secret)。主密钥也不是最终用于通信的,还会根据它来衍生出客户端和服务端用的会话密钥,避免只用一个密钥的安全隐患。
- 有了主密钥和派生的会话密钥后,客户端会把之前发送过的数据做个摘要,然后再用生成的会话密钥进行加密,发送给服务端进行验证,确认加密解密是否正确。服务端也是同样的操作。
- 双方都确认加密解密ok后,握手正式结束,可以开始收发加密后的HTTP报文。
RSA握手过程
RSA是传统的TLS握手过程,具体过程如下:
- 建立TCP连接。
- 客户端发送“Client Hello”信息。包括TLS版本号、支持的加密套件列表以及一个随机数(Client Random)。
- 服务端回复“Server Hello”信息。包括确认的TLS版本号、选定的加密套件以及一个随机数(Server Random)。
- 服务端发送证书,里面包括公钥。
- 服务器发送“Server Hello Done”信息,确认消息发送完毕。
- 客户端验证服务端证书。开始走证书链逐层验证,确认证书的真实性,拿到证书里的公钥。
- 确认之后客户端生成出“Pre-Master”参数,其实也是一个随机数,然后用证书里的公钥加密发送给服务端。
- 服务端接收后使用私钥解密拿到“Pre-Master”。
- 此时客户端和服务端都有了三个随机数:Client Random、Server Random、Pre-Master,用这三个作为原材料来生成会话加密的主密钥(Master Secret)。
- 有了主密钥后,客户端会把之前发送过的数据做个摘要,然后再用生成的会话密钥进行加密,发送给服务端进行验证,确认加密解密是否正确。服务端也是同样的操作。
- 双方都确认加密解密ok后,握手正式结束,可以开始收发加密后的HTTP报文。
RSA与ECDHE的区别
- ECDHE的“Pre-Master”是由随机数Client Params和Server Params结合算法算出来的,而RSA的“Pre-Master”由客户端直接生成。
- ECDHE握手在客户端发送完收尾消息后可以提前抢跑,直接发送HTTP报文。这也叫TLS False Start。
- RSA不具备前向安全性,而ECDHE有。
TLS1.3的改进点
相对于TLS1.2,新版本主要围绕着兼容、安全和性能三方面做了优化。
兼容
由于SSL/TLS1.2已经诞生很多年了,很多设备并不支持协议的升级。为了兼容性,SSL/TLS1.3不得不伪装成1.2版本,额外使用Extension扩展字段来增加自己的特性,包括新的1.3版本号。
如果旧设备不认识扩展的字段,就会忽略,从而实现向后兼容。
安全
SSL/TLS1.3修补了1.2版本中的漏洞和算法的弱点,废除了很多的加密算法,只保留了5个加密套件。其中最主要的是废弃了RSA。
由于RSA不具有“前向安全”,也就是如果服务端的私钥泄露或被破解,客户端生成的“Pre-Master”就会被截获破解,进而算出会话密钥。如果有人一直在耐心收集所有的报文,就会通过这个密钥破解所有的密文。
而ECDHE算法在每次握手生成的“Pre-Master”都是随机的,不具有一致性,就算被破解,也只能破解一次的通信密文,不会对历史密文造成影响。
性能提升
SSL/TLS1.3的握手流程也进行了优化,如下:
- 建立TCP连接。
- 客户端发送“Client Hello”信息。包括TLS版本号、支持的加密套件列表、随机数(Client Random)以及算法所需的参数(Client Params)。
- 服务端回复“Server Hello”信息。包括确认的TLS版本号、选定的加密套件、随机数(Server Random)以及算法所需的参数(Server Params)。
- 此时客户端和服务端都拿到了四个共享信息:Client Random和Server Random、Client Params和Server Params。两边各自通过ECDHE算法算出“Pre-Master”,进而算出主密钥。
- 算出主密钥后,随即进入加密通信。
- 服务端将证书、私钥签名认证以及之前所有数据摘要等信息加密后发出。
- 客户端进行证书认证,确认无误后将之前所有数据摘要等信息加密后发出。
- 双方都确认加密解密ok后,握手正式结束,可以开始收发加密后的HTTP报文。
1.3版本通过扩展字段来携带生成“Pre-Master”所需的参数,减少了一次的消息往返,提升了性能。
HTTPS的性能优化
HTTPS的工作大抵可以分为两个阶段:
- 非对称加密的握手阶段
- 对称加密的传输阶段
传输阶段的性能损耗几乎可以忽略不计,通常HTTPS连接慢都是由于握手阶段导致的,所以对于HTTPS的性能优化基本也是针对握手阶段。
硬件优化
HTTPS的连接是计算密集型,而不是I/O密集型。所以选择更快的CPU会有利于加速握手。
软件优化
由于软件在更新版本的时候都会做性能优化、修复错误,所以对于正在使用的软件,尽量升级到最新版本。
协议优化
尽量使用SSL/TLS1.3版本,它大幅度简化了握手过程,完全握手只需要一次消息往返,并且更加安全。
证书优化
由于客户端验证证书会消耗极大成本,所以服务端设置“OCSP Stapling”(OCSP 装订),让服务端预先访问CA获取OCSP响应,然后在握手时随着证书一起发给客户端,可以免去客户端连接CA服务器查询的时间。
会话复用
HTTPS建立连接的重点就是算出主密钥,如果把主密钥缓存起来重用,就可以大幅度优化握手成本。
会话复用方案主要有两种,分别是Session ID和Session Ticket。
Session ID
客户端和服务端在首次建立连接后各自保存一个会话的Session ID,内存里存储主密钥和其他相关信息。当客户端再次连接时将Session ID发送给服务端,服务端根据此ID在内存内查找,如果找到对应的ID号则直接用主密钥恢复对话状态,跳过证书验证等阶段。
这是最早出现的会话复用技术,也是应用最广的,但也有缺点,由于服务器必须保存每一个客户端的会话数据,对于拥有百万、千万级别用户的网站来说存储量就成了大问题,加重了服务器的负担。
Session Ticket
类似Cookie,不同的是存储的责任由服务器转移到了客户端,服务器加密会话信息,用“New Session Ticket”消息发给客户端,让客户端保存。
重连的时,客户端使用扩展“session_ticket”发送“Ticket”而不是“Session ID”,服务器解密后验证有效期,就可以恢复会话,开始加密通信。
为何不是所有网站都用HTTPS?
- HTTPS的实施需要门槛,从证书的选择、购买,再到部署,传统模式下都比较耗时耗力。
- 普遍认为HTTPS会更慢一些,相比于HTTP的明文传输它的加密通信会消耗更多的CPU及内存资源。
- 购买证书需要开销
- 国内安全意识可能没那么强