你好,我是谢友鹏。
安全是网络的重要职责之一。公网通信面临诸多安全挑战,如数据窃听、身份伪造、数据篡改以及恶意重放等风险。为了应对这些问题,TLS(传输层安全协议)提供了有效的解决方案。
这节课,我们将深入探讨TLS协议是如何通过加密、认证、防重放机制和数据完整性校验等多重安全手段,来确保数据在公网安全传输的。
SSL、TLS、HTTPS的关系
在正式讨论安全之前,我们先搞清楚SSL、TLS和HTTPS的关系。这几个词我们可能常常听到,但并没有深究。
首先,SSL(安全套接字层)最早用于保护网络通信,但由于存在一些安全隐患,它被TLS(传输层安全协议)替代。现在,SSL已经被淘汰,我们在课程中统一使用TLS来讲解。
如下图所示,TLS工作在传输层之上,为应用层协议套上一层数据安全的壳。

至于HTTPS,就是HTTP和TLS的结合。简单来说,HTTPS=HTTP+TLS。
对称加密
下面我们正式开始学习安全通信的内容。首先,加密能够防止数据被窃听。只要通信双方使用相同的密钥对发送的数据进行加解密,数据就无法被公网中的其他人窃听。这种方式叫做对称加密。
那么,问题来了——在正式开始数据通信之前,通信双方就使用的密钥达成一致呢?在回答这个问题之前,我们先来了解一下非对称加密。
非对称加密
非对称加密使用一对密钥——公钥和私钥。公钥可以公开给任何人,而私钥则必须严格保密。在非对称加密中,可以使用这对密钥中的任意一个进行加密,被加密的数据只能被另一个解密。这就使得非对称密钥有两个重要的应用场景:
第一个场景,公钥加密、私钥解密,用于加密数据。我画了个示意图,方便你理解。

如上图所示,接收者将自己的公钥公开,其他人可以用这个公钥加密发送给接收者的数据。由于只有接收者自己持有与公钥配对的私钥,只有接收者能够解密该数据,这样就实现了数据加密传输的功能。
第二个场景,私钥加密、公钥解密,用于身份认证。我同样画了个示意图,方便你理解。

如图所示,发送者公开自己的公钥,并使用私钥对数据加密后发送给接收者。如果接收者能够使用发送者的公钥解密数据,就能验证数据确实是由私钥持有者发出的,从而确保身份的真实性。
通过这样的方式,非对称加密避免了对称加密中需要提前共享密钥的烦恼。那么,难道直接使用非对称加密进行网络通信不就行了吗?其实并非如此,因为非对称加密的性能较低,效率无法满足大规模数据传输的需求。
主密钥的协商
那么有没有更好的办法呢?
基于公钥加密的密钥交换
我们可以结合对称加密的高性能和非对称加密无需提前共享密钥的优势。具体做法是,首先通过非对称加密算法协商一个主密钥,确保通信双方安全地达成一致。然后,使用对称加密算法以这个主密钥进行后续的数据加解密,从而兼顾了安全性和高效性。
我画个简图来带你理解这个流程。

如图所示,客户端生成一个用于双方通信的主密钥(ms,master secret),然后服务器生成自己的公私钥对,并将公钥发送给客户端。客户端用服务器的公钥对主密钥(ms)进行加密,得到加密后的密钥(ems,encrypted master secret),并将其发送给服务器。服务器使用自己的私钥解密ems,得到主密钥(ms)。之后,双方便可以基于共同的主密钥ms进行通信了。
看起来这个方案还不错,不过它仍然有缺陷。
虽然这种方式可以确保密钥交换的安全,但它容易受到重放攻击的威胁。重放攻击指的是攻击者录制了正常的握手报文,并将其重新发送给服务端。因为加密的主密钥(ms)是由客户端生成的,而与服务器的密钥无关,也与具体的会话无关,所以服务端无法识别这些消息是否是重复的,从而产生安全问题。
为了解决重放攻击的问题,TLS并不单纯依赖客户端生成的密钥来作为主密钥的唯一材料,而是引入了客户端和服务器各自生成的一个随机数。这样,主密钥的生成不仅与客户端的密钥相关,还与通信双方的随机数密切关联,这就能确保每次会话的唯一性,并有效防止重放攻击。
引入随机数后,密钥交换的流程就变成了后面这样。

