本文是《计算机网络学习笔记》系列的第五篇。有了 TCP,数据可以可靠地送达;但 TCP 是明文的,任何中间节点都能看到数据的内容,也能篡改它。TLS 正是为了解决这个问题而生——它架在 TCP 之上,为应用层提供加密、认证、完整性三重保障,是 HTTPS、SMTPS、FTPS 等所有安全协议的共同基石。

一、TLS 的身世:从 SSL 到 TLS 1.3

版本年份说明
SSL 1.01994NetScape 内部设计,从未公开发布
SSL 2.01995第一个公开版本,存在严重安全漏洞
SSL 3.01996重新设计,但后来发现 POODLE 漏洞
TLS 1.01999IETF 接手,SSL 3.0 的标准化继承者
TLS 1.12006修补若干漏洞
TLS 1.22008目前仍广泛使用,引入了更强的加密套件
TLS 1.32018当前最新版,握手速度更快,安全性更强
名字从 SSL 改成 TLS,不只是换个马甲——TLS 1.0 与 SSL 3.0 并不兼容,是一次实质性的重新设计。

TLS 在网络协议栈中的位置:技术上它运行在 TCP 之上、应用层之下,是对 TCP 的加密增强。从开发者角度看,把它理解为"加密版 TCP"最直观——用 TLS 替换 TCP 编程,多不了几个 API 调用,但数据从此在空中加密传输。


二、TLS 解决哪三个问题?

TLS 提供的三重保障,缺一不可:

保障含义对应机制
加密(Confidentiality)中间人看不到明文内容对称加密(AES 等)+ 协商的会话密钥
认证(Authentication)确认服务器是你想连的那个,不是冒充的数字证书 + CA 信任链
完整性(Integrity)数据在传输中没有被篡改HMAC 消息认证码

三者缺少任何一个都不安全:只加密不认证,可能被中间人换掉公钥(MITM 攻击);只认证不加密,明文照样被窃听;只加密不验完整性,密文可以被翻转(Bit-flipping 攻击)。


三、TLS 1.2 握手:四轮对话建立安全信道

TLS 握手发生在 TCP 三次握手完成之后。此时 TCP 连接已通,TLS 在这条明文 TCP 连接上再协商出一套加密参数。

客户端 (Client)                              服务端 (Server)
    |  [TCP 三次握手,已完成]                    |
    |                                            |
    |  ① ClientHello                            |
    |  ——————————————————————————————————————> |
    |                                            |
    |  ② ServerHello + Certificate              |
    |     + ServerHelloDone                     |
    |  <—————————————————————————————————————— |
    |                                            |
    |  ③ ClientKeyExchange                      |
    |     + ChangeCipherSpec                    |
    |     + Finished                            |
    |  ——————————————————————————————————————> |
    |                                            |
    |  ④ ChangeCipherSpec + Finished            |
    |  <—————————————————————————————————————— |
    |                                            |
    |       [加密的应用层数据传输开始]           |

第一步:ClientHello

客户端主动发起,告诉服务端自己的"底牌":

  • TLS 版本号:声明自己支持的最高版本(如 TLS 1.2);
  • 客户端随机数(Client Random):32 字节的随机数,后续用于派生会话密钥;
  • 加密套件列表(Cipher Suites):客户端支持的加密算法组合,由强到弱排列,例如:

    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    TLS_RSA_WITH_AES_128_CBC_SHA256
    ...
  • SNI(Server Name Indication):扩展字段,明文告知服务器"我想访问的域名是 example.com"。这让一台服务器(一个 IP)可以托管多个 HTTPS 网站,根据 SNI 选择对应网站的证书发给客户端。

第二步:ServerHello + Certificate + ServerHelloDone

服务端收到 ClientHello 后,回复一系列消息:

ServerHello

  • 确认 TLS 版本:从客户端支持的版本中选一个,如 TLS 1.2;
  • 服务端随机数(Server Random):同样是 32 字节,后续派生密钥用;
  • 确认加密套件:从客户端的列表里选一个双方都支持的,如 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

Certificate:发送服务器的数字证书,证书内含:

  • 服务器的公钥
  • 服务器的身份信息(域名、组织、有效期等);
  • CA 的数字签名(证明这张证书是权威机构认证过的)。

ServerHelloDone:告知客户端"我的初始信息发完了"。

第三步:验证证书 + 密钥交换 + 切换加密

这是握手中最关键、最复杂的一步。

3a. 验证证书

客户端拿到证书后,必须验证它的真实性(否则中间人随便伪造一张证书就能欺骗你):

  1. 验证签名:用内置的 CA 根证书(浏览器/操作系统预装)验证证书上 CA 的签名是否合法;
  2. 验证域名:证书里的域名是否与自己访问的域名一致(防止 A 网站的证书被用来冒充 B 网站);
  3. 验证有效期:证书是否在有效期内;
  4. 验证吊销状态:证书是否被 CA 吊销(通过 CRL 或 OCSP 查询)。

全部通过,从证书里提取服务器的公钥。任何一项失败,浏览器弹出"您的连接不是私密连接"警告。

