TLS(Transport Layer Security)及其前身 SSL(Secure Sockets Layer)是保障网络通信安全的核心协议。理解 TLS 的握手流程、证书体系和密码学基础,是设计安全系统的必备知识。
一、TLS 演进
| 版本 | 年份 | 状态 | 说明 |
|---|---|---|---|
| SSL 2.0 | 1995 | 废弃 | 存在严重安全漏洞 |
| SSL 3.0 | 1996 | 废弃 | POODLE 攻击 |
| TLS 1.0 | 1999 | 废弃 | 被认为不安全 |
| TLS 1.1 | 2006 | 废弃 | BEAST 攻击风险 |
| TLS 1.2 | 2008 | 主流 | 当前最广泛使用的版本 |
| TLS 1.3 | 2018 | 推荐 | 简化握手,增强安全性 |
二、TLS 1.2 握手流程
2.1 完整握手(2-RTT)
客户端 服务端
│ │
│ ── ClientHello ───────────────────────────────────────→ │
│ 支持的 TLS 版本 │
│ 支持的密码套件列表 │
│ 支持的压缩方法 │
│ 客户端随机数 (32 bytes) │
│ 支持的扩展 (SNI, ALPN, 椭圆曲线等) │
│ │
│ ←─ ServerHello ──────────────────────────────────────── │
│ 选择的 TLS 版本 │
│ 选择的密码套件 │
│ 服务端随机数 (32 bytes) │
│ │
│ ←─ Certificate ──────────────────────────────────────── │
│ 服务端证书链 (己方证书 + 中间 CA 证书) │
│ │
│ ←─ ServerKeyExchange ────────────────────────────────── │
│ (DHE/ECDHE 时发送公钥参数) │
│ │
│ ←─ ServerHelloDone ──────────────────────────────────── │
│ │
│ ── ClientKeyExchange ─────────────────────────────────→ │
│ 预主密钥 (Pre-Master Secret) 用服务端公钥加密 │
│ │
│ ── ChangeCipherSpec ──────────────────────────────────→ │
│ 通知后续使用协商密钥加密 │
│ │
│ ── Finished ──────────────────────────────────────────→ │
│ 握手消息哈希,验证密钥协商成功 │
│ │
│ ←─ ChangeCipherSpec ─────────────────────────────────── │
│ │
│ ←─ Finished ─────────────────────────────────────────── │
│ │
│ ═══════════════════════════════════════════════════════ │
│ 应用层数据(对称加密传输) │
2.2 密钥派生
预主密钥 (Pre-Master Secret)
│
▼ + 客户端随机数 + 服务端随机数
主密钥 (Master Secret) = PRF(pre_master_secret, "master secret", client_random + server_random)
│
▼ + 两个随机数
密钥块 (Key Block) = PRF(master_secret, "key expansion", server_random + client_random)
│
├── 客户端 MAC 密钥
├── 服务端 MAC 密钥
├── 客户端加密密钥
├── 服务端加密密钥
├── 客户端 IV
└── 服务端 IV
三、TLS 1.3 改进
3.1 简化握手(1-RTT)
TLS 1.3 减少了往返次数和握手消息:
客户端 服务端
│ │
│ ── ClientHello ───────────────────────────────────────→ │
│ 支持的密钥共享 (key_share) │
│ 客户端随机数 │
│ 支持的密码套件(缩减列表,仅 AEAD) │
│ │
│ ←─ ServerHello ──────────────────────────────────────── │
│ 选择的密钥共享 │
│ 服务端随机数 │
│ │
│ ←─ {EncryptedExtensions} ────────────────────────────── │
│ 加密的扩展信息 │
│ │
│ ←─ {Certificate} ────────────────────────────────────── │
│ │
│ ←─ {CertificateVerify} ──────────────────────────────── │
│ 证书签名验证 │
│ │
│ ←─ {Finished} ───────────────────────────────────────── │
│ │
│ ── {Finished} ────────────────────────────────────────→ │
│ │
│ ═══════════════════════════════════════════════════════ │
│ 应用层数据 │
改进点:
- ServerHello 后直接加密(使用 key_share 的公钥)
- 删除 RSA 密钥交换(仅支持 PFS:DHE/ECDHE)
- 删除多余消息(ChangeCipherSpec、ServerHelloDone 等)
- 握手消息签名覆盖整个握手过程(防篡改)
3.2 0-RTT 会话恢复
首次连接(1-RTT):
完成完整握手,客户端收到 NewSessionTicket
后续连接(0-RTT):
客户端 ── ClientHello + early_data ──→ 服务端
客户端直接发送加密数据(使用 PSK)!
⚠️ 0-RTT 的风险:重放攻击
- 解决方案:限制 0-RTT 只用于幂等操作
四、证书体系
4.1 X.509 证书结构
证书内容:
├── 版本号 (v3)
├── 序列号
├── 签名算法
├── 签发者 (Issuer)
├── 有效期
│ ├── 生效时间 (Not Before)
│ └── 过期时间 (Not After)
├── 主体 (Subject)
├── 主体公钥信息
│ ├── 公钥算法
│ └── 公钥值
├── 扩展项
│ ├── 主体备用名称 (SAN):DNS、IP 列表
│ ├── 密钥用途
│ ├── 扩展密钥用途
│ ├── CRL 分发点
│ └── 基本约束(是否为 CA)
└── 签名值
4.2 证书链验证
信任链验证:
服务端证书 (www.example.com)
│
▼ 签名
中间 CA 证书 (R3)
│
▼ 签名
根 CA 证书 (ISRG Root X1)
│
▼ 预置在操作系统/浏览器信任库中
信任锚
验证过程:
1. 验证证书有效期
2. 验证证书签名(用上级 CA 公钥)
3. 检查证书吊销状态(CRL/OCSP)
4. 检查域名匹配(CN 或 SAN)
5. 递归验证直到根 CA
五、Java TLS 编程
5.1 HTTPS 客户端
// Java 11+ HttpClient(自动 TLS)
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.version(HttpClient.Version.HTTP_2)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 信任所有证书(仅测试!)
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}}, new SecureRandom());
HttpClient unsafeClient = HttpClient.newBuilder()
.sslContext(sslContext)
.build();
5.2 配置 KeyStore
// 加载服务端证书和私钥
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("server.p12"), "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keyStore, "password".toCharArray());
// 加载信任库(验证客户端证书)
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream("truststore.jks"), "password".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
// 创建 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
// 创建服务端 Socket
SSLServerSocketFactory factory = sslContext.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(8443);
// 要求客户端证书(双向认证)
serverSocket.setNeedClientAuth(true);
// 限制 TLS 版本和密码套件
serverSocket.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
serverSocket.setEnabledCipherSuites(new String[]{
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
});
5.3 Netty TLS 配置
public class SslServerConfig {
public SslContext createSslContext() throws Exception {
return SslContextBuilder.forServer(
new File("server.crt"),
new File("server.key"))
.trustManager(new File("ca.crt")) // 客户端 CA
.clientAuth(ClientAuth.REQUIRE) // 要求客户端证书
.sslProvider(SslProvider.OPENSSL) // 使用 OpenSSL 引擎
.protocols("TLSv1.2", "TLSv1.3")
.ciphers(Arrays.asList(
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
), SupportedCipherSuiteFilter.INSTANCE)
.build();
}
public void addSslHandler(ChannelPipeline pipeline, SslContext sslContext) {
SSLEngine engine = sslContext.newEngine(pipeline.channel().alloc());
engine.setUseClientMode(false);
engine.setNeedClientAuth(true);
pipeline.addFirst("ssl", new SslHandler(engine));
}
}
六、密码套件解读
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
│ │ │ │ │ │ │
│ │ │ │ │ │ └── MAC/PRF 算法
│ │ │ │ │ └────── 加密模式
│ │ │ │ └───────────── 对称加密算法
│ │ │ └───────────────── 密钥交换算法
│ │ └───────────────────── 身份认证算法
│ └──────────────────────────── 基于椭圆曲线的 DH
└──────────────────────────────── TLS 协议
关键概念:
- ECDHE:临时椭圆曲线 Diffie-Hellman(前向安全)
- RSA:非对称加密,用于签名或密钥交换
- AES_256_GCM:对称加密,256 位 AES,Galois/Counter Mode
- SHA384:哈希算法,用于完整性校验
推荐配置(TLS 1.3):
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
七、证书操作
# 生成私钥
openssl genrsa -out server.key 2048
# 生成 CSR(证书签名请求)
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=www.example.com" \
-addext "subjectAltName=DNS:www.example.com,DNS:api.example.com"
# 自签名证书(开发测试用)
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
# 查看证书信息
openssl x509 -in server.crt -text -noout
# 转换为 PKCS12(Java 常用)
openssl pkcs12 -export -in server.crt -inkey server.key \
-out server.p12 -name "server" -password pass:changeit
# 查看证书链
openssl s_client -connect www.example.com:443 -showcerts
八、常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 证书过期 | 有效期已过 | 更新证书,启用自动续期 |
| 证书不匹配 | CN/SAN 不含域名 | 重新申请含正确 SAN 的证书 |
| 中间证书缺失 | 服务端未发送完整链 | 配置中间证书 |
| 协议不兼容 | 客户端/服务端版本不一致 | 配置兼容的 TLS 版本 |
| 密码套件不匹配 | 无共同支持的算法 | 调整密码套件配置 |
九、总结
| 主题 | 要点 |
|---|---|
| TLS 1.2 | 完整握手 2-RTT,兼容性好 |
| TLS 1.3 | 简化握手 1-RTT,更安全 |
| 证书链 | 服务端证书 → 中间 CA → 根 CA |
| 密钥交换 | 优先 ECDHE(前向安全) |
| 双向认证 | 服务端验证客户端证书 |
| Java 配置 | SSLContext + KeyStore + TrustManager |
TLS 是网络安全的基石。在生产环境中,应使用 TLS 1.3(或 1.2),禁用不安全的协议和密码套件,配置证书自动续期,并启用 HSTS 等安全头部。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。