HTTPS 与 TLS 基础

深入讲解TLS握手过程、证书体系、HSTS配置以及HTTPS安全最佳实践。

HTTPS 与 TLS 基础

概述

2017年,Chrome浏览器宣布将HTTP网站标记为"不安全"。这一决定标志着HTTPS从可选优化变成了行业标准。如今,如果你的网站还在使用HTTP,用户会看到醒目的安全警告,大量浏览器功能也会受限。

HTTPS不仅仅是给网站加一把锁那么简单。它解决了网络通信中的三个核心安全问题:机密性(内容加密,第三方无法窃听)、完整性(防止内容被篡改)、身份认证(证明你访问的确实是真实的网站,而非伪装的钓鱼网站)。

理解HTTPS的工作原理对于前端工程师至关重要。尽管TLS握手发生在传输层,但证书配置、安全头设置、混合内容处理等问题直接影响前端应用的安全性。一个配置不当的HTTPS站点可能仍然存在中间人攻击、证书伪造或数据泄露风险。

本节将从TLS握手过程开始,深入讲解证书体系、HSTS预加载、混合内容处理和TLS性能优化。我们将提供可落地到工程实践的配置指南,帮助你构建安全的前端应用基础设施。

目标

  • 深入理解TLS握手过程和密钥交换原理
  • 掌握证书类型选择和Let's Encrypt自动化配置
  • 学会正确配置HSTS并申请预加载
  • 识别和处理混合内容问题
  • 理解TLS性能优化策略

知识体系

1. TLS 握手过程

TLS(Transport Layer Security)是HTTPS的加密层,其前身的SSL(Secure Sockets Layer)因安全漏洞已被废弃。理解TLS握手过程是理解整个HTTPS安全模型的基础。

TLS握手是客户端和服务端协商加密参数、建立安全连接的过程。在握手完成之前,所有数据传输都是明文的。握手的目标是:商定加密算法、验证服务端身份(可选验证客户端)、建立会话密钥。

TLS 1.3 握手(简化)

TLS 1.3是最新版本,相比1.2有显著的性能提升。TLS 1.3的握手只需要1-RTT(Round Trip Time,往返时间),而TLS 1.2需要2-RTT。更激进的是1-RTT模式下,客户端在第一次请求中就携带了密钥交换信息,服务端可以直接计算会话密钥,无需等待。

Client                                  Server
  │                                        │
  ├── ClientHello ────────────────────────→│
  │   支持的密码套件、TLS 版本、随机数        │
  │   Key Share(DH 公钥)                  │
  │                                        │
  │←── ServerHello ────────────────────────┤
  │    选定的密码套件、随机数                 │
  │    Key Share(DH 公钥)                  │
  │    {EncryptedExtensions}               │
  │    {Certificate}                        │
  │    {CertificateVerify}                  │
  │    {Finished}                           │
  │                                        │
  ├── {Finished} ─────────────────────────→│
  │                                        │
  │←── 加密的应用数据 ─────────────────────→│

TLS 1.3 只需 1-RTT 完成握手(1.2 需要 2-RTT)
支持 0-RTT 恢复(PSK 模式),但有重放风险

TLS 1.3的0-RTT模式允许客户端在第一次请求中就发送加密数据,适用于已经建立过会话的场景。这可以减少连接建立的延迟,但存在重放攻击风险——攻击者可能截获并重放之前的0-RTT数据。因此,0-RTT模式适用于对重放不敏感的请求(如GET请求),不适用于涉及关键操作的POST请求。

密钥交换简述

TLS使用混合加密体系:非对称加密用于密钥交换和身份认证,对称加密用于实际数据传输。

非对称加密(握手阶段):
- 用于密钥交换和身份认证
- 算法:ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)
- 每次连接生成临时密钥对,提供前向安全性

对称加密(数据传输):
- 用于实际数据的加密和解密
- 算法:AES-128-GCM / AES-256-GCM / ChaCha20-Poly1305
- 使用握手阶段协商的对称密钥

前向安全性(Forward Secrecy)是现代TLS配置的重要特性。它的含义是:即使攻击者未来获取了服务端的长期密钥,他们也无法解密之前的通讯。这是因为TLS 1.3使用的ECDHE(Ephemeral Diffie-Hellman)每次连接都会生成新的临时密钥对,之前的通讯只能由那次连接的临时密钥解密。

2. 证书体系