3b. 生成预主密钥并加密发送

客户端生成一个随机的预主密钥(Pre-Master Secret,PMS),用服务端的公钥加密后发送。只有持有对应私钥的服务端能解密——这保证了 PMS 只有双方知道。

此消息称为 ClientKeyExchange

3c. 双方各自派生会话密钥

此时双方手里都有三样东西:

Client Random  +  Server Random  +  Pre-Master Secret
         ↓
    PRF(伪随机函数)
         ↓
派生出 4 个密钥:
  ├── 客户端写入密钥  (Client → Server 方向的加密密钥)
  ├── 服务端写入密钥  (Server → Client 方向的加密密钥)
  ├── 客户端 MAC 密钥  (Client → Server 方向的完整性校验密钥)
  └── 服务端 MAC 密钥  (Server → Client 方向的完整性校验密钥)

为什么要两个随机数 + PMS 混合,而不是只用 PMS 一个?

因为仅靠 PMS 派生密钥,如果 PMS 本身不够随机(或者被攻击者预测),安全性就全靠它撑着。加入两个由双方各自独立生成的随机数,即使某一方的随机数生成器被攻破,另一方的随机数也能保住最终密钥的随机性。这叫熵混合,是密码学中的常见设计。

3d. ChangeCipherSpec + Finished

客户端发送:

  • ChangeCipherSpec:宣告"从现在起,我发的包全部加密";
  • Finished:包含从 ClientHello 到此刻所有握手消息的摘要(用新密钥加密),让服务端验证:密钥是对的,且握手过程未被篡改。

第四步:服务端 ChangeCipherSpec + Finished

服务端验证客户端的 Finished 通过后:

  • 发送 ChangeCipherSpec
  • 发送 Finished(同样是握手消息摘要的加密版)。

客户端验证通过。

至此,TLS 握手完成。 后续所有通信——HTTP 请求、响应——都在这个加密信道里流动,外界只能看到一堆密文。


四、TLS 协议格式

TLS 有自己的记录层(Record Layer)协议,贯穿握手和数据传输的全程,不只是传数据时才用。

 +--------+----------+--------+---~~~~~~~~~~~~~~~~---+--------+
 |  type  | version  | length |        payload        |  HMAC  |
 | 1 byte | 2 bytes  | 2 bytes|      (variable)       |variable|
 +--------+----------+--------+---~~~~~~~~~~~~~~~~---+--------+
字段大小说明
类型(Type)1 字节0x14 = ChangeCipherSpec;0x15 = Alert;0x16 = Handshake;0x17 = Application Data
版本(Version)2 字节0x0301 = TLS 1.0;0x0303 = TLS 1.2(历史原因,TLS 1.3 的报文此处也填 0x0303)
长度(Length)2 字节后续 payload + HMAC 的总长度,单条记录最大 16KB
数据(Payload)变长加密后的载荷(明文长度 ≤ 16KB)
HMAC变长用 MAC 密钥对"数据"计算的哈希,防止数据被篡改

一条 TLS 记录的封装流程:

应用层数据(可能很长)
    ↓ ① 分片(每片 ≤ 16KB)
片段
    ↓ ② 计算 HMAC,附在片段末尾
片段 + HMAC
    ↓ ③ 用加密密钥整体加密 → 密文
密文
    ↓ ④ 加拼 5 字节明文头部(type + version + length)
TLS 记录(可以安全传输)

注意 HMAC 是在加密之前计算、之后一起加密的,这保证了"先认证、后加密"(MAC-then-Encrypt)的完整性保护。(TLS 1.3 改为了 AEAD 模式,加密和认证同步完成,更安全。

关闭连接:将类型字段设为 Alert0x15),发送一个 close_notify 警告,优雅地终止 TLS 会话,然后再由 TCP 四次挥手关闭底层连接。


五、数字证书与信任链

证书是什么?

数字证书相当于互联网上的身份证:它证明"这个公钥,确实属于 example.com,不是别人冒充的"。证书由证书颁发机构(CA, Certificate Authority) 签发,CA 相当于派出所

信任传递逻辑

浏览器/操作系统  →  内置信任  →  根 CA
      根 CA       →  签发信任  →  中间 CA
    中间 CA       →  签发      →  服务器证书(example.com)

只要你信任根 CA,根 CA 信任中间 CA,中间 CA 信任 example.com 的证书,你就信任 example.com。这就是 证书信任链(Chain of Trust)

常见的 CA 机构:

  • DigiCert:商业 CA,企业常用
  • Let's Encrypt:免费、自动化,个人和开发者首选
  • GlobalSign:老牌商业 CA

申请证书的流程

你                                  CA
│                                    │
│  ① 生成密钥对(公钥 + 私钥)      │
│  ② 生成 CSR(证书签名请求)       │
│     CSR 包含:你的公钥 + 域名信息 │
│  ③ 提交 CSR ——————————————————> │
│                                    │  验证你对该域名的控制权
│                                    │  (在域名的 DNS/服务器上放一个验证文件)
│  ④ 收到签名后的数字证书 <———————— │
│     (CA 用自己的私钥签了你的 CSR)│
│  ⑤ 把证书部署到服务器上           │

