SSL/TLS 最佳实践

安全的 SSL/TLS 实现对于保护传输中的数据至关重要,可防范拦截、篡改以及可能危及敏感通信机密性和完整性的中间人攻击。尽管 SSL/TLS 已被广泛采用(几乎所有现代网站都使用 HTTPS),但许多实现仍然存在因配置不当而产生的漏洞——使用已过时的协议版本(SSL 2.0、SSL 3.0、TLS 1.0、TLS 1.1),这些版本因 POODLE、BEAST 和 CRIME 等已知漏洞而被 IETF 和 PCI-DSS 等机构正式弃用;使用可被现代计算资源攻破的弱加密套件或已被破解的加密套件,如 RC4、DES、3DES 和 MD5;使用如今被认为不安全的 SHA-1 签名算法的证书;缺乏完全前向保密(PFS),这会使得在服务器私钥将来遭到泄露时被捕获的流量可被追溯解密;缺少 HSTS(HTTP Strict Transport Security),从而使得降级攻击成为可能,攻击者借此强制建立未加密的 HTTP 连接;以及关键移动应用中证书锁定(certificate pinning)不当或缺失,使得攻击者可通过在设备上安装恶意证书来进行拦截。经过优化的 SSL/TLS 配置不仅能改善安全态势,还会影响性能(TLS 1.3 的握手比 TLS 1.2 更快)、兼容性(非常老旧的客户端可能不支持现代配置)以及合规性(LGPD、GDPR、PCI-DSS 要求对传输中的数据进行强加密)。本文提供了遵循行业最佳实践以及 NIST、Mozilla SSL Configuration Generator 和 OWASP 建议来实现 SSL/TLS 的详细技术指南。

协议版本:TLS 1.2 和 TLS 1.3

在所有服务器上完全禁用 SSL 2.0、SSL 3.0、TLS 1.0 和 TLS 1.1——这些版本存在已记录的漏洞,即使为了与旧版客户端兼容也不应使用。将你的 Web 服务器(nginx、Apache、IIS)或负载均衡器(AWS ALB、Azure Application Gateway)配置为仅接受 TLS 1.2 和 TLS 1.3。TLS 1.3 于 2018 年作为 RFC 8446 获得批准,相较于先前版本提供了显著改进:更快的握手(1-RTT 而非 2-RTT,并支持用于会话恢复(resumed sessions)的 0-RTT)、移除不安全的加密套件(不再支持 RSA 密钥交换、CBC 模式加密、RC4、3DES)、所有加密套件中强制的前向保密,以及隐藏协议协商元数据的加密握手。在 nginx 中,使用 "ssl_protocols TLSv1.2 TLSv1.3;" 进行配置。在 Apache 中,使用 "SSLProtocol -all +TLSv1.2 +TLSv1.3"。AWS ALB 允许选择定义协议和加密算法的安全策略(security policy)——为现代环境选择 "ELBSecurityPolicy-TLS13-1-2-2021-06",如果你需要支持一些较旧的客户端,则选择 "ELBSecurityPolicy-2016-08"。监控你的访问日志以确定仍有多少客户端使用 TLS 1.0/1.1——如果该数字微不足道(低于流量的 0.1%),你可以将其禁用而不会产生实质性影响。对于你掌控两端端点的 B2B API,应专门要求使用 TLS 1.3。请记住,协议版本是在握手期间协商确定的——服务器应公布所支持的版本,客户端则选择它同样支持的最高版本。

加密套件:ECDHE 和 AES-GCM

加密套件定义了在 TLS 连接期间用于密钥交换、身份验证、对称加密和 HMAC 的算法——即使使用 TLS 1.2+,选择不当也可能危及整个通信的安全性。优先选择密钥交换使用 ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)的加密套件,因为它提供完全前向保密;对称加密优先选择 AES-GCM(Galois/Counter Mode)或 ChaCha20-Poly1305,因为它们是 AEAD(Authenticated Encryption with Associated Data)加密算法,可同时保证机密性和完整性。安全加密套件示例:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 或 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256。应完全避免:使用 RC4 的加密套件(易受统计偏差攻击)、出口加密(export ciphers,因旧的出口管制法规而限制为 40 或 56 位)、NULL 加密(无加密)、DES 和 3DES(块大小过小,易受 Sweet32 攻击)、TLS 1.0-1.1 中的 CBC 模式(易受 BEAST 和 Lucky13 攻击),以及不带 DHE/ECDHE 的 RSA 密钥交换(无前向保密)。在 nginx 中,配置 "ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';" 以及 "ssl_prefer_server_ciphers on;" 以强制使用服务器端的顺序。使用 ssllabs.com/ssltest 等工具来验证你的配置并获得 A+ 评级。对于 TLS 1.3,加密套件经过简化且默认更安全(TLS_AES_128_GCM_SHA256、TLS_AES_256_GCM_SHA384、TLS_CHACHA20_POLY1305_SHA256)。

HSTS:HTTP Strict Transport Security