如图所示,客户端首先生成一个用于双方通信的预主密钥(PMS,pre-master secret)以及一个用于本次会话的随机数(random1),并将该随机数通过 client hello报文发送给服务器。随后,服务器也生成一个随机数(random2)以及一对非对称密钥,并将随机数和公钥发送给客户端。
客户端使用服务器的公钥加密预主密钥(PMS),得到加密后的密钥(EPMS,encrypted pre-master secret),然后将其发送给服务器。服务器用自己的私钥解密EPMS,获得预主密钥(PMS)。至此,双方安全地得到了共同的预主密钥。
接下来,客户端和服务器使用相同的算法,将PMS、random1和random2作为输入,计算出相同的主密钥(MS)。然而,双方并不会直接使用主密钥进行通信,而是将主密钥进一步分解成加密密钥和认证密钥,用于加密和验证后续的通信。
在TLS 1.2版本之前(包括TLS 1.2版本),如果使用RSA这样的非对称加密算法进行密钥交换,密钥协商的基本流程就是前面说的这样。
基于公开数学运算的密钥交换
RSA等公钥加密的密钥交换方式,虽然解决了密钥交换问题,但存在一个缺点——如果私钥泄露,之前所有的加密数据都可以被解密。
这个问题称为前向保密性(Forward Secrecy,简称FS)。那么,有没有办法实现既安全又支持前向保密的密钥交换呢?
这时候 DH(Diffie-Hellman)算法应运而生。该算法的核心思想是,利用数学原理,通过公开的数学运算动态生成共享密钥。由于每次会话的共享密钥都是动态生成的,因此不存在前向保密性问题。
听起来似乎很神奇,公开的数学运算是如何生成密钥的呢?我们不妨探究一下DH算法的过程。

首先,通信双方各自生成一个随机数a,b作为自己的私钥。然后两边都基于公开的大素数 p 和基数 g进行数学运算,得到各自的公钥:

之后,他们将计算出的公钥进行交换,各自再用对方的公钥和自己的私有计算出共同的密钥:

我们把公钥的变量带入到公式中,就得到了后面的结果。

所以通过这种方式,通信双方能得到共同的密钥。
DH算法的安全性依赖于离散对数问题的难度。尽管攻击者知道公开的大素数 p 和基数 g,并能捕获双方交换的公钥,但从这些信息中计算出私钥是非常困难的。离散对数问题目前没有高效的求解算法,因此DH算法被认为是安全的。
另外,还有一个基于DH算法的椭圆曲线版本——ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)。相比传统的DH,ECDHE算法使用了椭圆曲线密码学来替代大素数。因此,在相同安全性级别下,能够使用更短的密钥,从而提高计算效率。该算法被广泛应用于TLS密钥协商。
认证
通信双方已经安全地协商出了共享密钥,但此时仍然没有验证通信者的身份。如果与攻击者建立了TLS会话,仍然会存在安全风险。
刚刚讲解密钥交换的时候,我们已经知道,使用非对称密钥对中的私有进行加密,然后将公钥公布出去,可以用于验证自己是这个密钥对的持有者。
那么现在只需要再想办法证明这个密钥对的持有者是合法的组织就可以了,这个证明需要权威机构作为背书,由此形成了公钥基础设施(PKI,Public Key Infrastructure)。
PKI
公钥基础设施(PKI,Public Key Infrastructure)是一个管理和支持数字证书、加密、公钥交换等相关技术的框架,其核心目的是确保公钥的合法性和可信性。PKI的基本构件是数字证书,它用于验证公钥的所有者身份。PKI的信任体系以证书授权中心(CA,Certificate Authority)为核心,CA负责签发和管理数字证书。
具体来说,CA的作用包括验证申请证书的实体(如个人或公司)的身份,确保其公钥属于该实体,并对非法证书进行吊销。CA通过对每个申请证书的公钥进行数字签名,生成数字证书,并将公钥嵌入其中。这样,信任关系就按照下图的方式建立起来了。

