你是不是也听过这么一句话:HTTP 不安全,上了生产环境一定要用 HTTPS。但你有没有认真想过,HTTPS 到底比 HTTP 多做了什么?"加密"二字背后,到底是哪几种加密方法在分工合作?浏览器地址栏那个小锁头,凭什么就能代表"安全"?中间人攻击又是什么,为什么加了证书以后黑客就骗不了你了?

这两年被大厂刷屏的"全站 HTTPS",可从来不只是把 http:// 换成 https:// 那么简单。这一篇加餐,我们就顺着"HTTP 为什么明文危险 → 怎么逐步逼近加密的正确答案 → 为什么离不开证书 → TLS 握手到底在握什么"这条线,把 HTTPS 的整个原理掰开揉碎。你不用有密码学基础,跟着我一步步来,读完你就彻底通了。

你应该有的知识准备

正式开始前,先给你三个"够用就好"的底子,都是我上节课讲过或你早有耳闻的东西:

  • HTTP 报文是明文传输的:请求行、响应行、各种头部、body,全部是裸的文本。这是理解"为什么要加密"的起点。
  • TCP 三次握手:HTTPS 建立加密通道之前,先要有一层可靠的传输连接(TCP)。等下看完整流程时,你会发现它是"先 TCP,再 TLS"。
  • 端口的概念:服务用端口区分,常见的 80、443 你会在这节课反复见到。

好,底子够了,我们开整。拿好一杯水,这一篇能把你对 HTTPS 的疑问一次性清理干净。

HTTPS 是什么:HTTP 上面套了一层"壳"

先给一个一句话定义:HTTPS(HTTP Secure,HTTP over TLS/SSL)也是应用层协议,它是在 HTTP 协议的基础上,引入了一层加密层(TLS/SSL)。

怎么理解这层"壳"?想象一下,HTTP 原本是裸着身子在网络上跑,谁路过都能看清它说的每一句话。HTTPS 就是给它套上了一件"加密外套",穿上之后,报文在网络上传输的是加密后的样子,抓包的人拿到手的是一堆看不懂的乱码,只有通信的双方才能脱下外套看到真实内容。

用一句业内常说的话概括:HTTPS = HTTP + TLS。可以画成下面这张协议栈:

+--------------------------------------------------+
|               HTTP(应用层协议,业务数据)              |
+--------------------------------------------------+
|            TLS / SSL(传输层安全协议,负责加密)         |
+--------------------------------------------------+
|            TCP(传输层,负责可靠传输)                  |
+--------------------------------------------------+
|              IP / 以太网(网络层与链路层)              |
+--------------------------------------------------+

注意,TLS 这一层插在 HTTP 和 TCP 之间。所以完整的 HTTPS 访问链路是:

DNS 解析域名 → TCP 三次握手 → TLS 握手 → 加密的 HTTP 传输 → TCP 挥手

这里有个非常关键的区分:TLS 并不只服务于 HTTP。HTTP、SMTP(邮件)、FTP 这些运行在应用层的协议,理论上都可以套上 TLS。你日常说的"SSL 证书""SSL 握手",其实指的是 TLS 协议,只是叫法还停留在历史习惯上罢了。关于 SSL 和 TLS 的区别,一句带过:SSL 是网景公司上世纪九十年代的产物,早已废弃;TLS 是它的继任者,由 IETF(国际互联网工程任务组)标准化,现在主流是 TLS 1.2 和 TLS 1.3,后面握手流程部分我们细讲。

那问题来了:HTTP 到底差在哪,非要套这么一层壳?

为什么要加密:运营商的"眼睛"与中间人攻击

前面说了,HTTP 的内容都是按文本方式明文传输的。明文(plaintext)——这个词你记一下——指的是没有经过任何加密处理、谁拿到都能直接读懂的原始数据。正因为是明文,传输过程中就非常容易被动手脚。

你自己回忆一个场景:下载一个手机 App。正常情况下,点"下载"按钮,手机上弹出的是这个 App 的下载链接。可如果你没走 HTTPS,运营商可以把你的响应报文悄悄篡改一下,最后弹给你的可能是某个浏览器的推广下载链接。这就是"运营商劫持"——下载链接被运营商网络设备"狸猫换太子"了。

为什么运营商的设备能做到?因为你的每一个网络数据包,几乎都要经过运营商的网络设备(路由器、交换机)。这些设备天然就能解析你的数据内容。更不用说,你的数据还可能经过公共 WiFi 热点、公司代理服务器等一系列物理节点。只要你是明文传输,任何一个节点上的设备,都能做到三件事:看(窃取内容)、改(篡改内容)、装(冒充对方)。

这三件事,正是 HTTPS 要解决的三个问题,对应着安全的三个目标:

风险你能看到的现象HTTPS 靠什么解决
窃听内容被偷看加密(机密性)
篡改内容被改动你无感知摘要/完整性校验
冒充对方是假的你还信了数字证书(身份认证)

顺便做一个小结:凡是经过网络传输、又无法确定它经手过哪些节点的明文数据,理论上都可能被劫持或篡改。这就是中间人攻击(Man-in-the-Middle Attack,简称 MITM)。所谓中间人,就是在通信双方之间"插了一脚",既能截获又能伪造报文的第三方。你要登录支付宝,黑客若刚好卡在中间,就可能看到你的账号、余额甚至支付密码——互联网上明文传输,确实是件高风险的事。这就是我们为什么要加密。

思考一下:为什么运营商会无聊到去改你的下载链接?回答请三连想:赚推广费(返利)、插自家广告(变现)、做流量调度。说到底,利益驱动。有利益的地方,就会有人伸手。而这恰恰是大厂们推动全站 HTTPS 的根本动力——不加密,你的钱、你的注意力都会在不知不觉中被"剪径"。

常见的加密方式:对称加密

