文章

HTTPS加密原理深度解析

HTTPS加密原理深度解析

一句话概括

HTTPS 是 HTTP 协议在 TLS/SSL 层之上的安全增强版本,通过非对称加密交换对称密钥、证书体系验证身份、数字签名保证完整性,让 HTTP 通信从”明信片”升级为”密封挂号信”。

背景与意义

从一场噩梦开始

假设你在咖啡馆连接公共 Wi-Fi,用手机银行 App 转账 5000 元。如果使用 HTTP,这条网络链路上的每一跳——咖啡馆路由器、ISP 网关、骨干网节点——都可以完整看到你发送的数据:

1
2
3
4
5
POST /transfer HTTP/1.1
Host: bank.example.com
Content-Type: application/x-www-form-urlencoded

from_account=622202***&to_account=622203***&amount=5000

这不是理论推测。2008 年,Firefox 插件 Firesheep 让任何人都能在一键抓取同一 Wi-Fi 网络下所有未加密 HTTP 会话的 Cookie,登录别人的 Facebook、Twitter。Firesheep 三天内下载量超 50 万次,直接推动了”全站 HTTPS”运动。

2013 年,斯诺登泄露的 NSA 文件显示美国国家安全局在互联网骨干链路上部署了大量的流量嗅探设备(通过 UPSTREAM 计划)。HTTPS 加密使得这些大规模监听的有效性大幅降低。

到了 2026 年,Google Transparency Report 显示全球前 100 万网站中 HTTPS 使用率超过 97%,Let’s Encrypt 已签发超过 40 亿张免费证书。HTTPS 不再是可选项,它是互联网通信的基础安全设施。

HTTPS 要解决的四个问题

安全威胁攻击方式HTTPS 解决方案
窃听中间人截获明文数据加密传输内容
篡改修改传输中的数据(如注入恶意广告)消息认证码 / 数字签名
冒充伪造网站身份(钓鱼攻击)证书认证体系 (CA)
重放截获合法请求后重复发送随机数 + 序列号机制

概念与定义

加密体系的三层结构

HTTPS 的加密体系可以形象地理解为三层安全机制:

1
2
3
4
5
6
7
8
9
10
应用层: HTTP 请求/响应 (明文)
             |
            ↓
TLS 记录层: 对称加密 (AES-256-GCM) — 加密实际数据
             |
            ↓
TLS 握手层: 非对称加密 (RSA/ECDHE) — 安全交换对称密钥
             |
            ↓
证书层: X.509 证书 (CA 签名) — 验证服务器身份

核心概念速览

概念比喻说明
对称加密保险箱(一把钥匙开和锁)加密和解密使用同一把密钥,速度快
非对称加密信箱(公钥投信,私钥取信)加密和解密使用不同密钥,速度慢
数字签名手写印章证明数据来自谁、没有被动过
CA 证书身份证权威机构证明公钥属于谁
TLS 握手当面核验身份 + 交换保险箱钥匙建立安全信道的过程

对称加密算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Node.js 对称加密示例 (AES-256-GCM)
const crypto = require('node:crypto');

// AES-256-GCM: 目前最广泛使用的对称加密算法
function encryptAES(plaintext, key) {
  const iv = crypto.randomBytes(12); // GCM 推荐 12 字节 IV
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  
  let encrypted = cipher.update(plaintext, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  
  const authTag = cipher.getAuthTag().toString('hex');
  
  return { iv: iv.toString('hex'), encrypted, authTag };
}

function decryptAES(ivHex, encryptedHex, authTagHex, key) {
  const decipher = crypto.createDecipheriv(
    'aes-256-gcm', 
    key, 
    Buffer.from(ivHex, 'hex')
  );
  decipher.setAuthTag(Buffer.from(authTagHex, 'hex'));
  
  let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  
  return decrypted;
}

// 使用示例
const key = crypto.randomBytes(32); // 256 位密钥
const message = '余额: 10000 元, 转账到: 622202****';

const { iv, encrypted, authTag } = encryptAES(message, key);
console.log('密文:', encrypted);
console.log('完整性标签:', authTag);

const plaintext = decryptAES(iv, encrypted, authTag, key);
console.log('解密:', plaintext); // 余额: 10000 元, 转账到: 622202****

最小示例

用 Node.js 实现一个完整的 HTTPS 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// server.mjs — 加密的 HTTPS 服务器
import https from 'node:https';
import fs from 'node:fs';
import path from 'node:path';

// 生成自签名证书 (仅用于开发):
// openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
// 生产环境使用 Let's Encrypt / 云厂商证书

const options = {
  key: fs.readFileSync('./key.pem'),
  cert: fs.readFileSync('./cert.pem'),
  
  // TLS 1.3 配置 (强制最高安全)
  minVersion: 'TLSv1.3',
  ciphers: [
    'TLS_AES_256_GCM_SHA384',
    'TLS_CHACHA20_POLY1305_SHA256'
  ].join(':'),
  
  // 启用会话缓存 (减少重复握手)
  sessionTimeout: 300, // 5 分钟
  sessionIdContext: 'my-app'
};

const server = https.createServer(options, (req, res) => {
  // 安全响应头
  res.setHeader('Strict-Transport-Security', 
    'max-age=63072000; includeSubDomains; preload');
  res.setHeader('X-Content-Type-Options', 'nosniff');
  
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({
    message: '这是通过加密信道传输的响应',
    tlsVersion: req.socket.getProtocol(),
    cipherName: req.socket.getCipher().name,
    timestamp: new Date().toISOString()
  }));
});