Let's Encrypt + certbot 实战

Let's Encrypt 是目前最流行的免费 CA,推荐用 certbot 来自动化申请和续期:

# 安装 certbot(以 Nginx 为例)
apt-get install certbot
apt-get install python3-certbot-nginx

# 申请证书(会自动修改 Nginx 配置)
certbot --nginx -d example.com -d www.example.com
# 按提示输入邮箱、同意服务条款即可

# 设置自动续期(证书有效期 90 天,建议每月自动续一次)
crontab -e
# 添加以下行:每月 1 日 00:00 自动续期
0 0 1 * * /usr/bin/certbot renew --quiet

certbot 会自动完成域名验证、下载证书、配置 Nginx 的全流程,几乎是零门槛。


六、进阶:TLS 1.3 的改进

TLS 1.3(RFC 8446,2018 年)相比 1.2 有几处重要的改进:

握手速度:从 2-RTT 降到 1-RTT

TLS 1.2 的握手需要2 个来回(2-RTT)才能完成,加上 TCP 握手的 1-RTT,一共要 3 个往返才能开始发数据。

TLS 1.3 把握手压缩到了 1-RTT

TLS 1.3 的握手:

Client ——→ ClientHello(含密钥共享参数)
Server ——→ ServerHello + 证书 + Finished(合并为一次发送)
Client ——→ Finished

← 完成!比 1.2 少一个往返 →

此外 TLS 1.3 还支持 0-RTT 恢复(Session Resumption):对于之前访问过的服务器,客户端可以在第一个握手包里直接附上应用层数据,以极低的延迟恢复会话(有一定的重放攻击风险,需谨慎使用)。

废除了不安全的加密套件

TLS 1.3 移除了大量老旧的加密算法,只保留少数被认为安全的:

  • 废除 RSA 密钥交换(不支持前向保密);
  • 废除 RC4、DES、3DES、MD5、SHA-1 等脆弱算法;
  • 只允许使用支持前向保密(Forward Secrecy)的密钥交换算法(ECDHE 等)。
前向保密(Forward Secrecy):即使服务端的私钥未来泄露,攻击者也无法解密历史流量。因为每次 TLS 连接使用临时生成的密钥,私钥只用于身份认证,不参与密钥派生。

七、实战:用 Wireshark 抓出解密后的 HTTPS 包

默认情况下,抓包工具只能看到加密密文。通过以下配置,可以让 Chrome 和 Wireshark 配合,看到解密后的明文 HTTP 内容:

原理:Chrome 可以把每次连接的会话密钥记录到一个日志文件,Wireshark 读取这个文件后,就能用密钥解密对应的 TLS 流量。

步骤

  1. 新建密钥日志文件,例如 C:\sslkey.log(内容为空即可);
  2. 设置环境变量 SSLKEYLOGFILE,指向该文件:

    # Windows(永久设置,需重启生效)
    setx SSLKEYLOGFILE "C:\sslkey.log"
    
    # Linux / macOS(临时,在同一终端启动 Chrome)
    export SSLKEYLOGFILE=~/sslkey.log
  3. 重启 Chrome 浏览器(必须在设置环境变量之后启动的 Chrome 才会记录密钥);
  4. 配置 Wireshark
    编辑首选项ProtocolsTLS(Pre)-Master-Secret log filename,选中刚才的 sslkey.log
  5. 开始抓包,访问任意 HTTPS 网站(如 https://v2ex.com);
  6. 在 Wireshark 过滤框输入 http2,即可看到解密后的 HTTP/2 请求和响应明文。

这是学习 HTTP/2、调试 HTTPS 问题时极其有用的技巧。


总结

阶段目的关键动作
ClientHello声明己方能力发送随机数、TLS 版本、加密套件列表、SNI
ServerHello + 证书服务端应答 + 身份证明确认版本和套件、发送证书(公钥+签名)
证书验证 + 密钥交换认证服务端真实性 + 安全传递 PMS验证信任链 → 公钥加密 PMS → 双方派生 4 个会话密钥
ChangeCipherSpec + Finished切换加密 + 验证握手完整性双方均加密 Finished 消息,互相验证密钥和握手未被篡改
应用层数据传输加密通信所有数据用会话密钥加密,HMAC 保护完整性

TLS 的设计哲学和 TCP 一脉相承:用多出来的几次来回(握手的代价),换取整个通信过程的安全性。理解了 TLS,HTTPS 就不再是一个神秘的"小锁头",而是一套有清晰步骤、可以在 Wireshark 里逐帧验证的具体协议。


本系列前四篇:
· 第一篇:《TCP 协议格式详解》
· 第二篇:《TCP 三次握手与四次挥手》
· 第三篇:《TCP 可靠数据传输》
· 第四篇:《TCP 流量控制与拥塞控制》


参考资料:《计算机网络:自顶向下方法》

标签: none

添加新评论