HSTS 是一种指示浏览器对特定域名始终使用 HTTPS 的机制,可防范 SSL stripping 攻击——在这种攻击中,中间人会在 301 重定向到 HTTPS 发生之前拦截第一个请求,从而强制建立未加密的 HTTP 连接。通过在服务器的所有 HTTPS 响应中添加 "Strict-Transport-Security" 标头来实现 HSTS:"Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"。max-age 参数(以秒为单位)定义浏览器应记住仅使用 HTTPS 的时长——31536000 秒 = 1 年是推荐值。"includeSubDomains" 将该策略应用于所有子域名(请谨慎使用,因为你需要确保所有子域名都支持 HTTPS)。"preload" 表示你希望将你的域名纳入由浏览器维护的 HSTS preload list——这是一份硬编码的域名列表,列表中的域名即使在首次访问、收到任何 HSTS 标头之前也必须始终使用 HTTPS。要被纳入 preload list,请在确认以下各项后于 hstspreload.org 提交你的域名:整个网站在 HTTPS 上完美运行、你拥有覆盖该域名及其子域名的有效证书、你将所有 HTTP 流量重定向到 HTTPS、你在基础域名上提供带有 includeSubDomains 和 preload 指令的 HSTS 标头,且 max-age 大于或等于 31536000。重要提示:preload 是单向决定——将域名从列表中移除可能需要数月时间,如果你届时无法再支持 HTTPS,在此期间将中断用户的访问。在 nginx 中:"add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;"。在 Apache 中:"Header always set Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload'"。

关键应用的证书锁定(Certificate Pinning)

证书锁定(certificate pinning)是一种技术,你的应用(尤其是移动应用和关键 API)会硬编码或嵌入服务器预期证书的哈希/指纹,在 TLS 握手期间验证所出示的证书与存储的 pin 是否匹配——这可防范以下攻击:攻击者(通过恶意软件或物理访问)在受害者设备上安装恶意证书,并拦截通常会被操作系统默认 CA 链信任的 HTTPS 流量。锁定有两种类型:锁定叶子证书(leaf certificate,针对该特定证书,证书到期/续期时必须更新),或锁定 CA 的公钥/公钥(更灵活,只要由同一 CA 签发即允许轮换证书)。在移动应用中使用原生框架实现锁定:iOS Network 框架允许通过 URLSessionDelegate 回调进行锁定,Android 使用带有 pin-set 元素的 Network Security Configuration XML,React Native 可使用 react-native-ssl-pinning 库。对于 API,可考虑双向 TLS(mTLS),即客户端也向服务器出示证书,并通过锁定进行验证。注意:如果你失去对已锁定(pinned)证书的访问权限且没有回退机制,错误的锁定可能会使你的应用瘫痪——务必实现:备份 pin(第二个有效的证书/CA)、空中(over-the-air)pin 更新,以及在强制执行 pin 之前的宽限期,以便在紧急情况下回滚。在可能的情况下使用公钥锁定(Public Key Pinning)而非证书锁定(Certificate Pinning)(对续期更具弹性)。在 Android 上使用 SHA-256 摘要和公钥的 base64 哈希的配置示例。openssl 等工具可以从证书中提取公钥哈希,以用于 pin。

完全前向保密(Perfect Forward Secrecy,PFS)

完全前向保密(Perfect Forward Secrecy)是一种密码学特性:即使服务器的长期私钥(SSL 证书的私钥)遭到泄露,也无法解密此前被捕获的会话——这是通过为每个会话动态生成临时密钥(ephemeral keys)并在使用后销毁来实现的。如果没有 PFS,攻击者捕获全部加密流量(借助网络分流器 network taps 轻而易举),并在数年后成功窃取你的私钥(通过数据泄露、内部威胁、法院命令),他便可以回到存储的 pcap 文件并解密所有历史会话——这种情形被称为"追溯解密"(retrospective decryption)。PFS 通过 Diffie-Hellman 算法生成唯一的会话密钥(session keys)来防止这种情况,这些密钥的派生不依赖于证书的私钥——即使私钥遭到泄露,先前的会话密钥仍然安全,因为它们是使用已不复存在的临时组件生成的。要确保 PFS,密钥交换只使用带 DHE(Diffie-Hellman Ephemeral)或 ECDHE(Elliptic Curve DHE)的加密套件——避免使用 RSA 密钥交换,它不提供 PFS,因为会话密钥是用服务器的公钥加密、用私钥解密的。在 TLS 1.3 中,PFS 是强制性的(所有加密套件都使用 ECDHE)。在 TLS 1.2 中,将服务器配置为优先选择 ECDHE 加密而非 RSA。使用以下命令进行验证:openssl s_client -connect yoursite.com:443 -cipher 'ECDHE'——如果连接成功,则说明 PFS 正常工作。PFS 的另一项好处是合规性——许多框架(PCI-DSS、NIST)建议或要求使用 PFS 来保护敏感数据。缺点:略有性能开销,因为 DHE 运算在计算上比 RSA 更昂贵,但借助现代硬件(AES-NI、CPU 加密加速),其影响可忽略不计。