数字证书是HTTPS身份认证的基础。证书由证书颁发机构(CA)签发,证明特定公钥属于特定域名。浏览器内置了主流CA的根证书,能够验证证书链的真实性。

证书类型

不同验证级别的证书适用于不同场景。选择合适的证书类型是安全与成本之间的权衡。

类型 验证级别 颁发时间 适用场景
DV (Domain Validation) 仅验证域名所有权 分钟级 个人网站、小型项目
OV (Organization Validation) 验证组织身份 数天 企业网站
EV (Extended Validation) 严格验证组织 数周 金融、电商(已不显示绿色地址栏)

DV证书只验证申请人对域名的控制权,通常通过在域名下放置特定文件或添加DNS记录来验证。DV证书的签发已经完全自动化,这也是Let's Encrypt等免费CA能够运作的基础。

OV证书在DV的基础上增加了对组织身份的验证。浏览器通常不显示额外的视觉提示,但在证书详情中可以查看组织名称。

EV证书曾经以绿色地址栏闻名,提供最高级别的身份验证。但浏览器厂商后来移除了绿色地址栏的显示,EV证书的优势主要体现在证书详情中显示的完整组织信息。

Let's Encrypt 自动化证书

Let's Encrypt是Mozilla主导的免费CA项目,通过ACME协议实现了证书申请和续期的完全自动化。这使得小型网站和个人开发者也能零成本使用HTTPS。

# 使用 certbot 获取证书
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com

# 自动续期
sudo certbot renew --dry-run

# 证书文件位置
# /etc/letsencrypt/live/example.com/fullchain.pem  — 证书链
# /etc/letsencrypt/live/example.com/privkey.pem    — 私钥

Certbot是Let's Encrypt官方推荐的客户端。生产环境中,应该配置自动续期的cron job,因为Let's Encrypt证书有效期为90天。虽然Certbot默认会创建自动续期脚本,但需要确保系统重启后cron服务正常运行。

Nginx HTTPS 配置

Nginx的TLS配置直接影响站点的安全等级和性能。以下是一个生产级别的配置示例:

server {
    listen 443 ssl http2;
    server_name example.com;

    # 证书配置
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # TLS 版本 — 仅允许 1.2 和 1.3
    ssl_protocols TLSv1.2 TLSv1.3;

    # 密码套件 — 优先使用强加密
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers on;

    # OCSP Stapling — 加速证书验证
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;

    # Session 复用 — 减少握手开销
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # DH 参数
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
}

# HTTP 强制跳转 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

有几个关键配置值得深入理解:

ssl_protocols限制TLS版本,只允许1.2和1.3,禁用所有旧版本(SSL 3.0、TLS 1.0、TLS 1.1)。旧版本存在POODLE、BEAST等著名攻击漏洞,不应继续使用。

ssl_ciphers指定密码套件列表和优先级。现代配置应该优先使用ECDHE系列算法,禁用不提供前向安全性的静态密码套件(如RSA),禁用已知不安全的算法(如3DES、RC4)。

OCSP Stapling优化证书验证性能。传统方式下,浏览器需要向CA的OCSP服务器查询证书状态;OCSP Stapling让服务端预先获取OCSP响应并在握手时发送给浏览器,减少了用户等待时间。

ssl_dhparam使用自定义的DH参数组。默认的DH参数可能不够安全,生成更强的参数可以提高密钥交换的安全性。

3. HSTS(HTTP Strict Transport Security)

HSTS是一种安全策略声明,告诉浏览器只能通过HTTPS访问网站。一旦浏览器收到HSTS头,后续对该域名的所有请求都会自动使用HTTPS,无需服务器重定向。

# 告知浏览器只能通过 HTTPS 访问
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

# max-age: 策略有效期(秒),推荐 2 年
# includeSubDomains: 包含所有子域名
# preload: 申请加入浏览器预加载列表

HSTS的主要价值在于防止中间人攻击。即使攻击者试图通过HTTP劫持流量,用户浏览器也会拒绝发起HTTP请求。对于安全性要求高的站点,这提供了额外的保护层。

// Express 设置 HSTS
import helmet from 'helmet';

app.use(
  helmet.hsts({
    maxAge: 63072000,       // 2 年
    includeSubDomains: true,
    preload: true,
  })
);

HSTS Preload List

HSTS Preload List是浏览器内置的域名列表。进入预加载列表的域名,浏览器在安装时就强制使用HTTPS,无需首次访问。这解决了HSTS的"首次访问"问题:用户首次访问恶意WiFi网络时,攻击者可能拦截HTTP请求并注入恶意内容。

