网络安全基础
用生活化的比喻,让你理解 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 = {
'&': '&', '<': '<', '>': '>',
'"': '"', "'": ''', '/': '/'
};
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-实战篇用底层知识排查游戏问题