server.listen(443, () => {
  console.log(`HTTPS 服务器运行中`);
  console.log(`TLS 版本: ${server.getTicketKeys() ? 'TLS 1.3' : 'TLS 1.2'}`);
});

// client.mjs — 验证加密的客户端
import https from 'node:https';

// 请关闭证书验证(仅开发)或使用正确 CA:
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

https.get('https://localhost/', (res) => {
  let data = '';
  res.on('data', chunk => data += chunk);
  res.on('end', () => {
    const parsed = JSON.parse(data);
    console.log('TLS 版本:', parsed.tlsVersion);
    console.log('加密算法:', parsed.cipherName);
    console.log('响应内容:', parsed.message);
  });
});

用 curl 验证 TLS 握手全流程

1
2
3
4
5
6
7
8
9
10
11
# 查看 TLS 握手详情(解密每一步)
curl -v https://www.example.com/

# 查看证书链
openssl s_client -connect www.example.com:443 -showcerts

# 只查看 TLS 版本和加密套件
curl -v --tls-max 1.3 https://www.example.com/ 2>&1 | grep "TLS"

# 测试 OCSP(证书在线状态)
openssl s_client -connect www.example.com:443 -status

核心知识点拆解

1. TLS 1.3 握手完整流程

TLS 1.3 相比 TLS 1.2 大幅简化了握手流程,使用了更少的往返次数(RTT):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
TLS 1.3 完整握手 (1-RTT):

客户端                                         服务端
  |                                             |
  | -------- ClientHello --------------------->|
  |   * 支持的 TLS 版本列表                      |
  |   * 支持的密码套件:                          |
  |     - TLS_AES_256_GCM_SHA384               |
  |     - TLS_CHACHA20_POLY1305_SHA256          |
  |   * key_share (客户端 Diffie-Hellman 参数)   |
  |   * supported_groups (X25519, P-256)        |
  |   * signature_algorithms                    |
  |   * session_id (会话复用)                    |
  |   * ALPN (http/1.1, h2, h3)                 |
  |                                             |
  | <--------- ServerHello --------------------|
  |   * 选定的 TLS 版本: 1.3                    |
  |   * 选定的密码套件                           |
  |   * key_share (服务端 DH 参数)               |
  |   * 服务器证书 (X.509)                       |
  |   * CertificateVerify (数字签名)             |
  |   * Finished (握手哈希的 MAC)                |
  |                                             |
  |   [此时双方均已计算出会话密钥]                |
  |   [从记录 0 开始启用对称加密]                 |
  |                                             |
  | -------- Finished ------------------------>|
  |   * 握手哈希的 MAC (证明密钥正确)             |
  |                                             |
  | ========= 加密安全信道已建立 ==============|
  |                                             |
  | -------- HTTP 请求 (加密) ----------------->|
  | <-------- HTTP 响应 (加密) -----------------|

与 TLS 1.2 的关键差异:

  • TLS 1.3 删除了不安全或过时的密码套件(RC4, 3DES, AES-CBC)
  • TLS 1.3 的 1-RTT 相比 TLS 1.2 的 2-RTT 节省了一个往返
  • TLS 1.3 的 ServerHello 之后所有握手消息都是加密的(保护证书不被泄露)
  • 0-RTT 模式:对于之前连接过的客户端,可以直接发送加密数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 模拟 TLS 1.3 握手的密钥派生 (简化)
