传输层安全:TLS/SSL协议详解

传输层安全,说白了就是给数据通信加一把锁。我做了这么多年支付系统,最深的体会就是:没有TLS的支付系统,就像没穿衣服走在街上——随时可能被劫持。

先说说TLS和SSL的关系。SSL是前辈,TLS是接班人。现在大家说的TLS,其实已经是1.2和1.3版本的天下了。我个人习惯直接跳过SSL,因为SSL 3.0已经被证明不安全了。你想想看,2014年的POODLE攻击,就是专门针对SSL 3.0的。我在项目中遇到过一家银行,还在用SSL 3.0,我当场就让他们升级了。

TLS握手过程

TLS握手,就是客户端和服务器互相确认身份、协商加密参数的过程。我画了一张图,帮你理清这个流程:

客户端 服务器 ① ClientHello (TLS版本、密码套件、随机数) ② ServerHello + 证书 + ServerHelloDone (服务器证书、密钥交换参数) ③ 客户端验证证书 (验证CA签名、域名、有效期) ④ ClientKeyExchange + ChangeCipherSpec (发送预主密钥,切换加密) ⑤ ChangeCipherSpec + Finished (服务器确认,握手完成) 🔒 加密通信开始 TLS 1.2 握手流程

嗯,这里要注意:TLS 1.3 简化了握手过程,从2-RTT降到了1-RTT。我建议新项目直接上TLS 1.3,性能提升很明显。

密码套件选择

密码套件就是一组加密算法的组合。我见过太多人随便选,结果踩坑。下面这张表是我常用的推荐:

套件名称 密钥交换 认证 加密 MAC 推荐等级
TLS_AES_128_GCM_SHA256 ECDHE RSA/ECDSA AES-128-GCM AEAD ⭐⭐⭐⭐⭐
TLS_AES_256_GCM_SHA384 ECDHE RSA/ECDSA AES-256-GCM AEAD ⭐⭐⭐⭐⭐
TLS_CHACHA20_POLY1305_SHA256 ECDHE RSA/ECDSA CHACHA20-POLY1305 AEAD ⭐⭐⭐⭐
TLS_RSA_WITH_AES_128_CBC_SHA RSA RSA AES-128-CBC HMAC-SHA1 ❌ 不推荐

核心原则:优先选择ECDHE密钥交换 + AEAD加密模式。不要用RSA密钥交换,它不支持前向安全性。

双向证书认证

双向认证,就是客户端也要出示证书。这在支付系统中很常见,尤其是银行间的交易。我做过一个跨境支付项目,对方银行要求必须双向认证,折腾了好几天才调通。

为什么会需要双向认证?说白了,就是服务器也要确认客户端的身份。你想想看,如果只有服务器出示证书,那谁都可以假装是合法客户端。在支付场景里,这风险太大了。

配置示例

以Nginx为例,双向认证的配置是这样的:

server {
    listen 443 ssl;
    server_name api.payment.com;
    
    # 服务器证书
    ssl_certificate /etc/ssl/certs/server.crt;
    ssl_certificate_key /etc/ssl/private/server.key;
    
    # 客户端证书验证
    ssl_client_certificate /etc/ssl/certs/ca.crt;
    ssl_verify_client on;
    ssl_verify_depth 2;
    
    # 验证失败时的处理
    ssl_client_certificate /etc/ssl/certs/ca.crt;
    ssl_verify_client optional;
    
    location / {
        # 获取客户端证书信息
        proxy_set_header X-SSL-Client-Cert $ssl_client_cert;
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
        proxy_pass http://backend;
    }
}

我的经验:双向认证的证书链验证一定要做完整。我曾经遇到一个案例,对方只验证了客户端证书本身,没验证CA链,结果被中间人攻击了。

HTTPS强制策略

HTTPS强制策略,就是确保所有流量都走HTTPS,不留任何HTTP的入口。我见过太多系统,只对登录页面做了HTTPS,其他页面还是HTTP。结果呢?cookie被截获,session被劫持。

强制HTTPS有几种方式:

  • 服务器端重定向:所有HTTP请求301/302到HTTPS
  • HSTS:告诉浏览器以后只走HTTPS
  • CSP:内容安全策略,限制资源加载
  • Secure Cookie:cookie只通过HTTPS传输

Nginx强制HTTPS配置

server {
    listen 80;
    server_name api.payment.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.payment.com;
    
    # SSL配置
    ssl_certificate /etc/ssl/certs/server.crt;
    ssl_certificate_key /etc/ssl/private/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    
    # 安全头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    
    location / {
        proxy_pass http://backend;
    }
}

注意:301重定向会暴露URL参数。如果URL中包含敏感信息,建议用307重定向。

HSTS部署

HSTS(HTTP Strict Transport Security)是强制HTTPS的终极方案。它告诉浏览器:以后访问这个网站,只走HTTPS,不要尝试HTTP。

我记得有一次,一个客户说他们的HSTS部署后,部分用户访问不了。排查了半天,发现是HSTS的max-age设得太短,浏览器还没来得及缓存。嗯,这里要注意:max-age至少要设半年以上。

HSTS头格式

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

各参数含义:

  • max-age:缓存时间,单位秒。建议31536000(1年)
  • includeSubDomains:是否包含子域名
  • preload:申请加入浏览器预加载列表

HSTS预加载

HSTS预加载是更狠的一招。你把域名提交到浏览器的预加载列表,浏览器在第一次访问前就知道这个域名必须走HTTPS。我建议所有支付系统都申请预加载。

申请条件:

  1. 有效的HTTPS证书
  2. 所有子域名都支持HTTPS
  3. HSTS头包含preload指令
  4. max-age至少10886400秒(18周)

部署检查清单:

  • ✅ 所有页面都支持HTTPS
  • ✅ 没有混合内容(Mixed Content)
  • ✅ HSTS头正确配置
  • ✅ 证书链完整
  • ✅ 密码套件安全
  • ✅ 定期SSL Labs测试

常见问题与避坑

我总结几个常见问题:

  • 证书过期:我建议用自动续签工具,比如certbot。手动续签太容易忘了。
  • 中间证书缺失:有些服务器只配了叶子证书,没配中间证书。这会导致某些客户端验证失败。
  • HSTS部署后回退:一旦部署HSTS,想回退就难了。浏览器会记住HSTS策略,直到max-age过期。
  • 性能问题:TLS握手确实有开销。我建议用会话复用和OCSP Stapling来优化。

我的建议:先用低max-age测试,比如5分钟。确认没问题后,再逐步增加到1年。这样即使出问题,影响范围也小。

好了,传输层安全就讲到这里。记住一句话:没有TLS的支付系统,就是在裸奔。该配的配齐,该测的测好,别给自己留隐患。

专注资料整理