任何加密,都离不开三个要素:明文、密文、密钥。

  • 明文(plaintext):要传输的原始信息。
  • 密文(ciphertext):明文经过变换后的结果,看不懂了。
  • 密钥(key):在加密、解密过程中起辅助作用的那份关键数据。密钥的"钥"正确读音是"yue(四声)",不过大家平时都习惯读成"yao(四声)",从流传度上来讲,读"yao"也不在少数,是个约定俗成的小知识。

加密就是把明文用一系列变换生成密文;解密就是把密文再还原成明文。给你讲个历史故事帮助记忆:清宫戏里,有人想谋害慈禧,权臣们要保持密谋。恭亲王奕䜣(yì xīn)给慈禧递的折子,表面写的是家常话,但套上一张挖了洞的纸,就能看到真正要表达的意思。这里的"折子全文字面上是家常话"就是涉及表里两层的玩法:真正要传达的明文是"当心肃顺、端华、戴恒"这几个权臣的名字,而那张挖了洞的纸就相当于密钥——没有它,你看到的就是普普通通的奏折。

加密解密发展到今天,已经是一门独立的学科——密码学,而它的奠基人之一,正是计算机科学祖师爷级的艾伦·麦席森·图灵。顺嘴说一句,图灵一生功勋卓著,二战后却被英国政府迫害,41 岁便英年早逝;后来计算机领域的最高荣誉"图灵奖"就是以他命名。

接着说对称加密。

对称加密(symmetric encryption),也叫单密钥加密,特征是:加密和解密用的是同一个密钥。同一个 key,既能用来把明文变成密文,也能把密文还原成明文。典型算法了解一下即可:DES、3DES、AES、Blowfish、RC2 等。它的特点是:算法公开、计算量小、加密速度快、加密效率高。一句话,快。

给你看一个最简单的对称加密直觉——按位异或(XOR)。假设明文 a = 1234,密钥 key = 8888。那么加密就是 a ^ key,得到密文 b = 9834。要还原,就对密文再异或一次 b ^ key = 1234。因为异或具有可逆性:(a ^ key) ^ key = a。

明文 1234 ^ 密钥 8888 = 密文 9834
密文 9834 ^ 密钥 8888 = 明文 1234   # 按摩:异或两次等价于还原

(对字符串也同理,因为每个字符都能表示成数字。)当然,这只是为了让你理解"同一个密钥能加也能解"的机制,HTTPS 真正用的对称算法是 AES 这类现代算法,不是这么简单的异或。

对称加密的最大优点:快,适合处理大量数据。 对称加密的最大痛点:密钥怎么安全地交给对方?如果密钥本身也要明文传输,一旦被截获,加密就形同虚设。这个问题,几乎是整篇的中心矛盾,我们下面会反复撞见它。

常见的加密方式:非对称加密

为了解决"密钥怎么安全传递"的问题,人们发明了非对称加密(asymmetric encryption)。

非对称加密需要两个不同的密钥:一个叫公钥(public key,公开谁都能拿),一个叫私钥(private key,只归自己保存)。两者是一对,由一个密钥加密的数据,只能用配对的另一个密钥解密。常见算法:RSA、DSA、ECDSA。它的特点是:算法复杂强、安全性高,但加密解密速度比对称加密慢得多。

非对称加密有两种用法方向,功能完全不同:

  • 公钥加密,私钥解密:谁拿公钥加密都行,但只有持有私钥的本人能解开。目的——保证内容安全(除了我谁也解不开)。
  • 私钥加密,公钥解密:只有拿私钥的人能"签",而任何拿到公钥的人都能验明这份密文确实出自私钥持有者。目的——证明身份、防冒充。

打个生活的比方,你体会一下:A 要给 B 送一份重要文件,但 B 人不在。于是 B 提前跟 A 说:"我桌上有把锁,你把文件放到盒子用锁锁上,回头我拿钥匙来开。" 在这套情景里,这把锁就相当于公钥,钥匙就是私钥。锁可以随便发给任何人(谁都能锁上,密码学里谁都能加密),但唯一能开锁的钥匙只有 B 有。这就是"公钥不怕泄露、私钥绝不能泄露"的原因。

注意,公钥和私钥的作用是"配对加解密",并不是"哪边更厉害"。它俩是数学上绑定的一对:一个负责加密,另一个负责解密;反过来用就是另一个方向的认证(签名),这点等到"数字签名"一节会很关键。

到这里你已经有直觉了:对称加密快但密钥难传;非对称加密能安全传密钥但慢。是否可以把两者组合起来——用慢但安全的非对称方式,把快但难传的对称密钥安全地送过去?这个想法,正是后面"方案四"的雏形,也是整篇的核心思路,先在心里记下这个念头。

数据摘要与数据指纹:哈希的妙用

对称加密解决"保密",可还有一个问题没解决:怎么确保证文没被悄悄改过?

这就轮到数据摘要(digest),也叫数字指纹登场了。它的基本原理是利用单向散列函数——也就是 Hash 函数(哈希函数)——对信息做运算,生成一串固定长度的数字摘要。关键字"数字指纹":就像每个人都有独一无二的指纹,一段数据的摘要就是这段数据的"指纹"。常见算法有 MD5、SHA1、SHA256、SHA512 等。

注意,摘要严格来说不是加密:因为它没有解密过程,你无法从摘要还原出原文。它更像是"给数据拍了一张照片"——从照片很难还原出本人,但变化一点点,照片差异就巨大。

以 MD5 为例,它有三大特点,务必记住:

  • 定长:无论输入的字符串多长,算出来的 MD5 值长度固定(常见 16 字节或 32 字节十六进制表示)。
  • 分散(雪崩效应):只要源字符串改动一个字符,得到的 MD5 值差别会非常大,完全没有规律。
  • 不可逆:从源字符串算 MD5 很容易,但从 MD5 反推源字符串,理论上是不可能的。

正因为"定长 + 分散 + 不可逆",我们才能近似认为:如果两个字符串的 MD5 值相同,那这两个字符串基本就是同一个;反之,只要值不同,内容就一定被改动过。

给你演示一下"改一个字符,指纹翻天覆地":

字符串 hello     → MD5:BC4B2A76B9719D91
字符串 hella     → MD5:BDBD6F9CF51F2FD8
                       ↑ 只改一个字符,摘要完全不同