提交到 HSTS Preload List 的要求:
1. 有效的 HTTPS 证书
2. 将 HTTP 重定向到 HTTPS(同一主机)
3. 所有子域名使用 HTTPS
4. HSTS Header 包含:
   - max-age ≥ 31536000(1 年)
   - includeSubDomains
   - preload

提交地址:https://hstspreload.org/

提交到预加载列表需要谨慎。一旦提交,移除非常困难,需要等待浏览器更新版本。这个决定应该是战略性的——只有确信不会在短期内需要HTTP访问的域名才应该加入。

4. 混合内容(Mixed Content)

混合内容是指HTTPS页面中加载HTTP资源。混合内容会降低页面的安全性,因为HTTP资源可能被中间人篡改。即使页面主体通过HTTPS加载,加载一个HTTP脚本就等于给攻击者开了后门。

浏览器会阻止某些危险的混合内容(如脚本),但会允许某些相对安全的混合内容(如图片)。这种区分是有道理的——图片被篡改最多是显示异常,但脚本被篡改可以窃取用户数据、执行任意操作。

# 升级不安全请求
Content-Security-Policy: upgrade-insecure-requests

# 阻止所有 HTTP 资源
Content-Security-Policy: block-all-mixed-content

upgrade-insecure-requests指令告诉浏览器将所有HTTP请求升级为HTTPS。这适用于那些确信所有HTTP资源都有HTTPS版本的场景。

// 检测和修复混合内容
function findMixedContent() {
  // 检查所有资源元素
  const elements = document.querySelectorAll('[src], [href], [action]');
  const insecure = [];

  elements.forEach((el) => {
    const url = el.src || el.href || el.action;
    if (url && url.startsWith('http://')) {
      insecure.push({
        element: el.tagName,
        attribute: el.src ? 'src' : el.href ? 'href' : 'action',
        url,
      });
    }
  });

  return insecure;
}

生产环境中的混合内容问题往往出现在第三方资源上。CDN、字体服务、分析工具等可能仍然使用HTTP URL。在迁移到HTTPS时,应该全面审计所有外部资源,确保它们支持HTTPS。

5. TLS 性能优化

HTTPS的性能开销主要来自TLS握手。建立连接时,需要额外的网络往返来交换密钥和验证证书。对于延迟敏感的应用,这个开销可能影响用户体验。

完整握手: ~300ms (2-RTT for TLS 1.2)
Session Ticket 恢复: ~150ms (1-RTT)
TLS 1.3: ~150ms (1-RTT)
TLS 1.3 0-RTT: ~0ms (但有安全风险)

TLS Session复用可以减少握手开销。Session Ticket和Session ID是两种复用机制,允许客户端使用之前握手的密钥信息恢复会话,避免完整的握手过程。

Session Resumption(会话恢复):
- Session ID:服务端存储会话状态,客户端提供Session ID恢复
- Session Ticket:会话状态加密发送给客户端保存(适合分布式环境)

证书链完整性对连接性能有显著影响。如果服务器只发送了不完整的证书链,浏览器需要额外查询CA获取中间证书,这个过程可能增加数百毫秒延迟。

# 检查证书链完整性
openssl s_client -connect example.com:443 -showcerts

# 确保只发送必要的证书
# 不要包含根证书(浏览器已内置)
# 正确顺序:服务器证书 → 中间证书

Early Hints (103)

HTTP 103 Early Hints是提升页面加载性能的新机制。它允许服务端在发送最终响应头之前,先发送"提示"头,告诉浏览器可以预加载某些资源。由于TLS握手发生在TCP握手之后,Early Hints的优化效果可能不如使用预连接(Preconnect)。

HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script

HTTP/1.1 200 OK
Content-Type: text/html

6. 安全 Header 最佳实践

现代Web应用应该配置一套完整的安全响应头。这些头提供了浏览器层面的安全防护,是纵深防御的重要组成部分。

// 使用 helmet 设置安全 Header
import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
    },
  },
  hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
  frameguard: { action: 'deny' },
  referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));

// 手动设置的重要 Header
app.use((req, res, next) => {
  // 阻止 MIME 类型嗅探
  res.setHeader('X-Content-Type-Options', 'nosniff');

  // 控制 Referer 信息泄露
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // 控制浏览器功能访问
  res.setHeader(
    'Permissions-Policy',
    'camera=(), microphone=(), geolocation=(self), payment=(self)'
  );

  // 阻止页面被嵌入 iframe
  res.setHeader('X-Frame-Options', 'DENY');

  next();
});

