网络安全基础

用生活化的比喻,让你理解 TLS 握手、证书链、常见攻击与防御——保护游戏通信安全的底层知识

前置知识:第06章 TCP/IP + 第07章 UDP与可靠传输(理解 TCP/UDP 协议基础)


阅读指南(初学者必看)

为什么你需要学习网络安全基础?

你的游戏通信每天都在面临安全威胁——中间人可以窃听、篡改、重放你的消息。你用 wss:// 但不知道为什么安全:

  • TLS 握手到底在做什么?为什么能防窃听?
  • 证书链是什么?浏览器怎么验证网站的证书?
  • 游戏通信应该怎么加密?

学完本章,你能回答:

  • TLS 1.3 的握手过程是怎样的?为什么比 TLS 1.2 快?
  • 证书链是怎么验证的?自签名证书为什么不被信任?
  • 常见的网络攻击有哪些?怎么防御?
  • 游戏通信应该怎么设计安全方案?

本文结构

第一部分:常见网络攻击与防御
第二部分:加密技术
第三部分:认证与授权
第四部分:TLS 握手过程(理解加密通信的建立)
第五部分:证书链与信任(理解身份验证)

9.1 常见网络攻击与防御

攻击 原理 防御
中间人攻击(MITM) 拦截通信,冒充对方 TLS + 证书验证
重放攻击 重复发送之前的合法请求 时间戳 + nonce + 序列号
DDoS 大量请求淹没服务器 CDN + 限流 + 验证码
SQL 注入 恶意 SQL 拼入查询 参数化查询
XSS 注入恶意脚本 转义 + CSP
CSRF 冒用用户身份发请求 CSRF Token + SameSite Cookie

XSS 防护

class XSSProtection {
  static escapeHtml(str) {
    const escapeMap = {
      '&': '&amp;', '<': '&lt;', '>': '&gt;',
      '"': '&quot;', "'": '&#x27;', '/': '&#x2F;'
    };
    return str.replace(/[&<>"'/]/g, char => escapeMap[char]);
  }

  static createCSPPolicy() {
    return {
      'default-src': "'self'",
      'script-src': "'self' 'unsafe-inline'",
      'style-src': "'self' 'unsafe-inline'",
      'img-src': "'self' data: https:",
      'connect-src': "'self' wss: https:",
      'object-src': "'none'",
      'frame-src': "'none'"
    };
  }
}

CSRF 防护

class CSRFProtection {
  static generateToken() {
    let array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
  }

  static addTokenToHeaders(headers) {
    let token = this.getCSRFCookie();
    if (token) {
      headers['X-XSRF-TOKEN'] = token;
    }
    return headers;
  }

  static setCSRFCookie(token) {
    document.cookie = `XSRF-TOKEN=${token}; path=/; SameSite=Strict`;
  }
}

9.2 加密技术

对称加密

class SymmetricEncryption {
  static async generateKey() {
    return crypto.subtle.generateKey(
      { name: 'AES-GCM', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );
  }

  static async encrypt(data, key) {
    let iv = crypto.getRandomValues(new Uint8Array(12));
    let encoder = new TextEncoder();
    let encrypted = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv },
      key,
      encoder.encode(data)
    );
    return { iv: Array.from(iv), data: Array.from(new Uint8Array(encrypted)) };
  }

  static async decrypt(encryptedData, key) {
    let iv = new Uint8Array(encryptedData.iv);
    let data = new Uint8Array(encryptedData.data);
    let decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv },
      key,
      data
    );
    return new TextDecoder().decode(decrypted);
  }
}

非对称加密

class AsymmetricEncryption {
  static async generateKeyPair() {
    return crypto.subtle.generateKey(
      {
        name: 'RSA-OAEP',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: 'SHA-256'
      },
      true,
      ['encrypt', 'decrypt']
    );
  }

  static async encrypt(data, publicKey) {
    let encrypted = await crypto.subtle.encrypt(
      { name: 'RSA-OAEP' },
      publicKey,
      new TextEncoder().encode(data)
    );
    return Array.from(new Uint8Array(encrypted));
  }
}