所以,摘要(哈希)可以用来判断数据有没有被篡改。做法很朴素:把原文和它的哈希值一起发给对方,对方收到后对原文重新算一次哈希,两相比对,不同就说明被改过了。

看到这你可能已经想到一个破绽:万一黑客把原文改了,顺便把新的哈希值也重新算一遍、一起发过去呢? 对,如果哈希值也明着传,那它就失去了校验意义。这就带出了下一个知识点——数字签名。

数字签名:用私钥给摘要"盖章"

我们刚刚发现:光看哈希不够,因为黑客会把"内容 + 新哈希"一起换掉。那怎么办?把哈希值加密之后再传,让它不能被人随便重新计算。

于是有了数字签名(digital signature)。一句话:摘要经过加密,就得到数字签名。更准确地讲,签名要借助非对称加密的"私钥加密、公钥解密"这一方向:

CA(或签名方)用自己的私钥,对数据的摘要(hash)加密 → 得到数字签名
别人用它的公钥去解密这个签名 → 还原出原始摘要 → 与重新计算的摘要比对

为什么非要对哈希加密,而不是直接加密整个内容?两个原因:

  1. 缩小密文长度:RSA 这类非对称加密对数据长度敏感,加密内容越长越慢。只加密固定长度的摘要,速度快得多。
  2. 保住完整性契约:签名校验的核心,就是"我能解出你当初那个摘要,并且能对上内容"。内容稍有改动,摘要就对不上,立即露馅。

把这个思路套到证书校验上,就变成了判断"这张身份证是不是伪造的"的过程。我们后面"证书验证"一节会完整走一遍,这里你先建立"哈希 + 签名 = 防篡改 + 防伪造"的印象。

承上启下(这一节是理解链的关键,也是课件反复强调的):回到对称加密——"只给 HTTP 做对称加密,能解决通信安全问题吗?" ——不能,或者说远不够。因为它绕不开一个死结:要让通信双方共享同一个对称密钥,这个密钥本身总得先安全地送到对方手里;而密钥若要加密传输,又得先协商出"密钥的密钥"……这就成了"先有鸡还是先有蛋"的循环。换言之,对称加密解决了"加密速度"问题,却没给出"密钥如何安全分发"的答案——密钥分发不安全,整个加密就形同虚设。这正是后面非对称加密要补的位。

HTTPS 工作过程探究:四种方案,一步踩一个坑

既然要保证数据安全,就得加密。思路有很多,但所有方案本质上都逃不出两大类的排列组合:对称加密和非对称加密。下面我们用演进的方式,逐一把每种思路的坑踩一遍,这样你就理解为什么最终方案长那样。

方案一:只使用对称加密

如果通信双方各持有同一个密钥 X,而且没有别人知道,那这双方的通信安全当然是有保障的(除非密钥被破解)。引入对称加密之后,即使数据被截获,黑客不知道密钥是什么,也无法解密出真实内容。

但问题随之而来:服务器同一时刻服务的是成百上千个客户端。这些客户端之间的对称密钥绝不能共用同一个——如果大家都用一个密钥,那密钥太容易扩散,一个客户端被攻破,全网就裸奔了。于是服务器必须为每个客户端维护"谁对应哪个密钥"的一张关系表,这本身就是个非常麻烦的维护难题。

更理想的做法,是客户端和服务器在建立连接时,现场协商出本次用的密钥。但问题又来了:

如果直接明文传输密钥 →
黑客截获了密钥 →
后面的加密全部形同虚设 → 白干

于是密钥的传输也必须加密。可要给密钥做对称加密,又得先协商出一个"密钥的密钥"……这不就掉进"先有鸡还是先有蛋"的循环了吗?

结论:只靠对称加密,解决不了密钥安全分发的问题。 第一次踩坑。

方案二:只使用非对称加密

看对称加密栽在密钥分发上,你会想:那全用非对称加密好了。服务器先把公钥明文发给浏览器,之后浏览器给服务器发的数据都用这个公钥加密。因为只有服务器有对应的私钥能解,所以客户端→服务器这条信道貌似是安全的。

但服务器→浏览器这条路怎么办?

如果服务器用私钥加密数据发给浏览器,浏览器用公钥解——可这个公钥是最开始明文传给浏览器的,如果这个公钥在半路被中间人劫持了,中间人也能用它解开服务器全明传来的所有信息。

结论:非对称加密方向搞反了会泄密;而且非对称加密本身太慢。 第二次踩坑(方向 + 性能)。

方案三:双方都用非对称加密

干脆别纠结方向了,双方各持一对密钥:

  1. 服务端有公钥 S 和私钥 S';客户端有公钥 C 和私钥 C'。
  2. 通信双方先交换各自的公钥。
  3. 客户端发给服务端:用 S 加密 → 只有 S' 能解(只有服务器能读)。
  4. 服务端发给客户端:用 C 加密 → 只有 C' 能解(只有客户端能读)。

看起来两头都安全了。但代价是:

  • 效率太低:非对称加密算法复杂,加解密都慢,全程用它处理海量 HTTP 数据,性能扛不住。
  • 依旧有安全问题:怎么说?别急,等中间人那一节,你会发现无论方案二、三、四,全都栽在同一块石头上——客户端无法证明"我手里的公钥,确实是目标服务器给的"。

结论:双向非对称加密既不快,也绕不开身份认证的坑。 第三次踩坑。

方案四:非对称加密 + 对称加密(混合加密)

