HTTPS协议详解------SSL/TLS握手、证书与加密通信
本文基于华为云 ECS 实战环境,在 server-2(139.9.119.225 / 192.168.0.225)上生成自签名证书、配置 Nginx HTTPS 服务,并通过 curl -v、openssl s_client、tcpdump 等工具深入分析 SSL/TLS 握手全过程。所有数据均来自真实生产环境操作,完整还原 HTTPS 加密通信的建立机制。
一、HTTPS协议概述
HTTPS(HyperText Transfer Protocol Secure)并非一个全新协议,而是 HTTP over TLS/SSL------即在 TLS 加密通道上传输 HTTP 数据。其核心目标是解决 HTTP 明文传输带来的三大安全隐患:
| 威胁 | HTTP 风险 | HTTPS 防护 |
|---|---|---|
| 窃听 | 数据明文传输,中间人可嗅探 | TLS 加密,密文传输 |
| 篡改 | 内容可被代理或路由器修改 | MAC/AEAD 完整性校验 |
| 伪造 | 任何人都可以冒充服务端 | 数字证书 + CA 信任链验证 |
HTTPS 默认使用 443 端口,在 TCP 三次握手完成后,额外增加 TLS 握手阶段,协商加密算法、交换密钥、验证证书,之后才进入 HTTP 通信。
二、加密基础
2.1 对称加密 vs 非对称加密
| 类型 | 特点 | 典型算法 | HTTPS 用途 |
|---|---|---|---|
| 对称加密 | 加解密使用同一密钥,速度快 | AES, ChaCha20 | 应用数据加密 |
| 非对称加密 | 公钥加密/私钥解密,速度慢 | RSA, ECDSA, X25519 | 密钥交换、数字签名 |
HTTPS 结合两者优势:先用非对称加密安全地协商出对称密钥(会话密钥),再用对称密钥加密后续所有应用数据。
2.2 数字证书与CA链
数字证书是将公钥与身份信息绑定的电子文档,由受信任的 CA(Certificate Authority)签发。其信任模型如下:
scss
根CA (Root CA)
|
+-- 中间CA (Intermediate CA)
|
+-- 网站证书 (server-2.example.com)
- 根证书:自签名,预装在操作系统/浏览器中,是信任的起点
- 中间证书:由根 CA 签发,用于签发终端证书
- 终端证书:网站实际使用的证书,包含域名和公钥
本文使用自签名证书(自己签发自己),因此 Issuer = Subject,浏览器会显示安全警告。在生产环境中应使用 Let's Encrypt 等受信任 CA 签发的证书。
三、自签名证书生成
3.1 生成命令
在 server-2 上执行 openssl 命令生成自签名证书:
bash
# 生成 RSA 2048 位自签名证书,有效期 365 天
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/server.key \
-out /etc/nginx/ssl/server.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=DevOps Lab/OU=IT/CN=server-2.example.com" \
-addext "subjectAltName=DNS:server-2.example.com,IP:192.168.0.225"
关键参数解析:
| 参数 | 说明 |
|---|---|
-x509 |
生成自签名证书而非 CSR |
-nodes |
不对私钥加密(Nginx 启动时无需输入密码) |
-days 365 |
有效期 365 天 |
-newkey rsa:2048 |
生成 2048 位 RSA 密钥对 |
-subj |
证书主题信息(C=国家, ST=省, L=市, O=组织, OU=部门, CN=通用名称) |
-addext |
添加扩展,指定 SAN(Subject Alternative Name) |
3.2 证书信息
通过 openssl x509 查看证书详情:
bash
openssl x509 -in /etc/nginx/ssl/server.crt -text -noout
真实输出(关键字段):
yaml
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
4e:28:3c:3f:fe:b9:c5:50:9d:05:5f:bf:fb:44:2d:96:48:58:04:9c
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = CN, ST = Beijing, L = Beijing, O = DevOps Lab,
OU = IT, CN = server-2.example.com
Validity
Not Before: Sep 17 04:03:18 2026 GMT
Not After : Sep 17 04:03:18 2027 GMT
Subject: C = CN, ST = Beijing, L = Beijing, O = DevOps Lab,
OU = IT, CN = server-2.example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
SAN 扩展信息:
bash
openssl x509 -in /etc/nginx/ssl/server.crt -text -noout | grep -E 'DNS:|IP:'
输出:
ruby
DNS:server-2.example.com, IP Address:192.168.0.225
| 字段 | 值 | 说明 |
|---|---|---|
| Version | 3 (0x2) | X.509 v3 证书 |
| Serial Number | 4e:28:3c:3f:... | 证书唯一序列号 |
| Signature Algorithm | sha256WithRSAEncryption | SHA256 签名 + RSA 加密 |
| Issuer | server-2.example.com | 签发者(自签名,与 Subject 相同) |
| Not Before | Sep 17 04:03:18 2026 GMT | 生效时间 |
| Not After | Sep 17 04:03:18 2027 GMT | 过期时间 |
| Subject | server-2.example.com | 证书主体 |
| Public Key | RSA 2048 bit | 公钥算法和长度 |
| SAN | DNS:server-2.example.com, IP:192.168.0.225 | 备用名称(域名和IP) |
SAN(Subject Alternative Name)是现代证书中域名匹配的核心机制。TLS 握手时,客户端会检查服务器证书的 SAN 字段是否包含请求的主机名,而非旧版 CN(Common Name)字段。
3.3 证书链验证
使用 openssl s_client 查看证书链:
bash
openssl s_client -connect localhost:443 -showcerts < /dev/null
真实输出:
yaml
depth=0 C = CN, ST = Beijing, L = Beijing, O = DevOps Lab, OU = IT, CN = server-2.example.com
verify error:num=18:self-signed certificate
verify return:1
depth=0 C = CN, ST = Beijing, L = Beijing, O = DevOps Lab, OU = IT, CN = server-2.example.com
verify return:1
CONNECTED(00000003)
---
Certificate chain
0 s:C = CN, ST = Beijing, L = Beijing, O = DevOps Lab, OU = IT, CN = server-2.example.com
i:C = CN, ST = Beijing, L = Beijing, O = DevOps Lab, OU = IT, CN = server-2.example.com
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Sep 17 04:03:18 2026 GMT; NotAfter: Sep 17 04:03:18 2027 GMT
verify error:num=18:self-signed certificate 表明这是一个自签名证书,不被系统信任链认可。证书链深度 depth=0 说明只有一层(没有中间 CA),Issuer 和 Subject 完全相同。
四、SSL/TLS握手过程
4.1 TLS 1.3 握手详解
使用 curl -v 在 server-2 上发起 HTTPS 请求,观察完整的 TLS 1.3 握手:
bash
curl -sk -v https://localhost/secure 2>&1
真实输出(握手部分):
scss
* Connected to localhost (127.0.0.1) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [25 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [1021 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
4.2 逐消息解析
| 步骤 | 方向 | 消息类型 | 大小 | 说明 |
|---|---|---|---|---|
| 1 | OUT | Client Hello | 512B | 客户端发送支持的密码套件、TLS版本、随机数 |
| 2 | IN | Server Hello | 122B | 服务端选定密码套件、发送随机数 |
| 3 | IN | Encrypted Extensions | 25B | 加密扩展(如 ALPN 协议协商) |
| 4 | IN | Certificate | 1021B | 服务端证书链 |
| 5 | IN | CERT verify | 264B | 证书验证签名(证明持有私钥) |
| 6 | IN | Finished | 52B | 服务端握手完成(含验证数据) |
| 7 | OUT | Change cipher spec | 1B | 客户端切换到加密模式 |
| 8 | OUT | Finished | 52B | 客户端握手完成 |
最终协商结果:
- TLS 版本: TLSv1.3
- 密码套件: TLS_AES_256_GCM_SHA384(AES-256-GCM 对称加密 + SHA384 哈希)
- 密钥交换: X25519(椭圆曲线 Diffie-Hellman)
- 签名算法: RSASSA-PSS(RSA 证书签名验证)
4.3 TLS 1.3 握手时序图
scss
Client (server-2) Server (server-2:443)
| |
|--- Client Hello (512B) ----------------->|
| 支持的密码套件、TLS版本、客户端随机数 |
| (明文传输) |
| |
|<-- Server Hello (122B) ------------------|
| 选定密码套件、服务端随机数 |
| (明文传输,之后切换为加密) |
| |
|<-- Encrypted Extensions (25B) -----------|
|<-- Certificate (1021B) ------------------|
|<-- Certificate Verify (264B) ------------|
|<-- Finished (52B) -----------------------|
| (以上全部加密传输) |
| |
|--- Change Cipher Spec (1B) ------------->|
|--- Finished (52B) ---------------------->|
| (客户端切换到加密模式) |
| |
|<========= 加密通道建立完成 ===============>|
| |
|<-- New Session Ticket (265B) x2 ---------|
| (会话恢复票据) |
| |
|--- HTTP GET /secure (加密) ------------->|
|<-- HTTP 200 OK (加密) -------------------|
TLS 1.3 的最大改进是将握手从 2-RTT 缩减到 1-RTT(首次连接)甚至 0-RTT(会话恢复)。Server Hello 之后的全部消息都已加密,大幅减少了明文信息暴露。
4.4 握手数据统计
openssl s_client 还报告了握手数据量:
python
SSL handshake has read 1564 bytes and written 373 bytes
- 服务端 → 客户端:1564 字节(证书 1021B + Server Hello 122B + 其他握手消息)
- 客户端 → 服务端:373 字节(Client Hello 512B 减去部分压缩 + Finished)
五、TLS 1.2 vs TLS 1.3
分别使用 -tls1_2 和 -tls1_3 参数测试两个版本的握手:
bash
# TLS 1.2
openssl s_client -connect localhost:443 -tls1_2 < /dev/null 2>&1 | head -30
# TLS 1.3
openssl s_client -connect localhost:443 -tls1_3 < /dev/null 2>&1 | head -30
两个版本均成功连接,证书链完全一致。关键差异对比:
| 特性 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手 RTT | 2-RTT | 1-RTT(0-RTT 恢复) |
| 密钥交换 | RSA 或 ECDHE | 仅 ECDHE/DHE(前向安全) |
| 证书消息 | 明文传输 | 加密传输 |
| 静态 RSA 密钥交换 | 支持 | 已移除 |
| CBC 模式密码套件 | 支持 | 已移除 |
| 压缩 | 支持 | 已移除(防 CRIME 攻击) |
| 会话恢复 | Session ID/Ticket | Session Ticket + PSK |
| Change Cipher Spec | 每次 | 仅兼容性保留 |
TLS 1.3 移除了所有不安全的算法和特性,强制前向安全(Forward Secrecy)。即使服务端私钥泄露,已捕获的历史流量也无法被解密。
六、HTTP vs HTTPS 性能对比
6.1 计时测试
在 server-2 上对比 HTTP(80端口)与 HTTPS(443端口)的请求耗时:
bash
# HTTP 计时
curl -s -o /dev/null -w 'HTTP Connect: %{time_connect}s\nHTTP TTFB: %{time_starttransfer}s\nHTTP Total: %{time_total}s\n' http://localhost/
# HTTPS 计时
curl -sk -o /dev/null -w 'HTTPS Connect: %{time_connect}s\nHTTPS SSL: %{time_appconnect}s\nHTTPS TTFB: %{time_starttransfer}s\nHTTPS Total: %{time_total}s\n' https://localhost/secure
真实输出:
yaml
=== HTTP (Port 80) ===
HTTP Connect: 0.000126s
HTTP TTFB: 0.000288s
HTTP Total: 0.000317s
=== HTTPS (Port 443) ===
HTTPS Connect: 0.000117s
HTTPS SSL: 0.003055s
HTTPS TTFB: 0.003149s
HTTPS Total: 0.003175s
6.2 耗时分解
| 阶段 | HTTP | HTTPS | 差值 | 说明 |
|---|---|---|---|---|
| TCP 连接 | 0.000126s | 0.000117s | -0.000009s | 基本一致 |
| SSL 握手 | - | 0.003055s | +0.003055s | HTTPS 额外开销 |
| TTFB | 0.000288s | 0.003149s | +0.002861s | 含 SSL 时间 |
| 总耗时 | 0.000317s | 0.003175s | +0.002858s | 约 10 倍 |
SSL 握手开销约 3 毫秒(0.003055s)。在本机回环测试中,这一开销几乎可以忽略。在跨网络场景中,TLS 1.3 的 1-RTT 握手通常只增加一个 RTT 的延迟(同城约 1-2ms,跨地域约 10-50ms)。
对于已建立连接的后续请求(HTTP Keep-Alive 或 TLS Session Resumption),SSL 开销可降至接近零。因此生产环境中建议启用 TLS 会话恢复和连接复用。
七、支持的加密套件
查看 OpenSSL 支持的加密套件:
bash
openssl ciphers -v 'HIGH:!aNULL' | head -20
真实输出:
ini
TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
DHE-DSS-AES256-GCM-SHA384 TLSv1.2 Kx=DH Au=DSS Enc=AESGCM(256) Mac=AEAD
DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH Au=RSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
DHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=DH Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
密码套件命名格式:密钥交换-认证-加密-MAC
| 组件 | 说明 | 示例值 |
|---|---|---|
| Kx (Key Exchange) | 密钥交换算法 | ECDH, DH, any(TLS1.3) |
| Au (Authentication) | 认证算法 | RSA, ECDSA, any(TLS1.3) |
| Enc (Encryption) | 对称加密算法 | AESGCM(256), CHACHA20/POLY1305(256) |
| Mac (Message Auth) | 消息认证码 | AEAD(认证加密一体) |
TLS 1.3 的密码套件命名简化为 TLS_加密_密钥长度_MAC,因为密钥交换和认证算法在协议层面固定。
八、HTTP到HTTPS重定向
8.1 Nginx重定向配置
在 Nginx 配置中,通过 80 端口的服务器块实现 HTTP 到 HTTPS 的 301 永久重定向:
nginx
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
return 301 返回 HTTP 301 Moved Permanently 状态码,指示客户端使用 HTTPS 重新请求。$host 保留原始主机名,$request_uri 保留原始路径和查询参数。
8.2 重定向验证
测试 HTTP 请求的响应:
bash
curl -s -I http://localhost/secure 2>&1
实际输出:
yaml
HTTP/1.1 404 Not Found
Server: nginx/1.24.0 (Ubuntu)
Date: Thu, 17 Sep 2026 04:03:23 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
X-Backend-Server: backend-1-192.168.0.225
此处返回 404 而非预期的 301,原因是 server-2 上已存在监听 80 端口的 Nginx 配置(反向代理配置),新配置中的 listen 80 块因服务器名称冲突被忽略。Nginx 启动时的警告日志也证实了这一点:
ini
2026/09/17 12:03:18 [warn] 10126#10126: conflicting server name "_" on 0.0.0.0:80, ignored
在生产环境中,确保 80 端口仅由重定向配置独占,避免多个 server 块监听相同端口和 server_name 冲突。正确配置后,HTTP 请求将收到:
arduino
HTTP/1.1 301 Moved Permanently
Location: https://localhost/secure
九、跨服务器HTTPS验证
从 server-1(113.44.212.242)向 server-2(192.168.0.225)发起 HTTPS 请求,验证跨服务器 TLS 通信:
bash
# 在 server-1 上执行
curl -sk -v https://192.168.0.225/secure 2>&1
真实输出:
scss
* Trying 192.168.0.225:443...
* Connected to 192.168.0.225 (192.168.0.225) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [21 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [1021 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* Server certificate:
* subject: C=CN; ST=Beijing; L=Beijing; O=DevOps Lab; OU=IT; CN=server-2.example.com
* issuer: C=CN; ST=Beijing; L=Beijing; O=DevOps Lab; OU=IT; CN=server-2.example.com
* SSL certificate verify result: self-signed certificate (18), continuing anyway.
* using HTTP/1.x
> GET /secure HTTP/1.1
> Host: 192.168.0.225
< HTTP/1.1 200 OK
< Strict-Transport-Security: max-age=31536000
< X-HTTPS: On
This is a secure HTTPS response! Protocol: HTTP/1.1 SSL: TLSv1.3 Cipher: TLS_AES_256_GCM_SHA384
跨服务器握手与本机一致,使用 TLS 1.3 和 TLS_AES_256_GCM_SHA384。-k 参数跳过了自签名证书验证(verify result: 18),响应中包含 HSTS 头和 X-HTTPS 标记。
十、Nginx SSL配置详解
10.1 完整配置
server-2 上的 Nginx HTTPS 配置:
nginx
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
# HTTPS 服务
server {
listen 443 ssl;
server_name _;
# 证书配置
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 协议与密码套件
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# 会话缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location /secure {
default_type text/plain;
return 200 "This is a secure HTTPS response! Protocol: $server_protocol SSL: $ssl_protocol Cipher: $ssl_cipher";
}
# HSTS 安全头
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-HTTPS "On" always;
}
10.2 配置项详解
| 指令 | 值 | 说明 |
|---|---|---|
| ssl_certificate | server.crt | 证书文件路径 |
| ssl_certificate_key | server.key | 私钥文件路径 |
| ssl_protocols | TLSv1.2 TLSv1.3 | 启用的 TLS 版本(禁用不安全的 SSLv3/TLSv1.0/TLSv1.1) |
| ssl_ciphers | HIGH:!aNULL:!MD5 | 密码套件筛选(高强度、禁用匿名、禁用MD5) |
| ssl_prefer_server_ciphers | on | 服务端优先选择密码套件 |
| ssl_session_cache | shared:SSL:10m | 共享会话缓存,10MB(约4万个会话) |
| ssl_session_timeout | 10m | 会话缓存超时 10 分钟 |
| HSTS | max-age=31536000 | 强制浏览器一年内使用 HTTPS |
10.3 HSTS头部
响应中的 HSTS(HTTP Strict Transport Security)头部:
ini
Strict-Transport-Security: max-age=31536000
HSTS 告知浏览器在 max-age 指定的时间(31536000秒=1年)内,始终使用 HTTPS 访问该站点,即使用户手动输入 HTTP 地址也不跳转。这是防止 SSL Strip 中间人攻击的有效手段。
十一、总结
本文通过真实操作数据,完整剖析了 HTTPS 协议的核心机制:
- 加密体系:非对称加密(RSA/X25519)协商密钥 + 对称加密(AES-256-GCM)传输数据
- 证书机制:自签名证书 RSA 2048 + SHA256 签名 + SAN 域名匹配,有效期 2026.09-2027.09
- TLS 1.3 握手:1-RTT 完成,Client Hello → Server Hello → 加密消息(证书/验证/Finished)
- 性能开销:SSL 握手仅增加约 3ms(本机回环),跨网络约为 1 个 RTT
- 安全配置:禁用旧版 TLS,启用 HSTS,配置会话缓存优化性能
- 跨服务器验证:server-1 到 server-2 的 TLS 1.3 通信正常,加密套件 TLS_AES_256_GCM_SHA384
在实际生产部署中,建议使用 Let's Encrypt 等受信任 CA 签发证书,启用 TLS 1.3,配置 OCSP Stapling 加速证书验证,并使用 ssl_session_cache 和 ssl_session_tickets 优化会话恢复性能。