哈希函数

class HashFunctions {
  static async sha256(data) {
    let encoder = new TextEncoder();
    let hash = await crypto.subtle.digest('SHA-256', encoder.encode(data));
    return Array.from(new Uint8Array(hash))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }

  static async hmac(key, data) {
    let encoder = new TextEncoder();
    let cryptoKey = await crypto.subtle.importKey(
      'raw', encoder.encode(key),
      { name: 'HMAC', hash: 'SHA-256' },
      false, ['sign']
    );
    let signature = await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(data));
    return Array.from(new Uint8Array(signature))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }
}

9.3 认证与授权

JWT 认证

class JWT {
  static base64UrlEncode(str) {
    return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
  }

  static base64UrlDecode(str) {
    str = str.replace(/-/g, '+').replace(/_/g, '/');
    while (str.length % 4) str += '=';
    return atob(str);
  }

  static async sign(payload, secret) {
    let header = { alg: 'HS256', typ: 'JWT' };
    let headerEncoded = this.base64UrlEncode(JSON.stringify(header));
    let payloadEncoded = this.base64UrlEncode(JSON.stringify(payload));
    let signature = await HashFunctions.hmac(secret, `${headerEncoded}.${payloadEncoded}`);
    let signatureEncoded = this.base64UrlEncode(
      String.fromCharCode(...signature.match(/.{2}/g).map(s => parseInt(s, 16)))
    );
    return `${headerEncoded}.${payloadEncoded}.${signatureEncoded}`;
  }

  static async verify(token, secret) {
    let parts = token.split('.');
    if (parts.length !== 3) return null;
    let [headerEncoded, payloadEncoded, signatureEncoded] = parts;
    let expectedSignature = await HashFunctions.hmac(secret, `${headerEncoded}.${payloadEncoded}`);
    // ... 验证签名
    let payload = JSON.parse(this.base64UrlDecode(payloadEncoded));
    if (payload.exp && payload.exp < Date.now() / 1000) return null;
    return payload;
  }
}

9.4 TLS 握手过程

生活类比:TLS 握手就像两个人在公开场合约定一个暗号。他们先确认对方身份(证书验证),然后安全地交换密钥(密钥协商),之后所有对话都加密(对称加密)。

TLS 1.3 握手(简化版):

客户端                                    服务器
  │                                         │
  │ ── ClientHello ────────────────────────▶ │  1. 支持的密码套件
  │    (密钥共享 + 支持的套件)               │     + 密钥共享数据
  │                                         │
  │ ◀── ServerHello ────────────────────── │  2. 选定套件
  │    (选定套件 + 密钥共享 + 证书)          │     + 服务器密钥共享
  │                                         │     + 证书链
  │ ── Finished ──────────────────────────▶ │  3. 验证完成
  │    (加密握手摘要)                       │
  │                                         │
  │ ←── 应用数据(加密) ────────────────→ │  4. 开始加密通信

TLS 1.3 vs TLS 1.2:
- TLS 1.2:2 RTT 握手
- TLS 1.3:1 RTT 握手
- TLS 1.3 0-RTT:重连时 0 RTT

9.5 证书链与信任

证书链验证:

根证书(DigiCert Root CA)── 操作系统/浏览器内置信任
  │
  └── 中间证书(DigiCert SHA2 Secure Server CA)
        │
        └── 终端证书(game.example.com)

验证流程:
1. 检查终端证书是否由中间 CA 签发(验证签名)
2. 检查中间证书是否由根 CA 签发(验证签名)
3. 根 CA 在信任列表中 → 信任!
4. 检查终端证书的有效期、域名、用途

自签名证书:
- 自己给自己签发 → 浏览器不信任
- 游戏开发/测试时常用
- 生产环境不能用

9.6 游戏安全特殊考虑

游戏协议安全:
- 消息加密:AES-256-GCM / ChaCha20-Poly1305
- 消息签名:HMAC-SHA256
- 防重放:时间戳 + 序列号 + nonce
- 防篡改:签名校验