具体过程是,网络服务提供者向CA注册证书,注册时会提供自己的域名、公钥等信息。CA会对这些信息进行核实,确认无误后,使用CA的私钥对证书进行签名并发放给服务提供者。服务提供者将得到的数字证书部署到服务器上。
与此同时,CA会将其公钥预先嵌入到服务访问者的设备中(例如浏览器)。当访问者向服务提供者发起请求时,服务提供者会将证书发送给访问者。此时,服务访问者除了验证证书的有效性(如证书有效期和域名匹配)外,还可以使用CA的公钥验证证书内容的真实性,确保该证书已经通过CA的验证。
如果只有一级CA,那么风险相对较大,因为如果CA的私钥泄露,那么该CA签发的所有证书都会面临安全风险。为了解决这个问题,引入了中间CA。最顶级的CA被称为根CA(Root CA),它负责为中间CA签发证书,而中间CA则负责为服务提供者签发域名证书。
通过这种方式,就形成了一个层级化的信任体系。如果某个中间CA出现问题,根CA只需吊销该中间CA的证书,而不会影响到其他的证书,这样就能有效降低风险。
在这种层次化的认证体系下,当服务提供者响应认证请求时,需要将证书链(包括域名证书、中间CA证书、根CA证书)发送给服务请求方。请求方通过逐级验证,最终确认根证书的合法性,从而完成完整的身份认证过程。
实战1:观察TLS握手的证书链和预埋根CA
道理讲了一大堆,你是不是已经迫不及待,下面看看证书链究竟是什么样子了呢?让我们通过Chrome浏览器观察一下。
观察证书链
首先,我们打开chrome浏览器,按下F12,然后按照下图方式选择Security。

然后在浏览器中任意输入一个有效的https网址,比如https://example.com 然后再按回车。

之后,点击查看证书,详细信息,就可以观察到证书链和证书的内容了。

如上图所示,证书中包含了许多关键信息,用于验证证书持有者的身份和确保通信的安全性。其中,域名信息(通过 CN 字段标识)、有效期、颁发者信息,以及签名值(上级证书用其私钥对该证书内容生成的哈希值加密所得)是核心的验证信息。这些内容共同保证了证书的真实性和可信度,使得访问者能够确信该证书确实由可信的上级机构签发,并未被篡改。
此外,证书中还包含了持有者的公钥。其他设备可以使用该公钥加密数据并发送给证书持有者,以实现数据的加密传输。同时,其他设备也可以利用公钥解密由证书持有者使用私钥加密的数据,从而验证证书持有者的身份。
最后,还能看到指纹,不过它并不是证书的一部分,而是任何工具都能通过SHA-256来对整个证书和公钥做哈希运算得到的结果,用来唯一标识一个证书。
验证者收到证书链后,可以验证证书的有效性,并识别其上级证书。接着,验证者会对上级证书进行相同的验证,直到验证到根证书为止。
观察预埋根ca证书
现在我们找一下刚刚请求得到证书链的根CA证书在哪里。
首先,在Chrome浏览器输入下面的内容,然后回车。
chrome://settings/security
接着点开管理证书。

之后在Chrome 根存储区,搜索根证书的名字,就找到该根CA证书了。

然后我们点开查看根CA证书的内容。

用此根CA验证证书链的最后一级中间证书后,就打通了整个信任关系。
完整性校验
学习完身份认证的内容后,我们接下来聊聊完整性校验的问题。
虽然我们可以通过对整个报文计算哈希值,然后将哈希值发送给接收者以进行完整性校验,但由于TCP是流式的,我们通常不希望等到所有数据都传输完成后才进行完整性校验。因此,TLS协议会在每个TLS报文中添加HMAC(Hash-based Message Authentication Code),以确保每次传输的数据没有被篡改。

报文的接受者收到如上图所示格式的TLS报文后,就会计算接收到的报文内容的哈希值,并和报文中携带的HMAC值进行比较,就可以验证报文的完整性了。
实战2:观察TLS握手过程
下一个实战内容是观察TLS握手过程。
tls1.2 rsa 算法握手过程
我们先观察tls1.2用rsa协商的过程。我们使用下面的命令行,触发使用rsa算法协商密钥的TLS握手,并抓取交互报文。
#抓包
$ sudo tcpdump host example.com -w tls1_2.pcap
#触发最高使用tls1.2版本协商,并制定使用rsa算法,--no-sessionid防止session复用。
$ curl -vo /dev/null --tls-max 1.2 --ciphers 'RSA' --no-sessionid https://example.com
然后,用Wireshark打开 tls1_2.pcap,查看报文交互过程。我们这就详细看看这个过程。
首先是第一步,客户端发送client hello,里面包含版本号、客户端生成的随机数、客户端支持的加密套件(加密、认证算法等信息)、SNI等信息。

接着是第二步,服务器回复server hello报文,里面包含协商好的版本号、加密套件以及服务器产生的随机数。

第三步,服务器发送证书链和server hello done,表示自己这边握手完成。

第四步,客户端收到证书链后会对其校验,验证通过后本地生成预主密钥(pre master secret),使用证书中的公钥对其加密后,发送给服务端。之后,用得到的pre master secret和前两步的随机数生成主密钥,进而得到加密和认证密钥。然后,通过Change Cipher Spec告知对端,自己后面的报文都是密文了。

