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 + 证书)

服务器收到客户端问候后,会做三件事:

  1. 从客户端提供的列表中,选一个双方都支持的密码套件和 TLS 版本。
  2. 发回自己的随机数(Server Random)。
  3. 发送数字证书 —— 这是身份验证的关键。证书里包含服务器的公钥、颁发机构信息、域名、有效期等。

注意:证书不是随意发的。我曾经遇到过一个案例:运维误用了泛域名证书,结果某个子域名被其他团队接管,中间人攻击因此得手。证书的管理,绝不能只靠「能跑就行」。

第三步:密钥交换与验证

客户端收到证书后,要做三件重要的事:

  1. 验证证书:检查证书是否在有效期内、是否由可信的 CA 签发、域名是否匹配。不通过就直接断开连接。
  2. 生成预主密钥(Pre-Master Secret):客户端随机生成一个 46 字节的预主密钥,用服务器的公钥加密后发过去。
  3. 计算主密钥:双方各自用 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 握手的核心逻辑串联起来。你可以把它当成一个速查板,后面讲到具体嵌入水印方法时,会频繁引用这些阶段。

HTTPS 握手阶段核心流程 客户端(Client) 1. ClientHello TLS 版本 · 密码套件 · 随机数 2. ServerHello + 证书 选定套件 · 服务器随机数 · 证书 3. 客户端密钥交换 预主密钥(RSD 公钥加密) 4. ChangeCipherSpec 通知:后续通信将加密 5. 握手完成 应用数据开始传输 关键提醒 握手阶段不传输任何应用数据。 所有业务数据必须等到握手完成、 协商出共享密钥之后才开始加密传输。

上面这张图展示的是 TLS 1.2 的标准握手流程。TLS 1.3 做了大幅简化——只需要 1-RTT 甚至 0-RTT 就能完成握手。不过原理是相通的,掌握了这个骨架,你再看任何协议变种都不会懵。

本章核心回顾:HTTPS 握手是身份验证 + 密钥协商 + 参数协商的三合一过程。客户端和服务器通过 ClientHello、ServerHello、证书验证、密钥交换、ChangeCipherSpec 这五个核心步骤,在完全不安全的信道上建立起一个安全的加密隧道。这是后续所有安全功能(包括嵌入水印)的基础设施。