WebSocket 安全:
- wss://(WebSocket Secure)= WebSocket + TLS
- 游戏必须用 wss://,不要用 ws://
- Origin 校验:只允许来自游戏域名的连接
class GameAccountSecurity {
  constructor() {
    this.maxLoginAttempts = 5;
    this.lockoutDuration = 15 * 60 * 1000;
    this.loginAttempts = new Map();
  }

  async login(username, password) {
    if (this.isLockedOut(username)) {
      throw new Error('Account locked. Try again later.');
    }
    // ... 验证密码
    this.clearFailedAttempts(username);
    return this.createSession(user);
  }

  async hashPassword(password) {
    let salt = Array.from(crypto.getRandomValues(new Uint8Array(16)))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
    let hash = await HashFunctions.pbkdf2(password, salt, 100000, 32);
    return `${salt}:${hash}`;
  }
}

自问自答

Q:TLS 1.3 为什么比 TLS 1.2 快? A:TLS 1.3 把握手从 2-RTT 缩短到 1-RTT。核心改进:1)客户端在 ClientHello 中直接发送密钥共享数据(Key Share),不需要先协商再共享;2)去掉了不安全的密码套件,减少了协商轮次;3)0-RTT 重连——如果之前建立过连接,客户端可以直接发送加密数据,无需等待服务器响应。

Q:自签名证书为什么不能用于生产? A:自签名证书的信任链不经过任何权威 CA。任何人都可以生成自签名证书,包括攻击者。如果信任自签名证书,中间人可以生成自己的证书冒充服务器,客户端无法区分真假。生产环境必须使用受信任 CA 签发的证书(Let's Encrypt 免费提供)。

Q:游戏通信应该用对称加密还是非对称加密? A:两者配合使用!TLS 的做法就是标准答案:1)非对称加密(ECDHE)用于密钥协商——安全地交换对称密钥;2)对称加密(AES-GCM/ChaCha20)用于数据传输——性能好。非对称加密慢但安全,对称加密快但需要安全地交换密钥。两者配合既安全又高效。

Q:为什么游戏必须用 wss:// 而不是 ws://? A:ws:// 是明文传输,三个风险:1)窃听——同一 WiFi 的人可以抓包看到你的游戏数据;2)篡改——中间人可以修改你的消息(如修改购买金额);3)冒充——攻击者可以伪装成服务器发假消息。wss:// = WebSocket + TLS,加密+认证+完整性保护,三重保障。

Q:怎么防止游戏消息被重放? A:每条消息加三个字段:1)时间戳——服务器检查时间差,超过阈值拒绝;2)序列号——严格递增,重复的序列号拒绝;3)nonce——随机数,确保每次请求唯一。三者结合,即使攻击者截获了消息,也无法重放(时间戳过期、序列号重复、nonce 已使用)。


实践任务

  • 任务1:用 Wireshark 抓包观察 TLS 1.3 握手过程,识别 ClientHello、ServerHello、证书、Finished 消息
  • 任务2:用 openssl s_client -connect google.com:443 连接 HTTPS 服务器,查看证书链
  • 任务3:配置 Nginx HTTPS(使用 Let's Encrypt 免费证书),理解证书配置流程
  • 任务4:实现一个简单的消息签名验证(HMAC-SHA256),理解消息防篡改
  • 任务5:用 mitmproxy 尝试中间人攻击自己的测试服务器,理解 MITM 的原理和 TLS 的防护

与其他章节的关联

本章内容 关联章节 关联点
TLS 握手 第06章 TCP/IP TLS 在 TCP 连接建立后进行握手
证书链 第07章 UDP与可靠传输 QUIC 内置 TLS 1.3,连接建立和加密一起完成
游戏安全 第08章 HTTP/2与HTTP/3 HTTP/2 和 HTTP/3 都强制 HTTPS
消息加密 第10章 实战篇 游戏通信安全需要自定义加密方案

上一章:08-HTTP2与HTTP3 | 下一章:10-实战篇用底层知识排查游戏问题