第五步,服务器收到之后加密的预主密钥(pre master secret)后用私钥解密,然后用得到的pre master secret和前两步的随机数生成主密钥,进而得到加密和认证密钥。之后也通过Change Cipher Spec告知对端,自己后面的报文都是密文了。

至此,基于rsa的TLS握手完成,后续数据都在TLS保护下交互了。
tls 1.3 ECDHE 算法握手过程
如果你的测试设备和访问的网站都支持tls1.3的话,请求会默认使用tls1.3。而tls1.3中已经不支持rsa算法,所以看到的就是ECDHE算法协商密钥。因此使用下面的命令行,可以触发使用ECDHE算法协商密钥的TLS握手。
#抓包
$ sudo tcpdump host time.geekbang.org -w tls1_3.pcap
#使用默认协商的tls版本和密钥加密算法。如果最终没使用tls1.3,请先检查自己的curl版本是否支持tls1.3。
$ curl -vo /dev/null --no-sessionid https://time.geekbang.org/
然后,我们依然用Wireshark打开tls1_3.pcap,查看报文交互过程。
首先还是客户端发送client hello,注意这里除了版本号、客户端生成的随机数、客户端支持的加密套件(加密、认证算法等信息)、SNI等信息外,还多了key_share(ECDHE算法中的公钥)和支持的椭圆曲线算法。

接着,服务器回复server hello报文,里面包含协商好的版本号、加密套件以及服务器产生的随机数外,还多了服务器的key_share(ECDHE算法中的公钥)。至此握手就完成了直接回复Change Cipher Spec通知对端后面用密文交互了。

讲到这里你是不是有个疑问,tls1.3不需要验证证书链吗?
其实是需要的,只不过tls1.3的证书是加密的,所以在wireshark中看不到。
至此,TLS 1.3 的握手过程已完成。可以看出,由于使用了 ECDHE 算法,TLS 1.3 不再需要等待服务器公钥的传输,就能在 Client Hello 消息中传送密钥材料。这一改进使得 TLS 1.3 的握手流程相比 TLS 1.2 少了一个 RTT(Round Trip Time),只需 1 个 RTT 即可完成握手。
此外,TLS 1.3 还支持 0-RTT 握手,这一机制的原理可以通过查阅相关资料进一步学习。
小结
今天的内容就是这些,我给你准备了一个思维导图回顾要点。

今天我们学习了基于 TLS 在公网安全传输数据的原理。
首先,我们讨论了 TLS、SSL 和 HTTPS 之间的关系,以及密钥协商的原理,重点是如何基于公钥加密和公开数学运算两种方式来协商密钥。另外,我们也同时了解了 TLS 如何防止重放攻击。
随后,我们深入学习了认证的相关知识,包括 PKI 体系和证书链的认证过程。在此过程中,我们通过实战观察了请求中证书链的内容,并找到了 Chrome 浏览器预埋的根 CA 证书。之后,我们还简要了解了 TLS 的数据完整性校验机制。
最后,我们通过实战演练,观察了 TLS 的握手过程,包括 TLS 1.2 使用 RSA 算法的握手过程,以及 TLS 1.3 使用 ECDHE 算法的握手过程。建议你课后自己跟着课程练习一下,这样印象会更深刻。
思考题
1.一个证书可以给多个域名使用吗?
- 服务端可以验证客户端的身份吗?
欢迎你在留言区和我交流互动,如果这节课对你有启发,也推荐你分享给身边更多朋友。
精选留言
2025-06-04 15:08:27
2025-04-23 22:50:25
在证书申请过程中,申请者只需要向CA提交公钥,而私钥必须严格保密吧
2025-03-20 15:46:41
2025-03-19 12:04:04
(1)单域名SSL证书:只能为一个域名(主域名或二级域名)提供保护。
(2)多域名证书:可以保护多个不同的主域名(如 example.com、shop.net),也可以保护同一主域名的多个子域名(如 blog.example.com、api.example.com)。
(3)泛域名证书:可以保护一个域名和该域名所有子域名。
思考题2,服务端可以验证客户端的身份吗?可以。
一般来说,HTTPS通信过程中,最常见的是单向认证,即客户端(浏览器)验证服务器的身份,服务器并不验证客户端的身份。在某些高安全性要求的场景下,如银行交易、支付业务等,可以采用双向认证,不仅客户端会验证服务器的身份,服务器也会验证客户端的身份。