这些安全头的含义:

X-Content-Type-Options: nosniff阻止浏览器MIME类型嗅探。浏览器可能会"猜测"响应类型并执行,这可能导致安全问题。

Referrer-Policy控制Referer头的发送策略。过度泄露Referer可能让第三方了解用户的浏览行为。

Permissions-Policy(原Feature-Policy)控制浏览器功能如摄像头、麦克风、地理位置等的访问权限。

X-Frame-Options控制页面是否可以被嵌入iframe。点击劫持攻击正是利用iframe来诱导用户点击隐藏的恶意元素。

7. 常见问题排查

TLS配置问题可能导致连接失败或安全等级下降。掌握诊断工具是解决问题的关键。

# 使用 SSL Labs 在线测试
# https://www.ssllabs.com/ssltest/
# 提供全面的TLS配置评估和修复建议

# 命令行工具
# 检查证书信息
openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -text -noout

# 检查证书过期时间
openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -dates -noout

# 检查支持的 TLS 版本
nmap --script ssl-enum-ciphers -p 443 example.com

# 检查 HSTS
curl -sI https://example.com | grep -i strict

SSL Labs的在线测试是最全面的TLS配置评估工具。它会检查证书链、协议版本、密码套件配置等,给出从A+到F的评分,并提供详细的修复建议。

// 前端证书错误处理
// Service Worker 中捕获证书错误
self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request).catch((error) => {
      if (error.message.includes('SSL') || error.message.includes('certificate')) {
        // 记录证书错误
        console.error('TLS Error:', error.message);
        return new Response('Security Error', { status: 0 });
      }
      throw error;
    })
  );
});

8. Certificate Transparency

Certificate Transparency(CT,证书透明度)是一个框架,要求CA公开记录所有签发的证书。这使得域名所有者可以监控是否有未授权的证书被签发,及时发现证书伪造攻击。

# Expect-CT Header(要求 CT 日志记录)
Expect-CT: max-age=86400, enforce, report-uri="https://example.com/ct-report"

CT日志是公开的,任何人都可以查询。域名所有者可以监控自己的域名是否出现了未经授权的证书。

// 使用 crt.sh API 监控证书透明度日志
// 检测是否有人为你的域名签发了未授权的证书
async function checkCertificateTransparency(domain) {
  const response = await fetch(
    `https://crt.sh/?q=${encodeURIComponent(domain)}&output=json`
  );
  const certs = await response.json();

  return certs.map((cert) => ({
    issuer: cert.issuer_name,
    notBefore: cert.not_before,
    notAfter: cert.not_after,
    serialNumber: cert.serial_number,
  }));
}

Certificate Transparency是应对SAR(Subject Alternative Name)攻击的技术。攻击者可能向某个CA谎称自己是域名所有者,申请到有效证书后进行中间人攻击。通过CT日志,域名所有者可以发现这些未授权证书。

实战练习

练习 1:HTTPS 本地开发

使用mkcert为本地开发环境配置HTTPS。理解mkcert的工作原理(它创建了本地信任的根CA),配置浏览器信任本地证书,并验证开发环境的HTTPS功能正常。

练习 2:安全 Header 审计

使用securityheaders.com审计项目的安全Header配置。分析当前配置的不足之处,逐一修复直到达到A+评分。记录每个Header的作用和配置理由。

练习 3:TLS 配置优化

检查生产环境的TLS配置,在SSL Labs测试中达到A+评分。如果当前配置已是A+,尝试理解哪些方面还有提升空间;如果未达到A+,制定修复计划。

延展阅读

关键术语

术语 解释
TLS Transport Layer Security,传输层安全协议,HTTPS的加密层
HTTPS HTTP over TLS,加密的HTTP协议
HSTS HTTP Strict Transport Security,强制浏览器使用HTTPS的策略头
Certificate 数字证书,证明服务器身份和公钥所有权的文件
CA Certificate Authority,证书颁发机构
OCSP Stapling 在线证书状态协议装订,优化证书验证性能
Forward Secrecy 前向安全性,确保过去的通讯不被未来密钥泄露解密
Mixed Content 混合内容,HTTPS页面中加载HTTP资源
Certificate Transparency 证书透明度,公开记录所有签发证书的框架
DH Parameters Diffie-Hellman参数,用于密钥交换的加密参数