引言
为什么把这个作为选题。
大概也是2年前,我的同事,在面试某大厂遇到的问题与我一起探讨。这个时候我发现,虽然TLS(https)这个东西大部分时候可能不会被直接用到,但很容易被作为考察的目标范围。同时这方面的认知不同的人很容易出现不同的偏差。
问题内容概括来说就是URI/URL部分在https中是否会被加密。面试官认为url在https里是不会被加密的,这与事实是有偏差的(最开始只是知道他这里是有误的,没有思考这个地方之所以容易被误解的原因),后面会提到,为什么这个点容易出现理解偏差。
下文将着重分析说明HTTPS的安全性是如何被保证的。(本文会尽量避免直接引用名词含义解释,在说明过程中会重点描述协议与当前内容有直接相关的特性,部分内容会引用作者之前写的帖子或评论)
HTTPS用什么来保障传输安全
结论还是很简单的:我们说https的安全性是由TLS保障的
先问是不是,再问为什么!
HTTP与TLS
这张图展示了http各版本店基本数据传输结构。(该图引至bagder)
从HTTP 1 开始http就在利用TLS确保其安全性(事实上http 0.9就开始在使用SSL)。无论HTTP如何升级,承载其安全特性的组件TLS的地位始终没有被动摇。(从1995年TLS3 首次部署到现在已经超过20年)
值得一提的是作为最知名的传输层协议TCP,HTTP也计划在HTTP3中将其替换,不过其依然沿用最新版本的TLS保障其安全 (举这个例子并不是说TCP不优秀)
上图时间线简单展示了HTTP及SSL/TLS的关键时间线(http1.1 在1999年时有一次定稿,有的资料上会认为1999年是http1.1正式发布的时间点)。不难发现2者的发展一直保持微妙的同步,当然这与web爆发发展有关系,http最知名的版本http1.1 及 现在TLS的基础版本SSL 3 都出现在1996年前后,其影响也一直持续到了现在。
20年前的基础
数据来源 :https://almanac.httparchive.org/en/2021/http
上图是http各版本当前在web系统中的使用占比,不难看出 http 1.1 至今仍然被广泛使用,这个数字在2020年甚至还有50% (虽然统计中HTTP 2+ 超过了7成,但是并不代表这7成服务不支持HTTP 1.1,他们中大部分可以自动协商降级支持1.1)
而1996年被重新设计的SSL3.0 也一直延用至今(1999年发布的TLS1.0与SSL3.0 修改不大,以至于后面有的地方认为他们是同一个版本)
PS :HTTP与TSL是完全2个独立的协议,只是HTTPS利用TLS保证其安全性。
TLS其他应用范围
当然不能因为http一直与tls绑定,我们就相信tls在安全方面的能力。 (毕竟大家平时在引入三方资源时都会先确认用“他”的人多不多,大部分会优先信任被广泛使用的资源)
我们常用的SSH利用TLS保证连接的安全性,http的好朋友Websocket也在使用TLS,我们常见的VPN也在使用TLS确保我们的隐私安全。
这些基础事实是大家对TLS的认可,同时也表明TLS在安全传输方面的事实地位。
HTTPS/TLS如何确保数据传输安全
那TLS是如何保证其安全的,为什么其地位20多年都无法被撼动。
密钥交换
数据如何安全传输,当然第一时间就能想到“数据加密”(不过数据的传输安全远不仅如此,除了机密性,还需要有鉴别,数据完整性,和不可抵赖性,这些特性TLS都可以做到,不过不在本文讨论范围)
虽然简单加密是不够的,不过消息加密确实一切的基础。看起来并不难,服务器与客户端只需要有一个只有他们2方才知道的密钥,如何用这个密钥传输数据就可以了。
现在真正的问题来了,这个密钥要怎么才能做的只有客户端与服务器才知道,我们知道在互联网上任何数据包的传输都需要经过许许多多路由器等网络设备,其中任意一台设备都知道你的数据包里是什么内容,我们不可能通过网络直接把这个密钥发送给对方。
有一个方案其实很常见,密钥离线分发。比如常见的U盾,口令卡,银行系统用的分量密钥卡等等都是用的类似离线的手段保证密钥的安全。这种密钥交换的方式的确是安全了不过代价太大,对于广泛应用与互联网的http显然不合适。
公开密钥算法
不过幸好早在上个世纪70年年代就出现了公开的非对称加密算法(公开密钥算法,个人觉得这个名字更能体现这个密钥算法的特点),而且其安全性到现如今依然被证明有效(这也是为什么1995就已经问世的SSL3 可以作为TLS的基础一直延用至今的原因),借助于公开密钥算法使得密钥在客户端于服务器之间的安全传播成为可能。
TLS支持的密钥交换算法有许多(比如RSA密钥交换,Diffie-Hellman密钥交换.......)。我们看下RAS密码交换(因为他被广泛应用,且相对简单),看他是如何保证密钥交换的安全性的。
先介绍非对称加密算法的一个重要特点,加密时需要用到一对密钥(公钥,私钥),公钥加密的数据只能通过私钥解密,私钥加密的数据只能通过公钥解密。
- 通常我们说的对称加密基本上都是简单异或或异或的简单变种操作,这正好是计算机擅长的,所以速度非常快。(一般密钥实际上只是一种二进制数据块)
- 非对称加密算法是用的数学算法,无论是加解密,还是密钥对的生成消耗都比对称加密慢很多。(密钥实际上是一个数字,很大的数字)
密钥如何交换
前面我们已经提到了工作密钥是不可以直接在网络上传播的,既然密钥不能中网络中直接传播,那我们能不能把密钥加密后传输呢?非对称加密算法与这个需求完美契合。
服务端可以先将公钥直接发送给客户端,然后客户端自己生成一个随机密钥(TLS里这里交换的其实是预主密钥),然后使用公开的公钥加密这个密钥发送给服务器,因为私钥只有服务器知道,所以只有服务器才能解密这个密钥。这样一来就出现了一个只有客户端和服务器才知道的预主密钥,然后通过这个预主密钥可以生成主密钥,再最终生成工作密钥。
上图简单概括了预主密钥传输过程(其实就是TLS握手的其中一部分)
中间人攻击
看起来是不是很完美,就算密钥交换过程中的数据被其他人知道也没有关系,因为他们没有私钥,没有办法解密,不过还是有漏洞,你连接的网络设备可不是只能看你的数据,他还可以任意截断修改你的原始报文,这就是中间人攻击。
虽然客户端可以使用公钥加密预主密钥,不过客户端的公钥是从哪里来的呢,其实也还是通过网络由服务器下发给客户端的。那既然也是通过网络,在下发公钥的时候工作密钥可还没有生成,这就是说公钥可以被网络中的第三方截获然后替换,客户端以为收到的是服务器的公钥,而实际上收到的是中间人自己生成的公钥(中间人知道与之对应的私钥),这样一来客户端就会在毫不知情的情况下与中间人完成密钥交换(TLS握手),然后中间人会代替客户端与服务端交互,用户及服务端都不知道有中间人的操作,数据就会完全暴露给攻击者。
证书验证
公钥基础设施
现在问题就变成了客户端如何确认自己收到的公钥不是任何攻击者的证书。PKI(公钥基础设施)完美的解决了这个问题。
其实之前就有提到过当前最安全的手段其实就是密钥离线分发,而整个web系统网站这么多不太可能每个网站每个服务器都来给网站离线下发密钥,这个时候就需要一个中间商,一个大家都认可的中间商CA中心(证书签发机构)来帮我们完成服务端公钥的验证,虽然为每个网站离线分发公钥不太可能,不过我们可以提前为客户端离线分发受信任的根证书,利用这个根证书去验证网站服务端发客户端的公钥,这就是前面提到的PKI。
CA中心会提前将自己的根证书加入到操作系统的根证书列表(系统安装的时候就在那里了),许多浏览器也有自己受信根证书列表,这些证书都是内置的,网络嗅探者无法污染这些根证书。
那操作系统凭什么又如何要信任这些根证书呢? 这个就很直白了,刚刚说了是“权威”机构的根证书,你可以不信任,不过基于PKI的证书校验将无法运作,当然这些权威的证书颁发机构也也确实要能对得起他的权威。
现在客户端已经有了离线的信任根证书,那他是如何认证网站发过来的公钥呢(网站发过来实际上是证书,这个证书里包含公钥)
证书验证过程
client会校验证书的合法性,并根据验证结果决定是否连接服务器。(本节内容yin yo)
如上图在浏览器中任意找一个https的网页,服务器在建立TLS连接前都会先将自己的公钥证书发给客户端,我们查看其证书信息。
从这里面我们能看到证书包含以下内容:
(1) Validity也即有效期,有效期包含生效时间和失效时间,是一个时间区间;
(2) 公钥信息Subject Public Key Info,包括公钥的加密算法和公钥内容;
(3) 指纹信息,指纹用于验证证书的完整性,也是证书校验的关键,他确保证书没有被修改过。 其原理就是在颁发证书时,颁发者根据指纹算法(单向散列函数)(此处证书使用了SHA-1和SHA-256算法 有多个指纹是为了兼容老的客户端)计算整个证书的hash指纹【证书内容hash值使用CA私钥加密就是指纹】并和证书放在一起,client在打开证书时,自己也根据指纹算法计算一下证书的hash值,同时使用自己信任的根证书的公钥解密hash指纹计算出原始hash,如果hash值不一致,则表明证书内容被篡改过;
(4) 证书的签名Certificate Signature Value和Certificate Signature Algorithm,对证书签名所使用的Hash算法和Hash值;
(5) 签发该证书的CA机构Issuer;
(6) 该证书是签发给哪个组织/公司信息Subject;
(7) 证书版本Version、证书序列号Serial Number以及Extensions扩展信息等。
上图即是证书指纹校验的主要过程,不难看出Client校验证书的核心其实是CA公钥解密出原始指纹。那这个CA公钥从哪里来,这就是上文中提到的系统根证书。
这里就有一个关键点证书的颁发,我们现在知道根证书里的公钥是用来解密网站公钥证书的指纹的,为了确保安全系统会有一批自己信任的CA公钥列表(根证书),操作系统会提前内置好这些CA的根证书。这些根证书为什么可以验证网站的证书呢 ,因为网址的证书也是这些CA机构签发的。CA对应的一般是权威机构或组织,然后由这些权威机构颁发证书时使用他们自己的私钥去签名(为证书生成指纹)然后再颁发给网站,这样就确保了只有权威机构颁发给各个网站的证书才会被客户端校验通过。
网站证书的颁发
顺便我们看一下CA颁发给网站的证书具体是怎样的,一般有2部分组成
公钥证书:这个就是网站后面会公开发送给客户端的证书 (CA的私钥只会用来给这个证书签名,不关心证书里的密钥)
私钥文件:里面只有一个私钥(与公钥证书里公钥是一对,用来在TLS握手只解密预主密钥)
如上图sipv4.com.key就是私钥文件,sipv4.com.cer就是公钥证书 (如果看的细,会发现cer里不止含有一个CERTIFICATE,其实他是一个证书链)
上图简单描述了证书如何在TLS里发挥作用。有一点需要注意,CA再为网站颁发证书时会验证申请者是网站的拥有者,这样可以确保只有网站的拥有者才可能拥有合法的证书。这一通操作下来即使中间人用自己生成的公钥证书来欺骗客户端时,客户端也能马上反应过来从而中止链接。
现在我们再来看下攻击者的处境,攻击者如果伪装成网站A,握手时攻击者需要给用户发送公钥证书,攻击者有2个选择。
- 发送真正的证书(因为公钥证书是公开的,任何人都可以方便的获取),这样虽然可以通过客户端的证书校验,不过攻击者因为没有私钥,无法解密客户端发过来的预主密钥,握手会最终失败。
- 发送攻击者自己生成的公钥证书,这时虽然攻击者有对应的私钥,不过因为自己生成的证书没有CA的签名,无法通过客户端的证书验证,握手也会被终止。
这样一来密钥的交换,交换过程的安全就都得到了保障。
结束
url加密
现在回到开头,为什么有些同学会认为https不会加密URL。前面讲到的在密钥交换完成前数据流其实是明文,也就是说网站公钥证书的传输是以明文的形式进行的,上面也提到了证书里是包含Subject,而这个信息里往往有目标网站的域名,所以如果直接查看未解密的https流量,往往是可以看到域名的明文信息的,这个信息很有可能让人误以为这是url信息
为什么不使用公钥加密数据
还有一个问题也会经常会被提到,TLS为什么不使用公钥直接加密数据,这个问题也与开头的问题一样,很容易有认知上的偏差
看了很多被广泛接受的答案都是“性能”问题,我很早之前也是这么认为的,不过现在我知道“性能”问题相对于安全性上是否可行那就根本不是问题,不能使用公钥加密是因为安全上根本不可行,会让TLS形同虚设!
随便打开一个https网站的证书,不同电脑不同时间获取的证书里的公钥其实都是一样的,也就是说这个公钥是公开的而且任何人都知道,那就不可能自己用公钥去加解密。这样服务器返回的数据那不是任何人都能解密了
如上图任何人都知道baidu的公钥,任何人也都能解密baidu返回给客户端的数据。
当然TLS的握手是个十分严谨及复杂的过程,他还有很多认证的功能在里面,如果直接用公钥加密数据了那就抛弃了握手里面的很多功能,这里就不展开了。(因为就上面提到的所有返回数据都能被任何人解密那就是致命问题了,相比之下其他的问题都是小问题)
关于非对称加密算法应该加密什么数据
而不用公钥加密数据本身这是个策略,不仅仅是TLS 这些,绝大部分加密系统都是使用的这个策略,因为非对称加密加密的内容本质其实是数字(可以想象把1kb的数据转换成数字 会是个什么情景,当然他也不会允许你加密这么大的数据,还要特殊处理),并且还是数学运算,计算机处理起来会很慢(很多银行机构为了提高速度还会使用特殊的加密设备加密机/卡这类东西)。可以看到非对称算法天生就不是为加密长度不可控的数据设计的,他往往用来加密长度有限的关键数据,比如数据的hash值。而DES、AES这种对称算法就不一样,他们加密主要靠异或跟置换,大数据直接分组 ,这些操作计算机天生就很在行速度很快,他们对加密这些任意长度的数据流就很合适。
总结:
绝大部分加密系统都不会用非对称算法直接加解密数据(因为就不是为这种场景设计的)
而TLS就更不可能用公钥加密数据(因为除了上面提到的设计上的不合理,这种操作还让服务返回的数据失去了任何保护能力)
TLS地位为什么难以被动摇
是不是有必要,作为其基础的非对称加密算法目前为止依然十分十分可靠,而已也找不到更加合适的替代方案。
与其有直接关系的PKI体系已经形成,已经遍布全球CA中心并不是这么容易建立,因为需要大家互相信任,要推翻这个体系也并不容易。