我不知道的HTTP(04)— HTTPS握手:TLS到底做了什么
很多人以为 HTTPS 就是"给 HTTP 加了把锁",地址栏多一个小锁图标,仅此而已。但实际上,HTTPS 在 HTTP 和 TCP 之间插入了一整层协议——TLS,它独立解决了三个完全不同的安全问题:机密性(数据不被偷看)、完整性(数据不被篡改)、身份认证(对方不是冒充的)…
一、问题引入
很多人以为 HTTPS 就是”给 HTTP 加了把锁”,地址栏多一个小锁图标,仅此而已。但实际上,HTTPS 在 HTTP 和 TCP 之间插入了一整层协议——TLS,它独立解决了三个完全不同的安全问题:机密性(数据不被偷看)、完整性(数据不被篡改)、身份认证(对方不是冒充的)。这三件事缺任何一个,所谓的”安全”都是纸糊的。
二、表面认知:大多数人停在这里
大多数开发者对 HTTPS 的理解停留在”加密传输”四个字。浏览器和服务器之间的数据被加密了,中间人看不到明文,安全问题就算解决了。
这个理解只覆盖了三分之一。假设一个场景:数据确实被加密了,但攻击者在传输途中悄悄改了几个字节——接收方没有任何手段发现数据已经被篡改。再假设另一个场景:浏览器和一个”服务器”建立了加密连接,全程密文传输,看起来很安全——但那个”服务器”其实是攻击者伪装的。
换句话说,“加密”和”安全”的根本区别在于:加密只解决了保密问题,安全还需要完整性校验和身份认证。TLS 做的事情,是同时搞定这三件。
三、往下挖:TLS 握手到底发生了什么
3.1 为什么需要握手
TCP 连接建立后,浏览器和服务器之间还不能直接发 HTTP 数据。双方需要先回答几个问题:用什么算法加密?密钥是什么?你是不是你声称的那个服务器?
TLS 握手就是解决这些问题的过程。握手完成后,双方会持有一个相同的”会话密钥”,后续所有 HTTP 数据都用这个密钥做对称加密。
3.2 混合加密:为什么不全程用一种加密
这里有一个很多人会忽略的细节——TLS 并不是全程用同一种加密方式,而是混合使用了对称加密和非对称加密。
对称加密(如 AES)速度快,但有一个致命问题:密钥怎么安全地交到对方手里?如果密钥本身在网络上明文传输,加密就毫无意义。非对称加密(如 RSA、ECDHE)能解决密钥交换问题,但它的速度比对称加密慢几个数量级,不可能用来加密全部通信数据。
说白了,混合加密就是各取所长:用非对称加密安全地交换密钥,然后用对称加密高效地传输数据。TLS 握手的核心任务,就是完成这个”密钥交换”的过程。
3.3 TLS 1.3 的 1-RTT 握手
TLS 1.3 的握手只需要一次网络往返(1-RTT)就能完成。这比 TLS 1.2 的两次往返快了整整一个 RTT——在典型网络环境下大约节省 30-50ms。
问题的关键在于——TLS 1.3 之所以能做到 1-RTT,是因为客户端在 Client Hello 消息中就直接携带了密钥材料(Key Share),不再等服务器先确认算法再发。
具体过程分两步。
Client Hello:客户端发送支持的加密算法列表、一个随机数(Client Random),以及基于 ECDHE 算法生成的临时公钥。这里的关键是”临时”二字——每次握手都生成全新的密钥对,用完即丢。
Server Hello + 证书 + Finished:服务器选定加密算法,返回自己的随机数(Server Random)、自己的临时公钥、服务器证书,以及一条加密的 Finished 消息。服务器用客户端的临时公钥和自己的临时私钥,通过 ECDHE 算法计算出共享密钥,再结合双方的随机数派生出最终的会话密钥。
客户端收到响应后,用同样的方式计算出相同的会话密钥,验证证书和 Finished 消息。一个往返,握手完成。
对比 TLS 1.2 的流程:第一个 RTT 只是协商算法版本,第二个 RTT 才开始交换密钥。TLS 1.3 把算法协商和密钥交换合并到了同一个往返里,这就是 1-RTT 的原因。
3.4 前向保密:为什么历史通信无法被回溯解密
很多人以为密钥泄露只影响泄露之后的通信,但实际上,如果攻击者一直在录制加密流量,一旦拿到密钥,就能回溯解密所有历史记录。
这正是 TLS 1.3 强制使用 ECDHE 而淘汰 RSA 密钥交换的原因。在 RSA 方案中,客户端用服务器的公钥加密一个预主密钥发过去。服务器的私钥是长期不变的——一旦私钥泄露,攻击者可以解密所有用这个私钥保护过的历史会话。
ECDHE 则不同。每次握手都生成全新的临时密钥对,握手完成后临时私钥立即销毁。 即使服务器的长期私钥在未来某天泄露,攻击者也拿不到已经被销毁的临时私钥,历史通信仍然安全。
如果你只记住一句话,记住这个:前向保密的意思是”今天的密钥泄露,不会危及昨天的通信”。TLS 1.3 通过强制使用临时密钥实现了这一点。
3.5 证书验证:怎么证明服务器不是冒充的
加密和密钥交换解决了机密性问题,但还有一个问题没回答:浏览器怎么确认对面的服务器就是 example.com,而不是一个伪装者?
答案是证书体系。服务器在握手时会出示一张由 CA(证书颁发机构)签发的数字证书,包含域名、公钥和 CA 的数字签名。浏览器内置了一份受信任的根 CA 列表,验证过程是沿着证书链逐级向上追溯:服务器证书 → 中间 CA → 根 CA。只要链条完整且根 CA 在信任列表中,身份就算验证通过。
说白了,证书体系就是一套”担保链”——根 CA 为中间 CA 担保,中间 CA 为服务器担保,浏览器只需要信任根 CA 就够了。
3.6 用命令行观察真实的 TLS 握手
理解了原理之后,可以用 openssl s_client 命令实际观察一次 TLS 握手的全过程。
下面这条命令会与目标服务器建立 TLS 连接,并打印出握手的所有细节:
openssl s_client -connect www.google.com:443 -tls1_3 -msg 2>/dev/null | head -30
典型输出会包含这些关键信息:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Server certificate
subject: CN = www.google.com
issuer : C = US, O = Google Trust Services, CN = WR2
Verify return code: 0 (ok)
从输出中可以直接读到三个关键事实:协议版本是 TLS 1.3、对称加密算法是 AES-256-GCM、证书由 Google Trust Services 签发且验证通过。这比任何文档描述都直观。
在浏览器中也可以查看。打开 Chrome DevTools,切换到 Security 面板,点击”View certificate”,可以看到证书链的完整层级、有效期和签名算法。Network 面板中选中任意 HTTPS 请求,Timing 选项卡里的”SSL”时间段就是 TLS 握手的耗时——通常在 20-50ms 之间。
四、这意味着什么:HTTPS 真的更慢吗
这是一个流传很广的误解:“HTTPS 比 HTTP 慢很多,能用 HTTP 就别用 HTTPS。”
在 TLS 1.2 时代,这个说法有一定道理。每次新连接需要额外 2 个 RTT 完成握手,加上对称加密的 CPU 开销,确实能感知到延迟。但 TLS 1.3 把握手缩短到 1-RTT,会话恢复场景甚至可以做到 0-RTT(复用上次会话的密钥直接发送加密数据)。而现代 CPU 几乎都内置了 AES 硬件加速指令(AES-NI),加解密的 CPU 开销已经可以忽略不计。
可以算一笔账:一个 TLS 1.3 的新连接,握手增加的延迟大约是 30-50ms(一个 RTT)。而一次 DNS 查询加上 TCP 握手本身就需要 2 个 RTT。TLS 握手的开销占整个连接建立时间的三分之一左右,远没有到”慢很多”的程度。
更值得关注的是,HTTP/2 和 HTTP/3 都强制或事实上要求 HTTPS。不用 HTTPS,意味着也用不了这些新协议带来的性能提升——多路复用、头部压缩、0-RTT 连接恢复。说白了,HTTPS 在今天不是性能负担,而是性能优化的前提条件。
实际迁移到 HTTPS 也并不复杂。Let’s Encrypt 提供免费证书,Certbot 可以自动签发和续期。服务器配置好证书后,设置 Strict-Transport-Security 响应头(HSTS)强制所有流量走 HTTPS,再配合 301 重定向,迁移就基本完成了。唯一需要注意的是混合内容问题——页面本身是 HTTPS,但引用了 HTTP 的图片或脚本,浏览器会阻止加载或弹出警告。把所有资源链接改成 HTTPS 或协议相对路径(//)即可解决。
五、总结
TLS 不只是”加密”,它同时解决了机密性、完整性和身份认证三个问题。TLS 1.3 用 1-RTT 握手和强制前向保密,让安全性和性能不再矛盾。
本系列其他文章:
相关主题:
- 第 01 篇中的 TLS 握手环节在整个请求链路中的位置:从输入URL到页面呈现
- Cookie 的 Secure 标记如何与 HTTPS 配合:Cookie到底是怎么工作的