# 第六章:TLS 协议深入解析 > "TLS 是互联网安全的基石,每一次 HTTPS 请求背后都有它的身影。" ```{mermaid} mindmap root((TLS 协议)) 演进历史 SSL 3.0 TLS 1.0/1.1 TLS 1.2 TLS 1.3 握手流程 密钥交换 身份验证 密码协商 安全特性 前向保密 0-RTT AEAD mTLS 双向认证 Service Mesh SPIFFE ``` ## 6.1 TLS 的演进 | 版本 | 年份 | 状态 | 说明 | |------|------|------|------| | SSL 2.0 | 1995 | ❌ 废弃 | 严重安全缺陷 | | SSL 3.0 | 1996 | ❌ 废弃 | POODLE 攻击 | | TLS 1.0 | 1999 | ❌ 废弃 | BEAST 攻击 | | TLS 1.1 | 2006 | ❌ 废弃 | 不支持 AEAD | | TLS 1.2 | 2008 | ✅ 广泛使用 | 支持 GCM、SHA-256 | | TLS 1.3 | 2018 | ✅ 推荐 | 1-RTT、移除不安全算法 | ## 6.2 TLS 1.2 握手流程 ```{mermaid} sequenceDiagram participant C as 客户端 participant S as 服务器 C->>S: 1. ClientHello (密码套件、随机数、SNI) S->>C: 2. ServerHello (选择的密码套件、随机数) S->>C: 3. Certificate (服务器证书) S->>C: 4. ServerKeyExchange (DH 参数) S-->>C: 5. CertificateRequest (mTLS 时) S->>C: 6. ServerHelloDone C-->>S: 7. Certificate (客户端证书, mTLS 时) C->>S: 8. ClientKeyExchange (DH 公钥) C-->>S: 9. CertificateVerify (mTLS 时) C->>S: 10. ChangeCipherSpec + Finished S->>C: 11. ChangeCipherSpec + Finished Note over C,S: 加密通信开始 (2-RTT) ``` ### 密码套件(Cipher Suite) TLS 1.2 密码套件格式:`TLS_密钥交换_WITH_加密算法_哈希算法` ``` 推荐的 TLS 1.2 密码套件: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 │ │ │ │ │ │ │ │ │ │ │ └── 哈希:SHA-384 │ │ │ │ └─────── 模式:GCM(认证加密) │ │ │ └──────────── 加密:AES-256 │ │ └───────────────────── 认证:RSA 证书 │ └──────────────────────────── 密钥交换:ECDHE(前向保密) └───────────────────────────────── 协议:TLS ``` ## 6.3 TLS 1.3 的改进 TLS 1.3 做了大幅简化和安全增强: ```{mermaid} sequenceDiagram participant C as 客户端 participant S as 服务器 C->>S: 1. ClientHello (密码套件、密钥共享、SNI) S->>C: 2. ServerHello (密钥共享) S->>C: 3. {EncryptedExtensions} S->>C: 4. {Certificate} S->>C: 5. {CertificateVerify} S->>C: 6. {Finished} C->>S: 7. {Finished} Note over C,S: 加密通信开始 (1-RTT) ``` ### TLS 1.3 vs 1.2 | 特性 | TLS 1.2 | TLS 1.3 | |------|---------|---------| | 握手延迟 | 2-RTT | 1-RTT(支持 0-RTT) | | 密钥交换 | RSA/DHE/ECDHE | 仅 ECDHE/DHE | | 对称加密 | CBC/GCM | 仅 AEAD(GCM/ChaCha20) | | 哈希算法 | MD5/SHA-1/SHA-256 | 仅 SHA-256/SHA-384 | | 前向保密 | 可选 | 强制 | | 0-RTT | 不支持 | 支持(有重放风险) | | 压缩 | 支持(CRIME 攻击) | 移除 | | 重协商 | 支持 | 移除 | ### TLS 1.3 密码套件 ``` TLS 1.3 只有 5 个密码套件: TLS_AES_256_GCM_SHA384 ← 推荐 TLS_AES_128_GCM_SHA256 ← 推荐 TLS_CHACHA20_POLY1305_SHA256 ← 移动设备推荐 TLS_AES_128_CCM_SHA256 TLS_AES_128_CCM_8_SHA256 ``` ## 6.4 SNI 与 ALPN ### SNI(Server Name Indication) SNI 允许客户端在 TLS 握手时指定要访问的主机名,使同一 IP 可以托管多个 HTTPS 站点: ``` ClientHello: server_name: example.com ← SNI 扩展 注意:SNI 是明文传输的! ECH(Encrypted Client Hello)是 TLS 1.3 的扩展, 可以加密 SNI,防止网络监控者知道你访问了哪个网站。 ``` ### ALPN(Application-Layer Protocol Negotiation) ALPN 在 TLS 握手时协商应用层协议: ``` ClientHello: alpn: ["h2", "http/1.1"] ← 支持 HTTP/2 和 HTTP/1.1 ServerHello: alpn: "h2" ← 选择 HTTP/2 ``` ## 6.5 TLS 常见攻击 | 攻击 | 年份 | 影响 | 防御 | |------|------|------|------| | BEAST | 2011 | TLS 1.0 CBC | 升级到 TLS 1.2+ | | CRIME | 2012 | TLS 压缩 | 禁用 TLS 压缩 | | Heartbleed | 2014 | OpenSSL 内存泄露 | 更新 OpenSSL | | POODLE | 2014 | SSL 3.0 | 禁用 SSL 3.0 | | FREAK | 2015 | 出口级密码 | 禁用弱密码套件 | | Logjam | 2015 | 弱 DH 参数 | 使用 2048+ 位 DH | | ROBOT | 2017 | RSA 密钥交换 | 使用 ECDHE | | DROWN | 2016 | SSLv2 跨协议 | 禁用 SSLv2 | ## 6.6 Nginx TLS 最佳实践 ```nginx server { listen 443 ssl http2; server_name example.com; # 证书 ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; # 协议版本:仅 TLS 1.2 和 1.3 ssl_protocols TLSv1.2 TLSv1.3; # 密码套件(服务器优先) ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305'; # DH 参数(2048 位以上) ssl_dhparam /etc/nginx/ssl/dhparam.pem; # OCSP Stapling ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/nginx/ssl/chain.pem; # Session 缓存 ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; # HSTS(HTTP Strict Transport Security) add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; # 安全头 add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; } ``` ## 6.7 Python TLS 编程 ```python import ssl import socket def create_tls_client(hostname: str, port: int = 443) -> ssl.SSLSocket: """创建安全的 TLS 客户端连接""" context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) # 设置最低 TLS 版本 context.minimum_version = ssl.TLSVersion.TLSv1_2 # 加载系统 CA 证书 context.load_default_certs() # 启用证书验证 context.verify_mode = ssl.CERT_REQUIRED context.check_hostname = True # 创建连接 sock = socket.create_connection((hostname, port)) tls_sock = context.wrap_socket(sock, server_hostname=hostname) # 打印连接信息 print(f"协议版本: {tls_sock.version()}") print(f"密码套件: {tls_sock.cipher()}") cert = tls_sock.getpeercert() print(f"证书主体: {cert['subject']}") print(f"证书有效期: {cert['notBefore']} - {cert['notAfter']}") return tls_sock # mTLS 客户端 def create_mtls_client(hostname: str, port: int, client_cert: str, client_key: str, ca_cert: str) -> ssl.SSLSocket: """创建 mTLS 客户端连接""" context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.minimum_version = ssl.TLSVersion.TLSv1_2 # 加载客户端证书 context.load_cert_chain(client_cert, client_key) # 加载 CA 证书 context.load_verify_locations(ca_cert) context.verify_mode = ssl.CERT_REQUIRED context.check_hostname = True sock = socket.create_connection((hostname, port)) return context.wrap_socket(sock, server_hostname=hostname) ``` ## 6.8 Java TLS 编程 ```java // Java TLS 客户端 import javax.net.ssl.*; import java.security.KeyStore; import java.io.*; public class TlsClient { public static SSLSocket createTlsConnection(String host, int port) throws Exception { SSLContext ctx = SSLContext.getInstance("TLSv1.3"); ctx.init(null, null, null); SSLSocketFactory factory = ctx.getSocketFactory(); SSLSocket socket = (SSLSocket) factory.createSocket(host, port); socket.setEnabledProtocols(new String[]{"TLSv1.3", "TLSv1.2"}); socket.startHandshake(); System.out.println("Protocol: " + socket.getSession().getProtocol()); System.out.println("Cipher: " + socket.getSession().getCipherSuite()); return socket; } // mTLS 客户端 public static SSLSocket createMtlsConnection( String host, int port, String keystorePath, String keystorePass, String truststorePath, String truststorePass) throws Exception { // 加载客户端证书 KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new FileInputStream(keystorePath), keystorePass.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(keyStore, keystorePass.toCharArray()); // 加载信任的 CA 证书 KeyStore trustStore = KeyStore.getInstance("JKS"); trustStore.load(new FileInputStream(truststorePath), truststorePass.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(trustStore); SSLContext ctx = SSLContext.getInstance("TLSv1.3"); ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); SSLSocket socket = (SSLSocket) ctx.getSocketFactory().createSocket(host, port); socket.startHandshake(); return socket; } } ``` ## 6.9 Go TLS 编程 ```go package main import ( "crypto/tls" "crypto/x509" "fmt" "os" ) // TLS 客户端 func createTLSConn(host string, port int) (*tls.Conn, error) { conf := &tls.Config{ MinVersion: tls.VersionTLS12, CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, }, } addr := fmt.Sprintf("%s:%d", host, port) conn, err := tls.Dial("tcp", addr, conf) if err != nil { return nil, err } state := conn.ConnectionState() fmt.Printf("Protocol: %x\nCipher: %x\n", state.Version, state.CipherSuite) return conn, nil } // mTLS 客户端 func createMTLSConn(host string, port int, certFile, keyFile, caFile string) (*tls.Conn, error) { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, fmt.Errorf("load client cert: %w", err) } caCert, err := os.ReadFile(caFile) if err != nil { return nil, fmt.Errorf("read CA cert: %w", err) } caPool := x509.NewCertPool() caPool.AppendCertsFromPEM(caCert) conf := &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caPool, MinVersion: tls.VersionTLS12, } return tls.Dial("tcp", fmt.Sprintf("%s:%d", host, port), conf) } ``` ## 6.10 TLS 证书验证与调试工具 在排查 TLS 连接问题时,以下工具是不可或缺的利器。 ### openssl s_client `openssl s_client` 是最常用的 TLS 调试工具,可以模拟客户端与服务器建立 TLS 连接,查看完整的握手过程和证书链。 ```bash # 基本连接测试:查看证书链和握手信息 openssl s_client -connect example.com:443 -servername example.com # 指定 TLS 版本 openssl s_client -connect example.com:443 -tls1_2 openssl s_client -connect example.com:443 -tls1_3 # 查看完整证书链(PEM 格式) openssl s_client -connect example.com:443 -showcerts # 验证证书链是否正确 openssl s_client -connect example.com:443 -verify 5 -CAfile /etc/ssl/certs/ca-certificates.crt # 检查证书过期时间 echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates # 查看证书详细信息(主体、颁发者、SAN 等) echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -text # mTLS 调试:携带客户端证书连接 openssl s_client -connect example.com:443 \ -cert client.crt -key client.key -CAfile ca.crt # 检查支持的密码套件 openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384' ``` **关键输出字段解读:** ``` # 成功的连接会显示: Verify return code: 0 (ok) ← 证书验证通过 --- SSL-Session: Protocol : TLSv1.3 ← 协商的协议版本 Cipher : TLS_AES_256_GCM_SHA384 ← 协商的密码套件 --- Certificate chain 0 s:CN = example.com ← 服务器证书 i:C = US, O = Let's Encrypt, CN = R3 ← 颁发者 1 s:C = US, O = Let's Encrypt, CN = R3 ← 中间 CA i:C = US, O = Internet Security Research Group, CN = ISRG Root X1 ``` ### curl --verbose `curl` 的 verbose 模式可以展示 TLS 握手的关键信息,适合快速验证 HTTPS 端点。 ```bash # 查看 TLS 握手详情 curl -v https://example.com 2>&1 | head -30 # 更详细的 TLS 调试信息(包括密钥交换细节) curl -v --trace-ascii /dev/stdout https://example.com # 指定 TLS 版本 curl --tlsv1.2 --tls-max 1.2 -v https://example.com curl --tlsv1.3 -v https://example.com # 指定密码套件 curl --ciphers 'ECDHE-RSA-AES256-GCM-SHA384' -v https://example.com # 使用自定义 CA 证书验证 curl --cacert /path/to/ca.crt -v https://internal-service.example.com # mTLS 连接 curl --cert client.crt --key client.key --cacert ca.crt \ -v https://mtls.example.com # 忽略证书验证(仅调试用,切勿用于生产!) curl -k -v https://self-signed.example.com ``` **典型输出解读:** ``` * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * Server certificate: * subject: CN=example.com * start date: Jan 1 00:00:00 2025 GMT * expire date: Apr 1 00:00:00 2025 GMT * issuer: C=US; O=Let's Encrypt; CN=R3 * SSL certificate verify ok. ``` ### testssl.sh [testssl.sh](https://github.com/drwetter/testssl.sh) 是一个功能全面的 TLS 安全扫描工具,可以检测服务器的协议支持、密码套件、已知漏洞等。 ```bash # 安装 git clone --depth 1 https://github.com/drwetter/testssl.sh.git cd testssl.sh # 完整扫描 ./testssl.sh example.com # 仅检查协议支持 ./testssl.sh -p example.com # 仅检查密码套件 ./testssl.sh -E example.com # 仅检查已知漏洞(Heartbleed、POODLE、ROBOT 等) ./testssl.sh -U example.com # 检查证书信息 ./testssl.sh -S example.com # 输出 JSON 格式(便于自动化处理) ./testssl.sh --jsonfile result.json example.com # 批量扫描(从文件读取目标列表) ./testssl.sh --file targets.txt ``` ### 其他实用命令 ```bash # 查看本地证书文件内容 openssl x509 -in cert.pem -noout -text # 验证证书与私钥是否匹配 openssl x509 -noout -modulus -in cert.pem | openssl md5 openssl rsa -noout -modulus -in key.pem | openssl md5 # 两个 MD5 值应相同 # 验证证书链完整性 openssl verify -CAfile ca.crt -untrusted intermediate.crt server.crt # 将 PFX/P12 转换为 PEM openssl pkcs12 -in cert.pfx -out cert.pem -nodes # 生成自签名证书(测试用) openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \ -sha256 -days 365 -nodes -subj '/CN=localhost' # 使用 nmap 扫描 TLS 配置 nmap --script ssl-enum-ciphers -p 443 example.com ``` ### TLS 调试流程图 ```{mermaid} flowchart TD A[TLS 连接失败] --> B{错误类型?} B -->|证书过期| C[检查证书有效期
openssl x509 -noout -dates] B -->|证书不信任| D[检查证书链
openssl s_client -showcerts] B -->|主机名不匹配| E[检查 SAN/CN
openssl x509 -noout -text] B -->|协议版本不兼容| F[检查支持的协议
openssl s_client -tls1_2/-tls1_3] B -->|密码套件不匹配| G[检查密码套件
testssl.sh -E] B -->|mTLS 认证失败| H[检查客户端证书
openssl s_client -cert -key] C --> I[更新证书] D --> J[补全中间 CA 证书] E --> K[重新签发含正确 SAN 的证书] F --> L[升级服务器/客户端 TLS 版本] G --> M[调整密码套件配置] H --> N[检查客户端证书链和私钥] ``` ## 6.11 小结 - TLS 1.3 是当前推荐版本:1-RTT 握手、强制前向保密、仅 AEAD 加密 - **mTLS** 是微服务安全和零信任的基础,SPIFFE/SPIRE 自动化了 mTLS 证书管理 - 正确配置 TLS 至关重要:禁用旧版本、选择安全密码套件、启用 HSTS - TLS 1.3 的 ECH 扩展将进一步保护用户隐私 - **多语言 TLS 编程**:Python(ssl 模块)、Java(JSSE/SSLContext)、Go(crypto/tls)均提供了完善的 TLS 和 mTLS 支持 - **善用调试工具**:`openssl s_client` 查看握手细节、`curl -v` 快速验证、`testssl.sh` 全面扫描,是排查 TLS 问题的三板斧