网络安全:TLS/SSL、证书与加密通信

深入 TLS 握手流程、证书链验证与密码套件协商,掌握 Java 中 SSLContext、KeyStore 与双向认证的配置

TLS(Transport Layer Security)及其前身 SSL(Secure Sockets Layer)是保障网络通信安全的核心协议。理解 TLS 的握手流程、证书体系和密码学基础,是设计安全系统的必备知识。

一、TLS 演进

版本年份状态说明
SSL 2.01995废弃存在严重安全漏洞
SSL 3.01996废弃POODLE 攻击
TLS 1.01999废弃被认为不安全
TLS 1.12006废弃BEAST 攻击风险
TLS 1.22008主流当前最广泛使用的版本
TLS 1.32018推荐简化握手,增强安全性

二、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 等安全头部。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「network」更多文章

  1. 负载均衡算法与高可用架构
  2. DNS 系统与智能解析
  3. WebSocket 协议与实时通信