const crypto = require('node:crypto');

class TLS13Handshake {
  constructor() {
    this.earlySecret = null;
    this.handshakeSecret = null;
    this.masterSecret = null;
    this.clientHandshakeTrafficSecret = null;
    this.serverHandshakeTrafficSecret = null;
    this.clientApplicationTrafficSecret = null;
    this.serverApplicationTrafficSecret = null;
  }

  // 使用 HKDF (HMAC-based Key Derivation Function) 派生密钥
  deriveSecret(ikm, label, transcriptHash) {
    // HKDF-Extract
    const salt = Buffer.alloc(32, 0);
    const prk = crypto.createHmac('sha256', salt)
      .update(ikm)
      .digest();

    // HKDF-Expand
    const hkdfLabel = Buffer.concat([
      Buffer.alloc(2), // length placeholder
      Buffer.from(`tls13 ${label}`, 'utf8'),
      transcriptHash
    ]);
    crypto.createHmac('sha256', prk).update(hkdfLabel);

    return crypto.createHmac('sha256', prk)
      .update(hkdfLabel)
      .digest();
  }

  // 模拟 ECDHE 密钥交换
  performECDHE(clientPublicKey, serverPrivateKey) {
    // 使用 X25519 曲线进行 Diffie-Hellman 密钥交换
    // 实际: crypto.createECDH('prime256v1')
    const ecdh = crypto.createECDH('x25519');
    ecdh.setPrivateKey(serverPrivateKey);
    return ecdh.computeSecret(clientPublicKey); // 共享密钥
  }
}

2. 证书链验证体系

当浏览器访问 https://www.example.com 时,它会验证以下证书链:

1
2
3
4
5
根 CA 证书 (内置在浏览器/操作系统中)
    ↓  签名