到这里,聪明的你一定想到了组合拳,这也是现代 HTTPS 的雏形思路:

  1. 服务端持有非对称密钥对(公钥 S、私钥 S')。
  2. 客户端发起 HTTPS 请求,获取服务端公钥 S。
  3. 客户端在本地随机生成一个对称加密密钥(我们暂时叫它"会话密钥"),用公钥 S 加密后发给服务器。
  4. 中间的网络设备没有私钥 S',即使截获了加密后的数据,也解不出里面的对称密钥。
  5. 服务器用私钥 S' 解密,还原出客户端发来的对称密钥;此后双方通信一律用这个对称密钥做对称加密。
  6. 因为只有客户端和服务器知道这个对称密钥,其他人即使截获也是乱码。

一句话:只在开始协商密钥这一小步用慢但不安全的非对称加密,把对称密钥安全送过去之后,全程改用快得多的对称加密。 又安全又快,看起来完美了。

但——方案二、三、四,全部还藏着同一个致命漏洞:如果中间人从"最开始握手"那一刻起就已经混进来了呢? 这正是下一节的主角。

中间人攻击:方案二三四共同的死穴

中间人攻击(Man-in-the-Middle Attack,简称 MITM 攻击):攻击者成功插在客户端和真实服务器之间,同时和两端各自建立"通信",而你毫不知觉。

我们用方案四的场景,把 MITM 完整演一遍,你立刻明白问题出在哪:

服务器(S)               中间人黑客(M)              客户端(C)
持有公钥S、私钥S'         持有公钥M、私钥M'
   |                          |                          |
   |    ①把公钥S明文发给C                               |
   |─────────────────────────┬──────────────────────────>
   |                          |  ②中途拦截,把公钥S存起来,
   |                          |    把报文里的公钥S偷换成公钥M,
   |                          |    再把伪造报文转发给C
   |                          |──────────────────────────>
   |                          |                          | ③C以为拿到的是
   |                          |                          |   服务器的公钥,
   |                          |                          |   生成对称密钥X,
   |                          |                          |   用公钥M加密X发出
   |                          |<──────────────────────────
   |                          | ④用私钥M'解出对称密钥X,
   |                          |    再用公钥S加密X转给服务器
   |<─────────────────────────┬──────────────────────────
   | ⑤用私钥S'解出对称密钥X                          |
   |════════ 此后双方用X对称加密通信 ════════           |
   |     但X早已被中间人掌握,窃听、篡改随意        |

看明白了吗?关键在第 ②步:中间人把服务器的公钥 S 给"狸猫换太子"成了自己的公钥 M,而客户端完全没有察觉。 于是客户端傻乎乎地用公钥 M 加密了对称密钥 X,中间人用自己私钥 M' 一下就解出来了。此后,中间人掌握了会话密钥 X,成了"隐形第三者"——数据在它面前,既是密文(它知道怎么解)又是明文(它能随时看和改)。

再来复盘一次这里"问题本质":

客户端没有办法确认,自己收到的这个装着公钥的报文,就是目标服务器本人发出来的。

只要这一步被攻破,后面换再强的加密算法都白搭——因为连钥匙都被人掉包了。方案二、三、四都死在这里,这几乎是所有基于"交换公钥"设计的共同死穴。

那怎么解决?答案早就写在前面了——用数字证书给服务器的公钥"验明正身"。

引入证书:CA 认证与数字证书

服务器在使用 HTTPS 之前,需要向一个权威第三方机构——CA(Certificate Authority,证书颁发机构)——申请一份数字证书(digital certificate)。数字证书里包含证书申请者的信息、公钥信息等。服务器把证书传给浏览器,浏览器从证书里拿出公钥即可。证书就像一张"电子身份证",用来证明"这个公钥确实属于这个服务器",从而确立公钥的权威性。

先看证书里面装了什么。你可以把证书理解成一个结构化的字符串,通常包含:

  • 证书发布机构(谁签发的,比如 Let's Encrypt、DigiCert 这类)
  • 证书有效期(从哪天到哪天)
  • 公钥(服务器自己的公钥)
  • 证书所有者(域名、组织名等身份信息)
  • 签名(CA 的私钥对证书内容的签名)
  • 以及证书链、签发算法等信息

关于申请证书,有两点值得你注意:

  1. 申请证书时,需要先在特定平台生成一对密钥(公钥 + 私钥)。这对密钥就是用来做网络通信加密和数字签名的。
  2. 其中公钥会随着 CSR 文件一起发给 CA 做权威认证;私钥由服务器自己保留,用来做后续的通信(主要是交换对称密钥)。CSR 全称 Certificate Signing Request,证书签名请求,你可以用 openssl req 之类工具或在线工具生成,不过一般真到要证书时,都是直接找证书服务商解决申请流程。

有了证书,最大的变化是:服务器不再直接发"我的公钥",而是发"我的公钥 + CA 给我的签名",对方可以拿去对质。

要折腾清楚证书为何可信,就得先明白 CA 是怎么给证书"盖章"的——这就是上一节铺垫的数字签名。

数字签名是怎么形成的

CA 也有自己的一对非对称密钥:私钥 A' 和公钥 A。它是这样给网站"盖章"的:

  1. CA 对服务器要申请的证书明文数据做 hash,生成一份数据摘要。
  2. CA 用自己的私钥 A'对这个摘要加密,得到数字签名 S。
  3. 原始证书明文 + 数字签名 S,合成一份完整的"数字证书",颁发给服务器。

请一定区分清楚:这里 CA 用的是"私钥加密、公钥验证"的方向(签名动作),跟你前面看到的"公钥加密、私钥解密"(保密动作)不是一回事。别把这两组密钥搞混。

证书明文 ──hash──▶ 摘要 ──CA私钥加密──▶ 数字签名S
证书明文 + 签名S ──────────────▶ 组成一份数字证书,颁发给服务器

客户端那边怎么校验?答案在操作系统里:操作系统内置了一批"受信任的 CA"根证书(里面含各 CA 的公钥)。浏览器校验证书时,用系统里保存的某个 CA 的公钥去验证这份证书的签名。

证书验证:客户端怎么识破假证书

新冠疫苗接种证明要扫码验真,证书也一样,浏览器拿到证书后要做一系列"验真"动作,缺一个都报不安全:

  • 判定证书有效期是否过期:过期或还没生效,直接不认。
  • 判定证书发布机构是否受信任:发证机构必须在你的系统/浏览器内置的"受信任 CA 列表"里;不在列表里的(比如某个自签证书),就提示"该证书不可信"。
  • 验证证书是否被篡改:这是核心。步骤是——从系统里拿出该 CA 的公钥,对证书上的签名解密,得到 CA 当初存的摘要(记为 hash1);再对整份证书内容重新计算哈希(记为 hash2);对比 hash1 和 hash2,相等则未被篡改,不等则说明被改过。
  • 实际浏览器还会校验域名是否匹配(证书里的域名跟你访问的域名一致)、是否被吊销(查 CRL/吊销列表)等。这块后文"边界与坑"里详说。

对应前面数字签名那节:认证的过程,其实就是在说"这张身份证是不是伪造的"。

那中间人能不能篡改这张证书?我们来逐个可能性地走一遍:

  • 中间人篡改证书明文? 他改了明文,但由于没有 CA 私钥,就没办法为新的明文生成匹配的签名。若强行篡改,客户端会立刻发现"解出的 hash1 与重算的 hash2 不一致",判定证书已被篡改、不可信,从而终止向服务器传输信息——反而保护了用户。所以,中间人没有 CA 私钥,就无法合法修改任何证书。

  • 中间人整个掉包证书? 他可以自己去向 CA 申请一张真证书,然后拿自己的证书把服务器的证书掉了包——但这份自己申请的证书的"证书所有者"字段写的是他自己的域名,而不是目标服务器(比如 example.com)。客户端校验域名时一对比就露馅了(你访问的是 example.com,证书却写着 hacker.com)。除非他真能骗过域名认证,但这在正常 CA 体系下极难。

一句话记住这节课的鸡肋点:永远记住——中间人没有 CA 私钥,所以他无法对任何证书做合法的修改,包括他自己那张。

思考一下:为什么中间人没有 CA 私钥,就无法制作一张能骗过浏览器的假证书? ——浏览器验真必经"用内置 CA 公钥验签"这一步。签名必须用 CA 私钥生成;中间人没有这个私钥,就产不出能通过验签的签名。而验签通过的签名是证书"内容没有被篡改"的唯一凭证。没有它,要么证书签名对不上(伪造失败),要么域名对不上(冒名失败)。认证体系的安全根基,就建立在"CA 私钥这个秘密只有 CA 有"之上。

好了,现在我们终于凑齐了整幅拼图。来,上重头戏——完整的 TLS 握手流程。

完整流程:TLS 握手时序图

TLS 握手(TLS Handshake) 是建立加密通道前,客户端和服务器为了让双方对"用什么算法、用谁的密钥、什么时候开始加密"达成一致而进行的一组报文交互。它的核心目的有三件事:

① 协商:双方敲定用的 TLS 版本、密码套件(算法组合)
② 密钥交换:在不安全的网络上,协商出一个只有彼此知道的对称会话密钥
③ 验证:客户端验证服务器身份(证书),防止中间人冒充

这里的"会话密钥(session key)"就是前面反复说的、每次连接临时协商出的那个对称加密密钥。它只对本次会话有效,会话结束即弃。这正是性能与安全的折中产物。

下面给你画一张完整的 HTTPS 访问时序图。先走一遍 TLS 1.2 经典 RSA 密钥交换方式(对应课件里"客户端生成对称密钥、用服务器公钥加密发过去"的讲法),它最容易理解原理;现代主流 ECDHE 的差异我们在后一节专门讲。

    浏览器(客户端)                              服务器(Server)         TCP:443
        │                                           │                     │
        │① TCP 三次握手                               │                     │
        │══════════════════════════════════════════>│                     │
        │                                           │                     │
        │② ClientHello                              │                     │
        │ 客户端随机数 cli_rand                      │                     │
        │ 支持的TLS版本、密码套件列表                  │                     │
        │──────────────────────────────────────────>│                     │
        │                                           │                     │
        │③ ServerHello                              │                     │
        │   服务端随机数 srv_rand、选定版本和套件      │                     │
        │④ Certificate(数字证书)                    │                     │
        │   = 公钥 + 域名 + 到期日 + CA签名等          │                     │
        │⑤ ServerHelloDone(服务端握手消息发完了)      │                     │
        │<──────────────────────────────────────────│                     │
        │                                           │                     │
        │⑥ 校验证书:                                 │                     │
        │   有效期/域名/受信任CA/签名是否匹配          │                     │
        │   验过→信任证书里的公钥                     │                     │
        │                                           │                     │
        │⑦ 生成随机 pre-master                       │                     │
        │⑧ ClientKeyExchange                        │                     │
        │   用证书中的公钥加密 pre-master 发给服务器    │                     │
        │──────────────────────────────────────────>│                     │
        │                                           │ ⑨ 用自己的私钥解出 pre-master
        │                                           │                     │
        │⑩ 双方各自用 pre-master +                  │⑩ 双方各自用 pre-master +
        │   cli_rand + srv_rand                     │   cli_rand + srv_rand
        │   算出同样的会话密钥(对称密钥)              │   算出同样的会话密钥(对称密钥)
        │                                           │                     │
        │⑪ ChangeCipherSpec + Finished              │                     │
        │   通知:后面开始用对称加密                    │                     │
        │──────────────────────────────────────────>│                     │
        │                                           │⑫ ChangeCipherSpec + Finished
        │<──────────────────────────────────────────│                     │
        │                                           │                     │
        │⑬ 密文 HTTP 请求 / 密文 HTTP 响应(对称加密)  │                     │
        │══════════════════════════════════════════>│                     │
        │<══════════════════════════════════════════│                     │

把整段流程翻译成人话,它其实是四件事:

  1. 打招呼(②③):客户端说"我能用这些版本和算法",服务器挑一个回话。双方顺手各报一个随机数。
  2. 亮证件(④):服务器把数字证书(含公钥)发来。
  3. 验身(⑥):客户端用内置的 CA 公钥验签,确认证书合法、没被改、域名匹配,于是敢相信证书里的公钥确实是这个服务器的。
  4. 交接钥匙(⑦⑧):客户端生成一段随机的 pre-master(预主密钥),用服务器的公钥加密发过去;服务器用私钥解出。接着双方用"pre-master + 两端随机数"各算一份完全相同的会话密钥——不,等等,严格说,客户端和服务器是各自独立算出来这个密钥,它们的计算结果一致,但双方从没直接传输过这个会话密钥本身。之后全部数据都用这个对称会话密钥加密。

到第⑬步,双方就进入加密传输阶段,地址栏的小锁头就亮了。

看了这张图,你会不会有个疑问:会话密钥是客户端发给服务器的,可它俩怎么做到"算出来一模一样"? 这是因为双方拥有相同的三个输入:pre-master(最终两边都拿到了)、cli_rand、srv_rand(随机数在报文明文里传过)。同一套算法、同一套输入,自然算出同一个结果。这就是 RSA 密钥交换的思路。

好,到这里你已经完全掌握了 HTTPS 的经典原理。接下来我们看看现代真实的互联网上,握手的另一套更主流的玩法。

从经典 RSA 到现代 ECDHE:前向保密

课件讲的经典 RSA 交换清晰易懂,但它有一个现代安全专家无法容忍的短板,我们得讲讲真实世界里怎么补。这也正是你要记住的"坑"之一。

RSA 密钥交换的隐患

还记得经典 RSA 流程里"客户端把 pre-master 用服务器公钥加密发过去"吗?它的风险是:加密通信用的是服务器的长期私钥。(长期)私钥一旦泄露,历史上所有记录过的加密流量都可能被回溯解密。

设想黑客今天截获了你全部的历史 HTTPS 数据并保存起来(这叫"记录流量"),某年某月服务器的私钥因为某个漏洞泄露了,黑客就能用这把私钥,把过去存下的所有 pre-master 都解出来,进而解出当时所有会话密钥、还原所有历史数据。这就是"加密不做前向保密"的代价。

ECDHE:每次会话都用"临时钥匙"

于是现代主流改用 ECDHE(临时椭圆曲线 Diffie-Hellman)密钥交换。它的大致思路是:

  • 客户端和服务端各生成一对临时密钥(每次握手都用全新的,用完即弃,不落长期存储)。
  • 双方不需要真的把"密钥"传过去,而是交换各自临时的公钥片段,通过 Diffie-Hellman 数学原理,在不传输密钥本身的前提下,各自独立算出同一个共享密钥。
  • 由于每把钥匙都是"一次性"的,即使将来服务器的长期私钥泄露,也无法破解已经录下的历史会话——因为那些会话用的临时密钥早就销毁了。这在密码学上叫前向保密(Forward Secrecy)。

一句话对比:

维度RSA 密钥交换(经典,多为 TLS 1.2 时代)ECDHE(现代主流,TLS 1.2/1.3)
会话密钥怎么定客户端生成 pre-master,用服务器公钥加密传输双方交换临时公钥,各自协商算出
是否用长期私钥加密是(长期私钥参与)否(临时密钥)
历史数据保护长期私钥泄露 → 历史全解密前向保密,历史不可回溯
握手往返TLS 1.2 下 2-RTTTLS 1.3 下 1-RTT

顺带说明:上面"经典 6 步 RSA 流程"对应的是 TLS 1.0/1.1/1.2 中的 RSA 密钥交换,便于理解原理;而现实中的主流浏览器、服务器基本都已在用 ECDHE(以及 TLS 1.3 强制 ECDHE)。微信、淘宝那些极小锁头,背后大多是 ECDHE。本段知识点(前向保密、1-RTT、强制 ECDHE)参考了 IETF RFC 8446(TLS 1.3)以及公开的 TLS 对比资料,如有兴趣可进一步查阅。

TLS 1.3:更快、更安全的握手

再看一眼 TLS 版本演进。SSL 早已废弃,TLS 1.2(2008 年定稿)和 TLS 1.3(2018 年定稿,见 RFC 8446)是当下主流。TLS 1.3 做了两大关键升级:

  1. 强制前向保密:删光了不合规的密钥交换方式(包括固定 RSA),只保留 ECDHE 这类临时密钥方案。
  2. 握手更省时间:TLS 1.2 完整握手要 2 次往返(2-RTT);TLS 1.3 把大步合并,一轮往返(1-RTT)就能完成,配合会话恢复甚至支持 0-RTT(首次访问省一程)。直观感受就是"打开网页更快了"。有实测对比,TLS 1.2 升到 1.3 握手时延大约能省一半的量级。

TLS 1.3 简并后的握手大概是:

ClientHello(把客户端共享的临时公钥一并带上)
   ▶
ServerHello(选定套件 + 服务端临时公钥)+ Certificate(证书链)+ Finished
   ▶ 客户端当场验签 + 算出会话密钥
   ▶ 用会话密钥加密"Finished"确认 + 直接发第一条加密的 HTTP 请求

相比 TLS 1.2 精简了许多消息类别,安全性也更省心。(依旧参考上节的资料。)

如果你在"socket 一层写个 HTTPS 客户端"的实验里看到"密码套件"这类字眼,比如 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,那么翻译一下:ECDHE 是密钥交换算法,RSA 是用于服务器身份认证的算法,AES_128_GCM 是对称加密算法,SHA256 是完整性校验用的哈希算法。看得懂这串"套件名",你对握手协商的理解就到位了。

三组密钥的总结:整个 HTTPS 就围着一件事转

课件最后有一个特别漂亮的总结——HTTPS 整个工作过程中,其实涉及三组密钥,它们的职责各不相同:

组加密类型作用持有方
第一组非对称校验证书是否被篡改,确立服务器公钥的权威性服务器持私钥(申请证书时生成),客户端持公钥(系统内置的受信任 CA 公钥)
第二组非对称协商出对称加密的会话密钥客户端用证书里的公钥加密会话密钥,服务器用私钥解密
第三组对称后续所有业务数据的加密传输客户端 + 服务器共享

再把这个表格倒过来看,你会发现一个惊人的"套娃"结构:第三组对称密钥负责真正跑业务;第二组非对称密钥是为了把它安全送到服务器;第一组非对称密钥是为了让你放心拿到第二组用到的公钥。

换句话说——一切的关键,都是那个对称会话密钥。 其他的机制,都是为了"把这个对称密钥安全地交到对方手里"而服务的。把这句看透,HTTPS 在你眼里就不再是一团浆糊。

边界与坑:这些细节很关键

这一节把初学者最容易踩、最常问的边界和坑集中讲清楚。它们不偏门,恰恰是你真正会用 HTTPS 时才见得到的东西。

坑一:为什么要"先非对称、后对称",而不是全用其中一种

这是全篇的核心问题,用几句话收束:

  • 只对称:快,但密钥无法安全分发,是最底层的死结。
  • 只非对称:能安全分发,但慢到无法承载海量数据。
  • 先非对称、后对称(混合加密):对策的精华——用慢且安全的非对称,把快且难传的对称密钥送到位;此后全程用对称。非对称只在握手那一小步出手,数据的绝大多数流量交给对称加密,既安全又高效。

所以答案是:对称加密负责"量大"的场景(加密数据),非对称加密负责"量小但关键"的场景(传密钥、验身份)。二者各司其职,缺一不可。

坑二:证书链验证——"谁是权威"也得一层层问

你有没有注意过,很多证书并不是权威根 CA 直接签的,而是"根 → 中间 → 服务器"这么一层层签出来的?这就是证书链(chain of trust)。

你的服务器证书(叶子证书 leaf)
   └── 由"中间 CA"签发(如 Let's Encrypt 的 R3)
        └── 由"根 CA"签发(如 ISRG Root X1)
             └── 根 CA 的公钥内置在操作系统/浏览器里(信任锚 trust anchor)

浏览器验证时,会沿着这条链往上走,逐级验证每一份证书的签名是否有效:

  1. 叶子证书的域名是否匹配当前访问的域名(CN/SAN 比对)。
  2. 叶子证书签名是否有效(由中间 CA 签发)。
  3. 中间 CA 的证书签名是否有效(由根 CA 签发)。
  4. 根 CA 是否在系统信任库中(trust anchor,OS 出厂预装)。
  5. 各级证书是否过期、是否被吊销。

为什么要这么麻烦、不直接用根签叶子?因为根 CA 私钥是全世界 PKI 体系(公开密钥基础设施)的命根子,它被锁在离线的高防硬件里。日常签发都用"中间 CA",这样一旦某个中间 CA 出了事,只需吊销它而不必动根——把风险隔离在局部。所以下次你再撞见"证书链不完整"的报错,它就是在告诉你:这条"信任链"有某一环断了,不敢信。

坑三:中间人到底是怎么"骗"到用户的——再复盘一遍

中间人的手法,本质是同时扮演两头的"合法国王":对服务器装客户端,对客户端装服务器。它最阴险的地方在于,它经常利用的恰恰是用户轻信的那一环:

  • ARP 欺骗:在局域网里,黑客监听到别人的 IP/MAC 映射,然后告诉被攻击者"我是 A",让发往 A 的包都流经黑客,实现"流量主动送到我门口"。
  • ICMP 重定向欺骗:伪造一个"更好的路由路径"的 ICMP 重定向报文,让受害者的上网流量都导向黑客接口,达到和 ARP 欺骗相同的截获效果。
  • 假 WiFi / 假网站:在公共场合搭一个同名 WiFi,或做一个长得一模一样的钓鱼网站,诱导用户在明文通道里交出数据。

但你发现没有,上面这些手段,全都要么发生在明文(HTTP)链路里,要么发生在"用户点击了不信任告警"之后。只要真正走了 HTTPS、证书验证通过,中间人就算截获了所有密文,也解不开(前向保密下尤其如此)。这就是 HTTPS 的价值:不是让中间人不存在,而是让中间人"看得到却看不懂、改不动、装不成"。

坑四:HTTP 用 80,HTTPS 用 443(端口这一嗓子别喊哑)

这是最容易被忽略、但实战必考的一点:

HTTP 默认端口号是 80,HTTPS 默认端口号是 443。(依据见 RFC 2818)

这意味着:

  • 你访问 http://example.com/...,浏览器默认连它的 80 端口。
  • 你访问 https://example.com/...,浏览器默认连它的 443 端口。
  • 一台服务器可以同时在这两个端口分别监听 HTTP 和 HTTPS;如果你擅自把 HTTPS 服务改到 80,不显式写端口用户根本连不上。

还有一层:现在很多站点会做"HTTP 自动跳 HTTPS"——访问 80 端口的明文,被服务器 301 重定向到 443 的加密地址。这是良好实践,但别因此以为"HTTP 请求就能不防了"——跳转发生在客户端之后的第一跳,中间人的某些把戏恰恰想在这趟"跳转的间隙"里截胡,所以内网全加密才是终极解法。这一嗓子的结论就一句:看到 80 想到明文,看到 443 想到加密。

坑五:性能开销——HTTPS 不是免费的午餐

很多人以为 HTTPS 只是"慢一点点",其实它的代价比你想象的多,但也不至于让人望而却步:

  • 多一轮 RTT:HTTPS 在 TCP 三次握手之后,还要再做 TLS 握手。TLS 1.2 是 2-RTT,TLS 1.3 缩到 1-RTT。在真实网络里,每一次网络往返都以几十毫秒计,所以首字节延迟(TTFB)会明显比纯 HTTP 高。
  • CPU 开销:加解密要消耗 CPU。但好在只有会话建立(握手)用非对称加密(贵),业务流量用对称加密(便宜且现代硬件常带 AES 指令加速),所以对现代服务器而言,快速 A 的主机 CPU 开销增长其实可以接受。
  • 连接复用与 HTTPS 加速的配合:HTTP/1.1 的 Keep-Alive、HTTP/2 的并发多路复用、都是"一次握手、长期复用"的关键。握手成本被摊薄到大量请求上,实际性能损失会进一步缩小。

给你一个直觉:握手慢在"建连那一下",后续真正的业务流量因为用的是对称加密,很快。 对于 CDN、协议栈等都做了优化的站点,全站 HTTPS 的性能差距远没有你想象的可怕。

坑六:会话恢复与"复用"的暗坑(进阶预告)

现实里我们并不会每次请求都重新算一遍完整握手。TLS 提供了会话恢复机制:

  • Session ID:服务器保存会话 ID,客户端重连时带回来即可快速恢复,但多机集群里可能对不上号。
  • Session Ticket:服务器把会话信息用只有自己知道的密钥加密成一张"票据"发给客户端保存,重连时客户端原样带回,服务器验证解密即可快速续上,不依赖共享存储。

暗坑在于: 无论是哪种恢复,都是"信任之前协商好的会话密钥还在自己掌控中"。这就要求服务器妥善管理会话密钥的过期(前向保密真谛就在于让临时密钥"短命")。如果你做的是中间人代理类软件,还得处理"两头各建一条 TLS 通道"的双向拆封问题——那是给企业做流量审计的人日思夜想的课题,先知道有这回事。

全篇问答自测(附详解答案)

课件里散布着几道"思考题",正文我其实已经逐个解答过。这里把全篇最值得反复咀嚼的问答题统一列出来,附上详细答案,方便你自测和复习。

1. 为什么运营商要进行劫持? 答:根本出发点是利益。常见的收益包括:把用户的下载/访问链接替换成"合作返利"的广告链接赚推广费;在网页里插入自己的广告栏变现;以及通过流量调度做数据统计。由于 HTTP 是明文,运营商网络设备能"看见"并把响应报文改掉,用户却毫无察觉——这就是"名为劫持、实为逐利"。这个例子同时说明:明文传输不只是可能被"偷看",更可能被"篡改",这正是 HTTPS 存在的理由。

2. 只对 HTTP 做对称加密,能解决数据通信安全的问题吗?问题是什么? 答:不能从根本上解决。对称加密解决了"加密速度"问题,却绕不开"密钥安全分发"的死结:要让双方共享同一把对称密钥,这把密钥总得先安全送达;而密钥若也明文传,被劫持后一切加密形同虚设;密钥若要加密传,又陷入"密钥的密钥"循环。除此之外,服务器还要为海量客户端各自维护"一人一钥"的映射表,管理和扩散都成问题。所以合理结论是:只在最终传业务数据这一步用对称加密,协商密钥这一步交给非对称加密。

3. 为什么要用非对称加密?为什么不全用非对称加密? 答:用非对称加密,是因为它能解决"如何在不安全通道上安全地传递秘密"这个对称加密解不开的死结——公钥可公开、私钥私有,可以建立一个不需要预先共享秘密的安全信道。不能全用非对称加密,是因为非对称加密算法复杂、计算慢,用它加密海量 HTTP 数据会拖垮性能;同时它还需要可信机制来解决"这个公钥到底是不是对方的"(即证书/CA),否则会被中间人掉包。因此,业界方案是把两者组合用:少量关键信息(密钥、身份)交给非对称,海量业务数据交给对称。

4. 为什么摘要内容在网络传输时一定要加密形成签名,而不能直接明着发? 答:摘要(哈希)的作用是防篡改。但如果摘要本身以明文发送,黑客可以在篡改内容的同时,顺手把新的哈希值也算出来、替换掉原哈希,客户端再比对也发现不了。为了让"摘要"也变得不可伪造,就必须用签名方的私钥把摘要加密后再传(即数字签名);只有持有公钥的接收方才能解开、并且只有真正的签发方才能生成这个签名。这样一来,内容一动、或签名一换,都会被立刻识破——加密的不是"内容是什么",而是"这份内容确实没被改过"这一承诺。

5. 为什么签名不直接加密整份内容,而是先 hash 再签名? 答:两大原因。一是性能:非对称加密对数据长度敏感,内容越长越慢;只对固定长度的摘要做加密,速度快得多。二是语义清晰:签名的目的是"证明内容完整且来源可信",用摘要做载体,正好把"完整性的校验"和"身份的认证"干净地耦合在一起——内容变,摘要变,签名就失效;私钥不在手上,就造不出有效签名。

6. 中间人没有 CA 私钥,为什么就制作不出能骗过浏览器的假证书? 答:浏览器验证证书的必经一步,是用系统内置的 CA 公钥对证书上的签名"验签"——也就是解出签名里的摘要并与证书内容比对。要生成能通过验签的签名,必须持有 CA 的私钥。中间人拿不到它,就产不出"既能对上内容、又来自可信 CA"的签名。于是它只剩两条路:要么篡改证书但签名对不上(验签失败),要么申请一张自己的真证书来掉包(域名对不上)。在正常 CA 体系下,这两条路都被堵死——"CA 私钥只有 CA 有"这一条,就是整套信任体系的地基。


这一篇加餐,我们从 HTTP 明文之痛出发,走过了对称加密、非对称加密、哈希摘要、数字签名,又借助"方案一到方案四"的演进,亲手把每种加密组合的坑都踩了一遍;再经由中间人攻击这个"公钥掉包"的死穴,引出 CA 与数字证书这道"身份关",最终给出了完整的 TLS 握手时序图——你如今知道了 HTTP 走 80、HTTPS 走 443,知道了为什么先非对称后对称,知道了证书链要一层层验、知道了前向保密让历史数据更难被回溯,也终于明白了三组密钥那个漂亮的"套娃"结构。

其实 HTTPS 的整套机制,每一步的设计都不是凭空拍脑袋:它是"对称加密解决速度、非对称加密解决密钥分发、哈希解决完整性、证书解决身份认证"这四块积木严丝合缝拼起来的产物。你在 curl 里敲一条 -k 会跳过证书校验、写 socket 程序时 openssl s_client 能观察握手报文——现在你都看得懂它背后的门道了。

下一章,我们终于要动手了:用 socket 把这套理论落在代码里,从 TCP 到 TLS,亲手把一条加密连接"握"出来。到那时你会发现,前面这些图的每个箭头,代码里都能对上号。准备好了吗?