本文目录导读:
这是一个非常核心的计算机安全问题,HTTPS 的本质就是 HTTP(应用层) 运行于 TLS/SSL(传输层安全协议)之上。
“握手”是客户端(如浏览器)和服务器建立安全连接的第一步,目的是协商加密算法、验证服务器身份、并生成只有双方知道的会话密钥。
下面深入到源码级别(以最流行的 OpenSSL 库和 Linux 内核的通信逻辑为例),拆解 TLS 1.3(目前主流且更简洁的版本,TLS 1.2 为参考)的握手底层原理。
核心概念:三个“秘密”的交换
在握手过程中,所有操作都围绕下面三个值展开(源码里会以 struct 或变量形式存在):
- 随机数 (Random):双方生成,防止重放攻击。
- 预主密钥 (Pre-Master Secret):客户端生成,使用服务器公钥加密传输。
- 主密钥 (Master Secret):由预主密钥和两个随机数通过一个
PRF(伪随机函数)推导出来,是后续生成所有加密密钥的根。
流程拆解(TLS 1.3 简化 + 源码视角)
TLS 1.3 将握手从 2-RTT 降为 1-RTT,效率更高,假设是首次连接(非会话恢复)。
ClientHello (客户端问候)
概念:客户端发送支持的 TLS 版本、密码套件(如 TLS_AES_128_GCM_SHA256)、一个 32 字节的随机数 以及密钥交换的公共参数(椭圆曲线 Diffie-Hellman 的初始公钥)。
源码级别演绎 (openssl/ssl/statem/statem_clnt.c 中的 tls_construct_client_hello()):
// 伪代码示意
int tls_construct_client_hello(SSL *s, WPACKET *pkt) {
// 1. 生成 32 字节客户端随机数
RAND_bytes(s->s3->client_random, 32);
// 2. 写入 TLS 版本 (0x0304 代表 TLS 1.3)
WPACKET_put_bytes_u16(pkt, s->version);
// 3. 写入随机数
WPACKET_memcpy(pkt, s->s3->client_random, 32);
// 4. 写入密码套件列表 (TLS_AES_256_GCM_SHA384)
// ...
// 5. **关键**: 生成客户端的 ECDHE (椭圆曲线 Diffie-Hellman Ephemeral) 密钥对
// 并将公钥 (KeyShare) 放入扩展字段
if (!tls_construct_extensions(s, pkt, ...)) {
// 扩展里有一个 extension_type = key_share
// 内容就是椭圆曲线类型 (如 x25519) 和公钥值
}
// 6. 发送给服务器
return 1;
}
核心意义:客户端不仅告诉了服务器“我想用谁”,还提前把公钥材料(KeyShare)发过去了,这是 TLS 1.3 减少一次 RTT 的关键。
ServerHello + 服务器参数 (服务器回复)
服务器收到后,也会做类似的步骤。
源码级别演绎 (openssl/ssl/statem/statem_srvr.c 中的 tls_construct_server_hello()):
int tls_construct_server_hello(SSL *s, WPACKET *pkt) {
// 1. 生成 32 字节服务器随机数
RAND_bytes(s->s3->server_random, 32);
// 2. 写入 TLS 版本 (与客户端协商一致)
WPACKET_put_bytes_u16(pkt, s->version);
// 3. 写入随机数
WPACKET_memcpy(pkt, s->s3->server_random, 32);
// 4. **关键**: 发送服务器自己的 KeyShare (也是 ECDHE 公钥)
// 服务器用客户端的 KeyShare 和自己的私钥,就能计算出共享密钥!
// ...
return 1;
}
服务器还会做一件极其重要的事:
源码演绎 (ossl_statem_server_post_process_message()):
// 伪代码 - 计算共享密钥 EC_POINT *client_pub = ...; // 从 ClientHello 中解析出客户端公钥 EC_KEY *server_priv = ...; // 服务器自己生成的临时私钥 // 重点: 两边各自用 (自己的私钥 + 对方的公钥) 计算出相同的共享密钥 "Z" unsigned char *shared_secret_Z = ECDH_compute_key(client_pub, server_priv); // 这个 shared_secret_Z Pre-Master Secret 的现代版本 // 然后立即使用 HKDF (HMAC-based Extract-and-Expand Key Derivation Function) // 从 shared_secret_Z 和两个随机数,派生出实际的会话加密密钥 tls13_generate_handshake_secret(s, shared_secret_Z, s->s3->client_random, s->s3->server_random); // 重要: 使用这个握手密钥加密服务器即将发送的 Certificate/Finished 消息
核心意义:
- 即使攻击者截获了所有公钥(都是明文的),没有私钥也无法计算出共享密钥
Z。 - 完美前向保密 (PFS):因为服务器使用的 ECDHE 私钥是临时生成的,每次连接都不同,即使将来服务器的长期私钥(证书私钥)泄露,也无法解密过去记录的流量。
证书验证 + 握手完成 (Certificate + Finished)
服务器动作:
- 发送自己的数字证书(包含公钥和 CA 签名),客户端会验证这个证书的合法性。
- 发送一个
CertificateVerify消息:用证书私钥对到目前为止的所有握手消息进行签名,客户端用证书公钥验签,证明服务器确实拥有该证书的私钥。 - 发送一个
Finished消息:使用刚刚计算出的握手密钥,对handshake记录进行 MAC 计算,确保握手过程未被篡改,客户端收到后,也能用自己算出来的握手密钥验证Finished消息,证明密钥协商成功。
客户端动作:
- 验证服务器证书(路径、有效期、吊销状态)。
- 用证书公钥验证
CertificateVerify签名。 - 用握手密钥验证
Finished消息。 - 生成最终的会话密钥,并发送自己的
Finished消息。
最终成果:双方现在持有完全相同的、只有它们知道的一组会话密钥(由 shared_secret_Z 派生而来)。
HTTPS 底层源码链路总结(从你的代码到握手)
当你用 https://example.com 访问时,底层发生了什么(以 curl + OpenSSL 为例):
- Socket 连接:
curl调用socket() -> connect()到example.com:443,这是 TCP 三次握手,不加密。 - 加载 SSL 上下文:
curl调用SSL_CTX_new(TLS_client_method())创建 SSL 上下文。 - 绑定 Socket:
SSL_new(ctx) -> SSL_set_fd(ssl, socket_fd)将加密层绑定到已连接的 TCP 套接字上。 - 执行握手:
SSL_connect(ssl),这个函数内部就会触发上述tls_construct_client_hello等一堆复杂的 OpenSSL 源码调用。- 内核层面:数据包通过
sendmsg()系统调用发送出去,经过 TCP 协议栈、IP 层,最终从网卡发出,接收也是同理。 - OpenSSL 层面:处理协议状态机、加密/解密、内存分配、大数运算(ECC 计算)。
- 内核层面:数据包通过
- 发送/接收数据:握手成功后,
SSL_read(ssl, buf, len)和SSL_write(ssl, buf, len)对应用层透明,OpenSSL 会在底层自动使用协商好的密钥进行AES-GCM加密和解密。
一个重要的“坑”:密码学与协议实现
源码不是一个简单的函数调用,而是状态机、加密工程和网络协议的复杂结合。
- 状态机:OpenSSL 有一张巨大的状态转换表,
SSL_ST_BEFORE->SSL_ST_OK->SSL_ST_READ_BODY->SSL_ST_READ_HEADER,错误的状态会导致连接中断。 - 加密工程:
RAND_bytes不是简单的随机,它可能调用/dev/urandom或 CPU 的RDRAND指令。ECDH_compute_key底层涉及椭圆曲线点乘、Montgomery 域运算、快速模约减等高度优化的汇编代码(如x86_64-mont5)。 - 不可信赖的握手:
Finished消息的目的是对所有握手消息进行 HMAC 验证,如果任何一个字节在传输过程中被篡改(比如中间人修改了KeyShare公钥),双方计算出的密钥将不一致,Finished验证必然失败,连接立即终止,这是底层保障安全性的防线。
HTTPS 握手底层原理 = TCP 连接 + 基于 DH/ECDH 的密钥交换 + 证书链的 PKI 验证 + 状态机驱动的明文/密文协议切换,所有这一切,在 OpenSSL 源码中体现为数千行 C 代码的严密逻辑、状态流转和数学运算,理解了 ECDHE 密钥协商和 Finished 消息的完整性验证,就抓住了 HTTPS 安全的本质。
标签: 密钥交换