HTTPS 协议概述与握手基础
说实话,很多人学了 HTTPS 好几年,还是没搞明白握手阶段到底在干什么。我个人觉得,这主要是因为大家把「握手」想得太神秘了。说白了,HTTPS 握手就是客户端和服务器之间,在正式传输数据之前,先互相确认身份、商量好加密方式、并且生成一把只有他俩知道的密钥——这个过程。
我刚开始做安全开发的时候,也踩过不少坑。记得有一次,我在内网部署一个高并发服务,测试环境一切正常,一上生产就卡成PPT。查了半天,发现是握手阶段使用的密码套件太复杂,导致服务器端计算压力过大。嗯,从那以后,我对握手阶段的每一个环节都格外谨慎。
今天这一章,我们就先把地基打好——把 HTTPS 为什么需要握手、以及握手阶段到底有哪些核心步骤,彻底讲透。
HTTPS 为什么需要握手?
你想想看,如果两个人在一个嘈杂的房间里对话,周围全是窃听者。他们直接喊「你好,我是小明,我要告诉你一个秘密」,那这个秘密根本不算秘密。网络上的 HTTP 请求就是这样——明文传输,任何人只要在路由路径上,就能看到你的请求内容、甚至篡改它。
HTTPS 就是为了解决这个问题。它用 TLS 协议来给 HTTP 穿上铠甲。而握手,就是穿上铠甲之前,双方先确认身份、交换铠甲钥匙的过程。
- 身份验证——确保你连接的那台服务器,真的是它声称的那台,而不是某个中间人伪装的。
- 密钥协商——客户端和服务器各自生成一部分秘密,通过握手交换组合,最终合成一个对称密钥。
- 参数协商——确认使用什么协议版本、什么加密算法、什么压缩方式。
核心认知:没有握手,就没有安全通信。握手的本质就是「先验证,再加密,后通信」。
握手的核心目标
我曾经在一个项目中,需要为金融系统实现自定义握手扩展。当时我翻阅了 RFC 文档,发现握手阶段其实只做三件事。搞懂这三件事,你就抓住了握手的骨架。
| 目标 | 解决的问题 | 涉及的关键消息 |
|---|---|---|
| 身份验证 | 如何证明服务器真的是它自己? | Certificate, CertificateVerify |
| 密钥协商 | 如何在不安全的信道上生成共享密钥? | ClientHello, ServerHello, KeyExchange |
| 参数协商 | 双方使用什么版本的协议和算法? | ClientHello, ServerHello |
我个人习惯:在做安全审查时,我会重点检查「证书验证」这个环节。因为很多开发者在测试环境关闭了证书验证,结果一不小心就带上生产环境了。我自己就犯过这种错。
握手流程深度拆解
标准的 TLS 1.2 握手流程,需要 2 个 RTT 才能完成。什么意思?就是客户端和服务器要来回通信两次,然后才能开始推送业务数据。这个延迟在某些场景下是很致命的——比如视频直播或者游戏对战。
我把握手流程拆成几个阶段,方便你对照着理解。
第一步:客户端问候(ClientHello)
客户端先发一个「你好」包,里面带着自己支持的 TLS 版本、密码套件列表、一个随机数(Client Random),以及一个 Session ID(如果打算复用之前的会话)。
// ClientHello 的核心字段(简化)
{
"version": "TLS 1.2",
"cipher_suites": ["TLS_AES_128_GCM_SHA256", "...", "TLS_CHACHA20_POLY1305_SHA256"],
"random": "32字节随机数",
"session_id": "可选,用于会话复用"
}
第二步:服务器回应(ServerHello + 证书)
服务器收到客户端问候后,会做三件事:
- 从客户端提供的列表中,选一个双方都支持的密码套件和 TLS 版本。
- 发回自己的随机数(Server Random)。
- 发送数字证书 —— 这是身份验证的关键。证书里包含服务器的公钥、颁发机构信息、域名、有效期等。
注意:证书不是随意发的。我曾经遇到过一个案例:运维误用了泛域名证书,结果某个子域名被其他团队接管,中间人攻击因此得手。证书的管理,绝不能只靠「能跑就行」。
第三步:密钥交换与验证
客户端收到证书后,要做三件重要的事:
- 验证证书:检查证书是否在有效期内、是否由可信的 CA 签发、域名是否匹配。不通过就直接断开连接。
- 生成预主密钥(Pre-Master Secret):客户端随机生成一个 46 字节的预主密钥,用服务器的公钥加密后发过去。
- 计算主密钥:双方各自用 Client Random、Server Random 和 Pre-Master Secret,通过 PRF 算法生成 48 字节的主密钥(Master Secret)。后续的会话密钥都从主密钥衍生出来。
// 密钥生成伪逻辑(不是真实代码,用于理解)
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)[0..47]
第四步:握手完成与确认
客户端和服务器分别发一个 ChangeCipherSpec 消息,告诉对方:后续的通信将使用商定好的密钥进行加密。然后各自发一个 EncryptedHandshakeMessage,证明自己确实持有正确的密钥。握手至此结束,开始传输应用数据。
避坑指南:我曾经接手过一个系统,每次握手都要卡 3 秒。查到最后,发现是客户端证书链太长——服务器发了 5 层中间证书,每个都需要客户端去 CA 在线验证。解决方法很简单:把中间证书合并成单层,或者在本地区缓存 CA 响应。这个小改动,让握手时间降到了 200ms 以内。
密码套件协商
密码套件(Cipher Suite)是握手阶段最容易被忽略但又最重要的一环。它决定了你的通信使用什么算法、什么强度。
一个典型的密码套件长这样:
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
拆开来看:
| 组成部分 | 示例值 | 说明 |
|---|---|---|
| 密钥交换 | ECDHE | 临时椭圆曲线 Diffie-Hellman,提供前向安全性 |
| 身份验证 | RSA | 用 RSA 证书进行签名验证 |
| 对称加密 | AES_128_GCM | 使用 AES-128 配合 GCM 模式进行数据加密,同时验证完整性 |
| 哈希 | SHA256 | 消息认证码使用的哈希函数 |
你想想看,如果双方协商出来的密码套件包含弱算法,比如 3DES、RC4 或者不安全的 CBC 模式,那即使握手成功了,后续的数据加密也是纸糊的。我建议在做安全审计时,一定要把不安全的套件从服务器配置中移除掉。
本章知识结构图
下面我用一张图把 HTTP 握手的核心逻辑串联起来。你可以把它当成一个速查板,后面讲到具体嵌入水印方法时,会频繁引用这些阶段。
上面这张图展示的是 TLS 1.2 的标准握手流程。TLS 1.3 做了大幅简化——只需要 1-RTT 甚至 0-RTT 就能完成握手。不过原理是相通的,掌握了这个骨架,你再看任何协议变种都不会懵。
本章核心回顾:HTTPS 握手是身份验证 + 密钥协商 + 参数协商的三合一过程。客户端和服务器通过 ClientHello、ServerHello、证书验证、密钥交换、ChangeCipherSpec 这五个核心步骤,在完全不安全的信道上建立起一个安全的加密隧道。这是后续所有安全功能(包括嵌入水印)的基础设施。