中间 CA 证书 (Let's Encrypt Authority X3)
    ↓  签名
服务器证书 (www.example.com)

验证过程包含六个检查

  1. 数字签名验证:用父证书的公钥验证子证书的签名
  2. 有效期检查:当前时间在 notBeforenotAfter 之间
  3. 域名匹配:证书的 subjectAltName 包含目标域名
  4. 吊销状态:通过 CRL 或 OCSP 检查证书是否被吊销
  5. 密钥用法:CA 证书有 keyCertSign 用法标记
  6. 路径长度约束:中间 CA 不能签发超过其限制的子 CA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 用 Node.js 验证证书链
import tls from 'node:tls';
import crypto from 'node:crypto';
import { X509Certificate } from 'node:crypto';

async function verifyCertificateChain(hostname) {
  return new Promise((resolve, reject) => {
    const socket = tls.connect(443, hostname, { rejectUnauthorized: false }, () => {
      const cert = socket.getPeerCertificate(true);
      
      // 获取完整的证书链
      console.log('=== 证书链 ===');
      let currentCert = cert;
      let depth = 0;
      
      while (currentCert) {
        console.log(`\n深度 ${depth}:`);
        console.log(`  主题: ${currentCert.subject.CN || ''}`);
        console.log(`  颁发者: ${currentCert.issuer.CN || ''}`);
        console.log(`  有效期: ${currentCert.valid_from}${currentCert.valid_to}`);
        console.log(`  指纹(SHA256): ${currentCert.fingerprint256}`);
        console.log(`  序列号: ${currentCert.serialNumber}`);
        
        // 检查 SAN (Subject Alternative Names)
        if (currentCert.subjectaltname) {
          console.log(`  SAN: ${currentCert.subjectaltname}`);
        }
        
        // 检查是否包含当前域名
        const sanPattern = new RegExp(hostname.replace(/\./g, '\\.'));
        if (sanPattern.test(currentCert.subjectaltname || '')) {
          console.log('  ✅ 域名匹配');
        }
        
        currentCert = currentCert.issuerCertificate;
        depth++;
      }
      
      socket.end();
      resolve(cert);
    });
  });
}

verifyCertificateChain('www.baidu.com')
  .then(() => console.log('证书链验证完成'));

3. 前向安全性 (Perfect Forward Secrecy)

问题:如果使用 RSA 密钥交换,客户端用服务端 RSA 公钥加密一个”预主密钥”发给服务端。如果攻击者录制了所有加密流量,第二天破译了服务端的 RSA 私钥,就能解密历史上所有会话的数据。

解决方案:使用 Diffie-Hellman 临时密钥交换(ECDHE)。

ECDHE 的核心思想:即使攻击者获得了长期私钥(服务端证书的私钥),也无法解密之前的会话。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
传统的 RSA 密钥交换:
Client                                            Server
  | 生成随机 PreMaster                        |
  | 用 RSA 公钥加密 PreMaster                  | 
  | 只能被 RSA 私钥解密                        |
  |                                            |
  如果 RSA 私钥泄露 → 所有历史会话全都能解密 ❌


ECDHE 密钥交换 (TLS 1.3 强制):
Client                                            Server
  | 生成临时 DH 密钥对 (client_private)         |
  | 发送 client_public                          |  
  |                     | 生成临时 DH 密钥对 (server_private)
  |                     | 发送 server_public
  | 计算共享密钥 =      | 计算共享密钥 =
  | DH(client_private,  | DH(server_private,
  |    server_public)   |    client_public)
  |                                            |
  即使 RSA 私钥泄露 → 每个会话的临时密钥不同 ✅
  攻击者需要 client_private 和 server_private 才能解密
  但这些临时密钥只保存在内存中,会话结束后立即丢弃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 演示 ECDHE 前向安全
const crypto = require('node:crypto');

class ECDHESession {
  constructor() {
    // 每个会话生成独立的临时密钥对
    this.ecdH = crypto.createECDH('prime256v1');
    this.ephemeralPublicKey = this.ecdH.generateKeys('hex');
  }

  computeSharedSecret(peerPublicKey) {
    return this.ecdH.computeSecret(peerPublicKey, 'hex', 'hex');
  }

  // 销毁临时密钥(模拟)
  destroy() {
    this.ecdH = null;
    this.ephemeralPublicKey = null;
  }
}

// 模拟一次会话
const client = new ECDHESession();
const server = new ECDHESession();

// 交换公钥,计算共享密钥
const sharedKey1 = client.computeSharedSecret(server.ephemeralPublicKey);
const sharedKey2 = server.computeSharedSecret(client.ephemeralPublicKey);

console.log('客户端共享密钥:', sharedKey1);
console.log('服务端共享密钥:', sharedKey2);
console.log('密钥一致:', sharedKey1 === sharedKey2);

// 销毁临时密钥
client.destroy();
server.destroy();
// 即使攻击者记录了所有数据,也无法计算之前的 sharedKey
console.log('✅ 前向安全:会话密钥已销毁,无法恢复');

实战案例

案例:金融支付系统的 HTTPS 安全配置

构建一个安全支付网关的 HTTPS 配置,包含 HSTS、证书钉扎、OCSP Stapling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# nginx HTTPS 最佳实践配置
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name pay.example.com;

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

    # TLS 版本 — 仅允许 1.2 和 1.3
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # 密码套件 — 仅选择前向安全的 AEAD 套件
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
    ssl_prefer_server_ciphers on;
    
    # OCSP Stapling — 服务端缓存证书吊销状态
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/pay.example.com/chain.pem;
    resolver 8.8.8.8 1.1.1.1 valid=300s;
    resolver_timeout 5s;
    
    # HSTS — 强制浏览器始终使用 HTTPS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    
    # 证书公钥钉扎 (HPKP) — 注意:HPKP 已被大多数浏览器弃用
    # 建议改用 Expect-CT header
    
    # 安全响应头
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header Content-Security-Policy "default-src 'self'";
    
    # 会话缓存 — 减少重复握手延迟
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;  # 禁用 session ticket 以增强安全性
    
    # 验证客户端证书(支付场景可选)
    # ssl_client_certificate /etc/ssl/ca.crt;
    # ssl_verify_client on;
    
    location / {
        proxy_pass http://backend:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

配套的 Node.js 支付路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// payment-gateway.mjs
import https from 'node:https';
import { randomBytes, timingSafeEqual } from 'node:crypto';

// 支付核心函数(强制执行加密校验)
class PaymentGateway {
  constructor() {
    this.rateLimiter = new Map();
  }

  async processPayment(req, res) {
    const tlsInfo = {
      protocol: req.socket.getProtocol(),
      cipher: req.socket.getCipher().name,
      // TLS 1.3 特有的密钥更新验证
      keyUpdate: req.socket.getCipher().version >= 'TLSv1.3' ? '支持' : '不支持'
    };

    // 安全检查:拒绝非 TLS 1.3 的连接
    if (tlsInfo.protocol !== 'TLSv1.3') {
      return res.writeHead(426, { 
        'Content-Type': 'application/json',
        'Upgrade': 'TLS 1.3'
      }).end(JSON.stringify({
        error: 'REQUIRES_UPGRADE',
        message: '本服务仅接受 TLS 1.3 连接'
      }));
    }

    // 安全检查:全链路加密验证
    const requestId = randomBytes(16).toString('hex');
    const clientIp = req.socket.remoteAddress;
    
    // 速率限制(基于 IP + TLS 指纹)
    const rateKey = `${clientIp}:${req.socket.getCipher().name}`;
    if (this.isRateLimited(rateKey)) {
      return res.writeHead(429).end('Too Many Requests');
    }

    // 处理支付(真实业务在此调用第三方支付接口)
    const response = {
      transactionId: `TXN${Date.now()}`,
      requestId,
      encryptedOver: 'TLS 1.3 AES-256-GCM',
      timestamp: new Date().toISOString(),
      // 部分字段使用额外的应用层加密
      maskedCardLast4: '****' + (req.body?.cardNumber?.slice(-4) || '0000')
    };

    res.writeHead(200, {
      'Content-Type': 'application/json',
      'X-Request-ID': requestId,
      'X-TLS-Version': tlsInfo.protocol
    });
    res.end(JSON.stringify(response));
  }

  isRateLimited(key) {
    const now = Date.now();
    const window = 60000; // 1 分钟窗口
    const limit = 30;     // 最多 30 次

    const entries = this.rateLimiter.get(key) || [];
    const recent = entries.filter(t => now - t < window);
    
    if (recent.length >= limit) return true;
    
    recent.push(now);
    this.rateLimiter.set(key, recent);
    return false;
  }
}

const gateway = new PaymentGateway();

https.createServer({
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem'),
  minVersion: 'TLSv1.2',
  ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384'
}, (req, res) => {
  if (req.url === '/pay') {
    gateway.processPayment(req, res);
  }
}).listen(4433);

配置后,可以通过 SSL Labs 测试工具验证安全等级:

1
2
3
4
5
6
7
8
# 用 openssl 测试服务器配置
openssl s_client -connect pay.example.com:443 -tls1_3

# 检查是否支持前向安全套件
openssl s_client -connect pay.example.com:443 -cipher 'ECDHE'

# 测试 OCSP Stapling
openssl s_client -connect pay.example.com:443 -status | grep -A 20 "OCSP"

底层原理

OpenSSL 库中的 TLS 1.3 实现

OpenSSL 是互联网上使用最广泛的 TLS 库,Node.js、nginx、Apache 底层都依赖它。以下是 OpenSSL 中 TLS 1.3 握手的核心流程(取自 3.0 版本源码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 简化自 OpenSSL 3.0: ssl/statem/statem_lib.c
// TLS 1.3 状态机处理

static int ossl_statem_server_pre_work(SSL *s, WORK_STATE *wst)
{
    // TLS 1.3 握手状态机
    switch (s->statem.hand_state) {
    case TLS_ST_SR_CLIENT_HELLO:
        // 1. 解析 ClientHello
        // 2. 选择密码套件
        // 3. 生成 ECDHE 临时密钥
        // 4. 构造 ServerHello
        break;

    case TLS_ST_SW_SERVER_HELLO:
        // 发送 ServerHello (包含选定版本、密码套件、DH 参数)
        tls_construct_server_hello(s);
        break;

    case TLS_ST_SW_SERVER_CERTIFICATE:
        // 发送证书链 (TLS 1.3 中证书总是加密后发送)
        tls_construct_server_certificate(s);
        break;

    case TLS_ST_SR_FINISHED:
        // 验证客户端 Finished 消息 (握手完整性检查)
        // 此时所有握手已完成,可以切换加密状态
        s->statem.hand_state = TLS_ST_OK;
        break;
    }
}

BoringSSL 的 CHACHA20-POLY1305 实现

Google 的 BoringSSL(Chromium 使用的 TLS 库)针对 ARM 平台做了大量优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 简化自 BoringSSL: crypto/chacha/chacha.c
// ChaCha20 流加密的 ARM NEON 优化实现

void CRYPTO_chacha_20_neon(uint8_t *out, const uint8_t *in,
                           size_t in_len, const uint8_t key[32],
                           const uint8_t nonce[12], uint32_t counter) {
    // 使用 NEON SIMD 指令一次处理 4 个块 (256 字节)
    for (size_t offset = 0; offset < in_len; offset += 256) {
        // 4 路并行 ChaCha 轮函数
        for (int i = 0; i < 20; i += 2) {
            // ChaCha Quarter Round (并行处理 4 个块)
            // 第 1 和 3 列的旋转
            qround_neon(&state0, &state1, &state2, &state3);
            // 第 2 和 4 列的旋转
            qround_neon(&state4, &state5, &state6, &state7);
            // 对角线操作
            ...
        }
        // XOR 明文/密文
        // 在 ARM 上使用 NEON vld1q_u8 / veorq_u8 指令一次性加载/XOR/存储
    }
}

TLS 记录层协议格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TLS 记录层:
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  | 内容类型 | 版本    | 长度     | 载荷                         |
  | (8 bits) |(16 bits)|(16 bits) | (长度指定)                    |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  
  内容类型:
    20 = ChangeCipherSpec
    21 = Alert
    22 = Handshake
    23 = Application Data
    24 = Heartbeat (可选)
  
  TLS 1.3 记录层加密:
    - 使用 AEAD (Authenticated Encryption with Associated Data)
    - 密文 = AES-GCM-Encrypt(key, nonce, plaintext, associated_data)
    - nonce = 记录序列号 XOR 静态 IV (防重放)
    - associated_data = 内容类型 + 版本 + 长度 (防止类型混淆攻击)

高频面试题解析

面试题 1:HTTPS 建立连接需要几次往返?详细描述每一步发生了什么。

难度:这个问题的陷阱在于——它依赖于 TLS 版本和是否复用会话。

答案

分三种情况:

场景 A:TLS 1.2 完整握手 → 2 RTT

1
2
3
RTT 1: TCP 三次握手 (SYN, SYN-ACK, ACK) 
RTT 2: ClientHello → ServerHello (证书) → 客户端发送密钥 → 完成
总计:2 RTT(TCP 1 + TLS 1)

场景 B:TLS 1.3 完整握手 → 1 RTT

1
2
3
4
RTT 1: TCP 三次握手
RTT 1 (同时): TLS 1.3 ClientHello (含 DH 共享参数)
             → ServerHello (含 DH 参数 + 证书)
总计:1 RTT(TCP 1 + TLS 0,握手合并到 TCP 之后的同一个 RTT)

场景 C:TLS 1.3 0-RTT → 0 RTT(数据与连接建立同时发出!)

1
2
3
4
RTT 1: TCP 三次握手
RTT 0: TLS 0-RTT 数据(与 ClientHello 同时发出)
       服务器收到后立即交付给应用(HTTP 请求)
总计:0 RTT

加分项:如果能说明 0-RTT 依赖 PSK(预共享密钥),且存在重放攻击的风险——因此修改类请求(转账、发帖)不适合 0-RTT,而查询类(加载页面)非常适合——就显示出了对安全权衡的理解深度。

面试题 2:什么是”中间人攻击”?HTTPS 如何防御?

答案要点

中间人攻击(MITM)是指攻击者插入到客户端和服务器之间,独立与双方通信:

1
2
3
4
5
6
正常通信:
  客户端 ←直接→ 服务器

MITM 攻击:
  客户端 ←→ 攻击者 ←→ 服务器
  客户端以为自己连服务器,实际连的是攻击者

HTTPS 的防御层级:

  1. 证书验证防止冒充:浏览器会检查服务器证书是否由信任的 CA 签发、域名是否匹配。攻击者无法伪造一个有合法 CA 签名的 www.bank.com 证书。

  2. 公钥加密防止窃听:即使攻击者拦截了握手数据,没有私钥无法解密。

  3. 消息认证码防止篡改:AEAD 加密(如 AES-GCM)在加密的同时提供完整性验证,篡改数据会导致解密失败。

常见的 HTTPS MITM 变种:

  • 证书劫持:攻击者在用户设备上安装自己的 CA 证书(如企业 SSL 解密代理、恶意软件)。防御:浏览器检测到不是公共 CA 签名会发出警告。
  • SSL Strip:攻击者在 HTTP 阶段降级到 HTTP。防御:HSTS (HTTP Strict Transport Security) 头强制浏览器始终使用 HTTPS。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 模拟防 MITM 的证书验证
class CertificateValidator {
  // 内置的根 CA 列表 (信任锚)
  static TRUSTED_ROOTS = new Set([
    'Mozilla Included CA Certificate List',  // ~150 个根 CA
  ]);

  static validate(cert, hostname) {
    // 1. 域名验证
    if (!this.matchHostname(cert.san, hostname)) {
      throw new Error('证书域名不匹配');
    }

    // 2. 证书链验证
    const chain = this.buildChain(cert);
    if (!this.verifySignatures(chain)) {
      throw new Error('证书链签名验证失败');
    }

    // 3. 有效期检查
    if (Date.now() < chain[0].validFrom || Date.now() > chain[0].validTo) {
      throw new Error('证书已过期');
    }

    // 4. 吊销检查
    if (this.isRevoked(cert)) {
      throw new Error('证书已被吊销');
    }

    return true;
  }
}

面试题 3:TLS 1.3 移除了哪些加密套件?为什么?

答案要点

TLS 1.3 的密码套件从 TLS 1.2 的 37 个锐减到 5 个。移除的套件及其原因:

移除的套件移除原因漏洞/风险
RSA 密钥交换没有前向安全性私钥泄露→解密所有历史会话
RC4 流密码已被彻底攻破相关性攻击,可以恢复明文
CBC 模式 (AES-CBC)易受 Padding Oracle 攻击Lucky13 攻击,只需 2^13 次解密尝试即可恢复明文
3DES密钥过短 (56 位有效)Sweet32 生日攻击,可恢复密钥
DHE (非椭圆曲线)CPU 负担高用 ECDHE 替代,性能更好且同样安全

TLS 1.3 保留的 5 个套件:

1
2
3
4
5
TLS_AES_128_GCM_SHA256       — 普遍兼容
TLS_AES_256_GCM_SHA384       — 最高安全
TLS_CHACHA20_POLY1305_SHA256 — 移动设备优化 (无 AES 硬件加速时更快)
TLS_AES_128_CCM_SHA256       — IoT 场景
TLS_AES_128_CCM_8_SHA256     — 受限设备 (更短的验证码)

加分项:提及 CHACHA20-POLY1305 在 ARM 设备(如 iPhone、Android)上的性能优势——这些设备没有 AES-NI 硬件加速指令,CHACHA20 使用纯 SIMD 指令,速度可达 AES-128-GCM 的 2-3 倍。

总结与扩展

HTTPS 的加密体系是一个多层次的协作系统,每一层解决一类特定的安全问题:

  1. 证书体系解决”你在和谁说话”——通过可验证的身份证明,防止冒充攻击
  2. 非对称加密解决”如何安全交换密钥”——在不可信信道上建立共享密钥
  3. 对称加密解决”如何高效保护数据”——AEAD 同时提供机密性和完整性
  4. 前向安全解决”密钥泄露后历史数据的安全”——每个会话独立密钥

作为开发者,理解这些原理有助于:

  • 诊断网络问题:TLS 错误往往指向特定的握手阶段(证书过期、密码套件不匹配、SNI 问题)
  • 安全配置:知道每个配置项的真实含义,而不是复制粘贴示例
  • 性能优化:合理配置会话缓存、OCSP Stapling、ALPN,可以显著减少连接延迟

未来方向

  • TLS 1.4:正在讨论中,可能引入更强的后量子密码算法(如 Kyber, Dilithium)
  • TLS 证书透明度:所有 SSL 证书必须公开记录在日志中,防止 CA 误签
  • 加密 ClientHello (ECH):隐藏握手阶段的域名信息,防止 SNI 泄露
  • DNS-over-HTTPS/QUIC (DoH/DoQ):加密 DNS 查询,彻底终结”域名明文”这个最后的非加密环节

HTTPS 不是银弹。它保护的是传输链路,不保护端点(服务器或客户端被攻破照样无济于事),不保护用户隐私(CA 知道你在访问谁,IP 地址暴露了通信双方)。但它仍然是互联网安全最底层的基石——没有 HTTPS,其余所有安全措施都是空中楼阁。

本文由作者按照 CC BY 4.0 进行授权

© 独行的风. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

本站总访问量 本站访客数 本文阅读量