通信安全学习笔记
一份覆盖通信安全完整知识点的学习笔记。从密码学基础到协议安全,从进程间通信到工业总线,重点讲"为什么这么设计"(费曼式 📌 讲解),配 draw.io 图。
图表在 /通信安全学习笔记/diagrams/ 目录。图注名即原图文件名(如图注"图 1 · 编码加密哈希对比"对应 图1_编码加密哈希对比.d2 / 图1_编码加密哈希对比.d2.svg),改图请编辑同名 .d2 源文件后运行 scripts/build-d2.ps1 重新渲染。
怎么用这份笔记
- 学习/复习 → 顺序看正文,重点看 📌 类比和"为什么"
- 查方案 → 翻 附录 A 速查手册
- 防攻击 → 看 附录 B 常见攻击与防御对照表
- 找模块 → 查 附录 C RyBOS 模块矩阵,直接定位代码
1 · 编码 vs 加密 vs 哈希

1.1 三个概念为什么容易混淆
新手最容易犯的错误:把 Base64 当加密,把 MD5 当加密,把加密当编码。这三个东西目的完全不同,混用会出严重安全问题。
TIP
📌 打个比方:
- 编码(Base64/Hex)像"把中文翻译成英文"——谁都能翻回来,目的只是换个表示方式(兼容传输通道)
- 加密(AES/XOR)像"把信装进带锁箱子"——有钥匙才能打开,目的是保密
- 哈希(MD5/SHA256)像"给信件盖指纹章"——不可逆,目的是验证完整性(信没被篡改)
:::
1.2 对比表
|
编码 (Encoding) |
加密 (Encryption) |
哈希 (Hashing) |
| 目的 |
格式转换,兼容传输 |
保密,只有持钥者能读 |
完整性验证,不可逆 |
| 可逆 |
是(无密钥) |
是(需密钥) |
否 |
| 密钥 |
无 |
有(对称/非对称) |
无 |
| 示例 |
Base64, Hex, URL编码 |
AES, XOR, RSA |
MD5, SHA256 |
| 安全保证 |
无 |
保密性 |
完整性 |
1.3 Base64 — 编码不是加密
Base64 把任意字节流映射成 64 个可打印 ASCII 字符(A-Z a-z 0-9 + /),让二进制数据能安全穿过"只认文本"的通道。
原始: Hello → 3字节
Base64: SGVsbG8= → 4字符(= 是末尾填充)
:::tip
📌 为什么 Base64 不是加密?因为它的"解码表"是公开的、固定的,任何人都能还原。它的作用是适配通道,不是保密。如果你用 Base64 "保护"密码,等于把密码贴在墙上换了种字体。
📌 Base64 让数据变大约 33%:3 字节编码成 4 字符(4/3 ≈ 1.33)。这是为了兼容性付出的空间代价。
RyBOS 中的实现(cpp/Crypto):
std::string encoded = RyB::Base64::encode("Hello, World!");
// → "SGVsbG8sIFdvcmxkIQ=="
std::string decoded = RyB::Base64::decodeToString(encoded);
// → "Hello, World!"
1.4 Hex — 十六进制编码
Hex 把每个字节用两个十六进制字符表示。比 Base64 更直观——你能直接看到原始字节的值,调试加密结果时常用。
std::string hexStr = RyB::Hex::encode("Hello");
// → "48656c6c6f"
auto rawBytes = RyB::Hex::decode("48656c6c6f");
// → {0x48, 0x65, 0x6c, 0x6c, 0x6f}
TIP
📌 Hex vs Base64 怎么选?
- 调试/日志/校验和展示 → Hex(可读,直接对照字节值)
- 网络传输/嵌入文本 → Base64(更紧凑,33% vs 100%膨胀)
- 两者都是编码,都不提供保密性
:::
1.5 URL 编码
URL 中某些字符有特殊含义(& = ? + 空格),如果用户数据里包含这些字符,会破坏 URL 解析。URL 编码(百分号编码)把这些字符替换成 %XX 形式。
原始: name=张三 & age=20
URL编码: name=%E5%BC%A0%E4%B8%89%20%26%20age%3D20
在 HTTP 通信中,永远对用户输入做 URL 编码,否则可能导致参数注入。
2 · 对称加密

2.1 什么是对称加密
加密和解密用同一把密钥。发送方用密钥加密,接收方用同一把密钥解密。
明文 → [加密] → 密文 → 传输 → [解密] → 明文
↑ ↑
└──── 同一把密钥 ────────┘
:::tip
📌 打个比方:对称加密像一把锁配两把相同的钥匙。你把信锁在箱子里寄出去,对方用同样的钥匙开箱。问题是——怎么安全地把钥匙给对方?如果钥匙在寄送途中被截获,加密就形同虚设。这就是"密钥分发问题",非对称加密就是来解决这个的。
2.2 XOR 加密 — 最简单的对称加密
XOR(异或)是对称加密的"hello world"——原理极其简单,但揭示了对称加密的核心思想。
XOR 运算规则
0 ^ 0 = 0 相同→0
0 ^ 1 = 1 不同→1
1 ^ 0 = 1
1 ^ 1 = 0
XOR 加密原理
明文字节 ^ 密钥字节 = 密文字节
密文字节 ^ 密钥字节 = 明文字节 ← 同一密钥,再 XOR 一次就还原
TIP
📌 为什么 XOR 能加密?因为 XOR 有自逆性:A ^ B ^ B = A。加密是 A ^ B,解密是再 ^ B,同一个操作、同一把密钥。这就是"对称"的本质。
📌 XOR 的致命弱点:如果密钥比明文短,密钥会循环使用。攻击者拿到"明文+密文"对,XOR 一下就得到密钥流:明文 ^ 密文 = 密钥。所以XOR 绝不能用于真正的安全场景,只适合轻量级混淆。
RyBOS 中的实现(cpp/XorUtils):
// 单字节密钥
XorUtils::xorFile("data.bin", 82);
// 多字节密钥(循环使用)
std::vector<uint8_t> data = {0x00, 0x01, 0x02, 0x03, 0xFF};
uint8_t key[] = {'A', 'B', 'C'};
auto encrypted = XorUtils::xorEncryptDecrypt(data.data(), data.size(), key, 3);
// 加密过程: 0x00^'A', 0x01^'B', 0x02^'C', 0x03^'A'(循环), 0xFF^'B'
// 解密 = 再加密一次
auto decrypted = XorUtils::xorEncryptDecrypt(encrypted.data(), encrypted.size(), key, 3);
2.3 AES — 工业级对称加密
AES(Advanced Encryption Standard)是 NIST 在 2001 年确立的加密标准,取代了 DES。它是分组加密——把明文切成固定大小的块(16 字节),逐块加密。
AES 的三种密钥长度
| 变体 |
密钥长度 |
轮数 |
安全等级 |
| AES-128 |
16 字节 |
10 轮 |
足够日常使用 |
| AES-192 |
24 字节 |
12 轮 |
更高安全 |
| AES-256 |
32 字节 |
14 轮 |
军事/金融级 |
分组模式
ECB (Electronic Codebook):
明文块1 → 加密 → 密文块1
明文块2 → 加密 → 密文块2
每块独立加密,相同明文块→相同密文块(暴露模式)
CBC (Cipher Block Chaining):
明文块1 ⊕ IV → 加密 → 密文块1
明文块2 ⊕ 密文块1 → 加密 → 密文块2
每块先和前一块密文异或,相同明文块→不同密文块
TIP
📌 ECB 为什么不安全?因为 ECB 对每个块独立加密,相同的明文块产生相同的密文块。如果加密一张位图,加密后的图片轮廓依然可见——著名的"ECB 企鹅"就是例子。永远优先用 CBC(或 GCM)模式。
📌 CBC 为什么需要 IV?IV(初始向量)是一个随机值,让每次加密即使明文相同,密文也不同。IV 不需要保密,但每次加密必须用不同的 IV,否则 CBC 退化成类似 ECB 的效果。
RyBOS 中的实现(cpp/Crypto,Windows 使用 BCrypt API):
// 生成随机密钥和 IV
auto key = RyB::AES::generateKey(32); // AES-256
auto iv = RyB::AES::generateIV(); // 16 字节随机 IV
// 加密
auto ciphertext = RyB::AES::encrypt(plaintext, key, iv, RyB::AES::Mode::CBC);
// 解密
auto decrypted = RyB::AES::decrypt(ciphertext, key, iv, RyB::AES::Mode::CBC);
// 字符串便捷方法(自动派生密钥 + Base64 编码)
std::string enc = RyB::AES::encryptString("机密内容", "MyPassword", iv);
std::string dec = RyB::AES::decryptString(enc, "MyPassword", iv);
PKCS7 填充
AES 是分组加密,明文长度不一定是 16 的倍数。PKCS7 填充规则:缺几个字节就补几个,每个字节的值等于填充长度。
明文: [AA BB CC DD] → 缺 12 字节 → 填充 [0C 0C 0C ... 0C](12个0x0C)
明文: [AA BB ... 16字节] → 正好 16 字节 → 补一整块 [10 10 10 ... 10]
TIP
📌 为什么正好 16 字节也要补一整块?因为解密时要看最后一个字节的值来确定去掉了多少填充。如果原文恰好是 16 的倍数,不补的话,解密时看到最后一个字节可能是数据而非填充长度,造成歧义。补一整块 0x10,解密时一看就知道去掉 16 字节。
2.4 密钥派生
用户提供的密码通常不是 16/24/32 字节,需要"派生"成合法长度的 AES 密钥。
用户密码 "MyPassword" (10字节)
↓ MD5 哈希 (16字节) → AES-128 密钥
↓ SHA256 哈希 (32字节) → AES-256 密钥
auto key128 = RyB::AES::deriveKeyFromString("MyPassword", 16); // MD5 → 16字节
auto key256 = RyB::AES::deriveKeyFromString("MyPassword", 32); // SHA256 → 32字节
WARNING
🚧 简单哈希派生的弱点:直接对密码做一次 MD5/SHA256 派生密钥,容易被彩虹表攻击。生产环境应使用 PBKDF2 / bcrypt / scrypt 等"慢速派生"函数,增加暴力破解成本。
3 · 哈希与消息摘要
3.1 哈希是什么
哈希函数把任意长度的输入压缩成固定长度的输出("指纹")。好的哈希函数满足:
- 确定性:相同输入永远产生相同输出
- 雪崩效应:输入改 1 bit,输出变化约 50%
- 不可逆:从输出无法推算输入
- 抗碰撞:很难找到两个不同输入产生相同输出
"hello" → MD5: 5d41402abc4b2a76b9719d911017c592 (32字符/16字节)
"hello!" → MD5: e0b3d2b... (完全不同)
"hello" → SHA256: 2cf24dba5fb0a30e26e83b2ac5b9e29e... (64字符/32字节)
3.2 MD5 vs SHA256
|
MD5 |
SHA256 |
| 输出长度 |
128 bit (16 字节) |
256 bit (32 字节) |
| 速度 |
快 |
较慢(约 MD5 的 1/2) |
| 安全性 |
已被攻破(碰撞攻击) |
目前安全 |
| 适用场景 |
文件校验、非安全场景 |
密码存储、数字签名、安全场景 |
TIP
📌 MD5 为什么"不安全"?2004 年王小云团队找到了 MD5 的碰撞方法——能在合理时间内构造两个不同文件,使它们的 MD5 相同。这意味着攻击者可以伪造"签名"。但 MD5 作为文件完整性校验(防误传,不防恶意篡改)仍然够用。
📌 密码存储应该用什么?不要直接 MD5(password)。应该用 PBKDF2 + SHA256 或 bcrypt,它们内置"加盐"和"多轮迭代",大幅增加暴力破解成本。
RyBOS 中的实现(cpp/Crypto):
std::string md5 = RyB::MD5::hash("Hello");
// → "8b1a9953c4611296a827abf8c47804d7"
std::string sha = RyB::SHA256::hash("Hello");
// → "185f8db32271fe25f561a6fc938b2e264302ec435f7e4b5b..."
3.3 消息认证码 (MAC)
哈希只能验证"数据没变",但不能验证"是谁发的"。攻击者可以篡改数据后重新计算哈希。MAC(Message Authentication Code)用密钥参与哈希计算,没有密钥就无法伪造正确的 MAC。
HMAC-SHA256(key, message) → MAC值
发送方: message + HMAC(key, message) → 接收方
接收方: 用同样的 key 重新计算 HMAC,比对是否一致
TIP
📌 哈希 vs MAC 的区别:
- 哈希:任何人都能计算 → 只能防"误传/损坏",不能防"恶意篡改"
- MAC:需要密钥才能计算 → 既防"篡改"又验证"来源"
📌 什么时候用 MAC?通信双方共享一个密钥时,每条消息附带 HMAC,接收方验通过才信任。这就是 TLS Record Layer 的做法。
3.4 加盐哈希
如果两个用户密码相同,哈希值也相同——攻击者查表就能批量破解。加盐是在密码后追加随机值再哈希:
不加盐: MD5("password") → 所有人相同
加盐: MD5("password" + salt) → 每人不同(salt 随机生成并存储)
4 · 密钥管理与混淆

4.1 密钥分发的困境
对称加密的安全完全依赖密钥保密。但密钥怎么从发送方传给接收方?这是通信安全的核心难题:
方案1: 直接发送密钥 → 传输途中可能被截获
方案2: 预先共享密钥 → 需要物理接触,不适合远程
方案3: 非对称加密传密钥 → 用对方公钥加密对称密钥(HTTPS 的做法)
TIP
📌 HTTPS 怎么解决密钥分发:
- 客户端连上服务器,服务器出示证书(含公钥)
- 客户端验证证书可信(CA 签名链)
- 客户端生成随机对称密钥,用服务器公钥加密发过去
- 服务器用私钥解密,拿到对称密钥
- 后续通信用这个对称密钥加密(性能考虑)
这就是"非对称加密护送对称密钥"的混合方案。
4.2 密钥混淆 — 编译时保护
在嵌入式/桌面软件中,密钥经常需要硬编码在程序里。如果直接写 const char* key = "MySecret",用 strings 命令或十六进制编辑器就能直接看到。
密钥混淆的思路:把密钥以混淆形式存储在二进制中,运行时才还原,让逆向分析者无法直接提取。
RyBOS 中的实现(cpp/KeyObfuscator)——三层 XOR 混淆:
mask1 = 0xA7 ^ ((位置 × 13 + 7) & 0xFF)
mask2 = 0x5C ^ ((位置 × 29 + 3) & 0xFF)
mask3 = 0x3E ^ ((位置 × 17 + 11) & 0xFF)
混淆字节 = 原始字节 ^ mask1 ^ mask2 ^ mask3
// 编译时生成混淆代码
std::string code = RyB::KeyObfuscator::obfuscateToCode("MySecretKey", "obfKey");
// 输出可直接粘贴到代码中的 C++ 数组声明
// 运行时解混淆
auto obf = RyB::KeyObfuscator::obfuscate("MySecretKey");
std::string key = RyB::KeyObfuscator::deobfuscateToString(obf);
RyB::KeyObfuscator::secureZero(key); // 用完安全擦除
TIP
📌 三层 XOR 为什么比单层强?单层 XOR 相同字节在不同位置产生相同的混淆结果,容易被统计分析破解。三层不同步长的掩码组合,使相同字节在不同位置产生完全不同的混淆结果,大幅增加逆向难度。
📌 混淆不是加密:混淆只是提高逆向门槛,不是真正的加密。有经验的逆向工程师仍可通过动态调试在内存中抓到还原后的密钥。混淆的正确定位是"提高攻击成本",而非"不可破解"。
4.3 安全内存擦除
密钥用完后,内存中的明文密钥必须被安全擦除。但直接 memset 可能被编译器优化掉(因为编译器认为"变量之后不再使用,清零无意义")。
// BAD: 可能被编译器优化掉
memset(&key, 0, sizeof(key));
// GOOD: volatile 防止优化
void secureZero(void* ptr, size_t len) {
volatile uint8_t* p = static_cast<volatile uint8_t*>(ptr);
while (len--) *p++ = 0;
}
TIP
📌 为什么 volatile 能防止优化?volatile 告诉编译器"这个内存地址可能被外部访问,每次读写都必须真正执行"。编译器不敢省略对 volatile 指针的写操作,从而保证清零代码不被优化掉。
RyBOS 中的实现:
std::string key = getKey();
// ... 使用 key ...
RyB::KeyObfuscator::secureZero(key); // volatile 防优化擦除
5 · TCP/UDP 通信安全

5.1 TCP 的安全模型与缺陷
TCP 协议本身不提供任何安全保证:
- 无加密:数据明文传输,任何网络中间人都能抓包看到内容
- 无认证:无法确认对方真实身份,任何人都可以伪造源 IP
- 无完整性校验:数据可被中途篡改(TCP 校验和只防传输错误,不防恶意篡改)
客户端 ──明文TCP──→ 路由器 ──明文TCP──→ 服务器
↑
攻击者可以:
① 抓包看内容
② 篡改数据
③ 伪造连接(SYN Flood)
④ 劫持会话(TCP Hijacking)
TIP
📌 TCP 校验和为什么不算"完整性保护"?TCP 校验和是 16 位简单求和,设计目的是检测传输误码(电磁噪声导致 bit 翻转),不是防恶意篡改。攻击者篡改数据后重算校验和即可,毫无门槛。真正的完整性保护需要 MAC(带密钥的哈希)。
5.2 TCP 常见攻击与防御
| 攻击 |
原理 |
防御 |
| SYN Flood |
大量伪造 SYN 包,占满半连接队列 |
SYN Cookie、连接限速 |
| TCP 劫持 |
攻击者猜到序列号,注入数据 |
使用 TLS 加密 + MAC |
| 重放攻击 |
录制合法数据,稍后重发 |
序列号 + 时间戳 + Nonce |
| 中间人攻击 |
攻击者冒充双方中转 |
证书认证(TLS) |
| 端口扫描 |
逐个端口发 SYN 探测开放服务 |
防火墙、最小暴露原则 |
5.3 UDP 的安全特点
UDP 比 TCP 更"裸"——无连接、无握手、无序列号、无流量控制:
TCP: 三次握手 → 建立连接 → 有状态 → 可追踪
UDP: 直接发包 → 无连接 → 无状态 → 更难追踪
TIP
📌 UDP 为什么更危险:
- 无连接 = 无法验证来源(源 IP 可随意伪造)
- 无序列号 = 天然无法防重放
- 无握手 = 防火墙难以区分"合法响应"和"伪造包"
📌 UDP 反射放大攻击:攻击者伪造受害者 IP 发 UDP 请求到开放服务(如 DNS/NTP),服务端把大响应发到受害者。DNS 放大可达 50-70 倍。防御:配置服务端不响应外部查询、路由器做源地址验证(BCP38)。
5.4 在 TCP/UDP 上构建安全层
既然传输层不安全,我们在应用层构建安全:
┌─────────────────────────────────┐
│ 应用数据 (明文) │
├─────────────────────────────────┤
│ MAC 校验 (防篡改 + 防重放) │ ← 应用层安全
├─────────────────────────────────┤
│ 加密层 (AES 加密数据) │ ← 应用层安全
├─────────────────────────────────┤
│ TCP/UDP (传输) │ ← 不安全
├─────────────────────────────────┤
│ IP/链路层 │
└─────────────────────────────────┘
RyBOS 的分层设计正是如此——TcpSocket/UdpSocket 只管传输,Crypto 负责加密,CommandProtocol 负责 CRC 校验,各层各司其职。
// RyBOS: 在 TCP 上叠加加密 + CRC
RyB::TcpClient client;
client.connect("127.0.0.1", 8888);
// 发送前:加密
std::string plaintext = "敏感数据";
auto key = RyB::AES::deriveKeyFromString("shared_secret", 32);
auto iv = RyB::AES::generateIV();
auto encrypted = RyB::AES::encrypt(
std::vector<uint8_t>(plaintext.begin(), plaintext.end()), key, iv);
// 用 CommandProtocol 封装为带 CRC 的帧
auto frame = RyB::FrameBuilder::buildFromHex(RyB::Hex::encode(encrypted));
client.send(frame.data(), frame.size());
RyBOS 中的 TCP 模块(cpp/TcpSocket)提供客户端和服务器:
// 服务器
RyB::TcpServer server;
server.onConnection([](RyB::TcpConnection& conn) {
conn.send("Welcome!");
conn.onReceive([](const uint8_t* data, size_t len) {
// 处理数据
});
});
server.listen(8888);
// 客户端
RyB::TcpClient client;
client.connect("127.0.0.1", 8888);
client.send("Hello Server!");
RyBOS 中的 UDP 模块(cpp/UdpSocket)支持服务器、客户端、广播和心跳:
// UDP 广播(设备发现场景)
RyB::UdpBroadcaster broadcaster;
broadcaster.broadcast(9999, "DISCOVER");
// UDP 心跳(连接保活)
RyB::UdpHeartbeat heartbeat;
heartbeat.setTarget("192.168.1.100", 8888);
heartbeat.setInterval(1000); // 1秒
heartbeat.setHeartbeatData("PING");
heartbeat.start();
6 · HTTP/HTTPS 安全

6.1 HTTP 的安全问题
HTTP 是明文协议,所有内容(包括密码)都以明文在网络上传输:
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=admin&password=Secret123 ← 明文密码!
TIP
📌 HTTP 三大原罪:
- 窃听:明文传输,Wireshark 抓包就能看到一切
- 篡改:运营商/路由器可以插入广告、修改页面
- 冒充:没有证书验证,你不知道对面真的是 example.com 还是钓鱼网站
HTTPS = HTTP + TLS,解决的就是这三个问题。
6.2 HTTPS (TLS) 工作原理
客户端 服务器
│ │
│ ── 1. ClientHello ──────────────────→ │ (支持的TLS版本、加密套件、随机数)
│ ←── 2. ServerHello + 证书 + 公钥 ──── │ (选定套件、服务器随机数、CA签发的证书)
│ │
│ 3. 验证证书(CA签名链、域名、有效期) │
│ │
│ ── 4. 生成预主密钥,用公钥加密 ────────→ │
│ │ 5. 用私钥解密,得到预主密钥
│ │
│ ←── 6. 双方用三个随机数生成会话密钥 ───→ │ (对称密钥,用于后续加密)
│ │
│ ←══ 7. 后续通信用会话密钥加密 ═════════→ │
TIP
📌 为什么需要三个随机数?客户端随机数 + 服务器随机数 + 预主密钥,三者一起生成会话密钥。这样即使预主密钥泄露,没有两个随机数也无法还原会话密钥。三个随机数保证了每次连接的会话密钥都不同,即使攻击者录制了全部流量也无法复现。
📌 为什么用非对称加密只传对称密钥,不直接用非对称加密所有数据?因为非对称加密(RSA/ECC)比对称加密(AES)慢 100-1000 倍。所以 TLS 用非对称加密"护送"对称密钥,之后全用对称加密——兼顾安全和性能。
6.3 HTTP 安全头
即使不启用 HTTPS,HTTP 头也能提供一定的安全防护:
| 头字段 |
作用 |
示例 |
Strict-Transport-Security |
强制 HTTPS(HSTS) |
max-age=31536000; includeSubDomains |
X-Content-Type-Options |
禁止 MIME 嗅探 |
nosniff |
X-Frame-Options |
防点击劫持(禁止被 iframe 嵌入) |
DENY |
Content-Security-Policy |
限制资源加载来源 |
default-src 'self' |
X-XSS-Protection |
浏览器 XSS 过滤 |
1; mode=block |
6.4 HTTP 认证安全
Basic Auth: Authorization: Basic dXNlcjpwYXNz ← Base64(用户名:密码)
Bearer Token: Authorization: Bearer eyJhbGci... ← JWT
WARNING
🚧 Basic Auth 必须配合 HTTPS:Base64 不是加密,抓包就能还原密码。在 HTTP 上用 Basic Auth 等于裸奔。
JWT 不要存敏感信息:JWT 的 payload 只是 Base64 编码,任何人都能解码。签名只保证"未被篡改",不保证"保密"。
RyBOS 中的 HTTP 客户端(cpp/HttpClient):
RyB::HttpClient client;
client.setHeader("Authorization", "Bearer " + token);
client.setTimeout(10); // 10秒超时
auto response = client.post("https://api.example.com/data",
"{\"key\":\"value\"}",
"application/json");
if (response.statusCode == 200) {
// 处理 response.body
}
7 · WebSocket 安全

7.1 WebSocket 协议特点
WebSocket 在 HTTP 握手后"升级"为持久双向连接,解决了 HTTP 只能客户端发起请求的问题。
1. 客户端发起 HTTP Upgrade 请求:
GET /ws HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
2. 服务器返回 101 Switching Protocols:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
3. 之后转为 WebSocket 帧格式通信
7.2 WebSocket 帧格式与掩码
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
TIP
📌 为什么客户端发送的帧必须有掩码(MASK=1)?RFC 6455 规定:客户端→服务器的每帧必须用 4 字节掩码异或加密,服务器→客户端不需要。这不是为了保密,而是为了防止缓存投毒攻击——攻击者可能利用中间代理的缓存机制,把伪造的 HTTP 响应注入缓存。掩码让 WebSocket 帧数据看起来是随机的,不会被误认为 HTTP 内容。
📌 掩码不提供安全性:掩码密钥就在帧头里明文传输,任何人都能解。它的唯一目的是防止中间代理误解 WebSocket 流量。
RyBOS 中的实现(cpp/WebSocket):
// WebSocket 客户端
RyB::WebSocketClient client;
client.onMessage([](const std::string& msg) {
std::cout << "收到: " << msg << std::endl;
});
client.connect("ws://localhost:8080/ws");
client.send("Hello WebSocket!");
// WebSocket 服务器
RyB::WebSocketServer server;
server.onConnection([](RyB::WebSocketConnection& conn) {
conn.send("Welcome!");
});
server.listen(8080);
7.3 WebSocket 安全建议
| 风险 |
防御 |
| 明文传输 |
使用 wss://(WebSocket over TLS) |
| 跨站 WebSocket 劫持 |
验证 Origin 头,只允许可信来源 |
| 未授权连接 |
握手阶段验证 Cookie / Token |
| 消息注入 |
对消息内容做输入验证和转义 |
| 资源耗尽 |
限制连接数、消息大小、心跳超时 |
WARNING
🚧 WebSocket 不受同源策略保护:与 AJAX 不同,WebSocket 不受 SOP 限制——任何页面的 JS 都能连到任意 WebSocket 服务器。必须在服务器端验证 Origin 头,否则攻击者可以在恶意页面中连上你的 WebSocket 服务。
8 · 命令帧协议安全

8.1 为什么需要帧协议
裸 TCP 是字节流,没有"消息边界"概念。发送方发了两条 100 字节的消息,接收方可能一次收到 200 字节,也可能分 3 次收到 50+80+70 字节。帧协议就是定义"一条完整消息的边界和格式"。
没有帧协议:
发送: [消息A 100字节] [消息B 100字节]
接收: [150字节] [50字节] ← 消息边界丢失!
有帧协议:
发送: [帧头][消息A][CRC][帧尾] [帧头][消息B][CRC][帧尾]
接收: 缓冲区累积 → 检测帧头/帧尾 → 提取完整帧
8.2 RyBOS CommandProtocol 帧格式
RyBOS 的 cpp/CommandProtocol 模块定义了通用命令帧协议:
发送帧:
[FA FA] [cmd_data ...] [CRC16_BE] [AF AF]
帧头 命令数据 校验和 帧尾
接收帧:
[FA FA] [len_hi len_lo] [fixed_fields + payload]
帧头 长度(大端) 总长 = 13 + payload_length
8.3 帧协议的安全要素
| 要素 |
作用 |
RyBOS 实现 |
| 帧头/帧尾 |
定位帧边界,抗数据流错位 |
FA FA / AF AF |
| 长度字段 |
防止缓冲区溢出(读取前知道该读多少) |
大端 2 字节 |
| CRC 校验 |
检测传输损坏,防误解析 |
CRC-16/XMODEM |
| 流式解析 |
累积缓冲 + 帧边界检测 |
FrameParser |
TIP
📌 为什么帧头要用两个字节而不是一个?如果帧头只有一个字节 0xFA,那么命令数据中恰好出现 0xFA 就会被误认为帧头。两个字节 0xFA 0xFA 降低了误匹配概率——数据中连续出现两个 0xFA 的概率远低于一个。
📌 CRC 防的是什么?CRC 防的是传输误码(电磁干扰、线缆噪声),不是恶意篡改。攻击者篡改数据后可以重算 CRC。要防恶意篡改,需要 MAC(带密钥的哈希)。但在工业控制场景中,CRC 已经足够——威胁主要来自环境噪声而非人为攻击。
8.4 CRC-16 校验原理
CRC(循环冗余校验)把数据看作一个大二进制数,除以一个约定的"生成多项式",余数就是 CRC 值。
// RyBOS 中的 CRC-16
RyB::CRC16 crc;
uint16_t checksum = crc.calculate(data, length);
// 帧构建(自动附加 CRC)
auto frame = RyB::FrameBuilder::buildFromHex("0001000000A10AFF");
// → FA FA 00 01 00 00 00 A1 0A FF [CRC16] AF AF
// 帧验证
bool valid = RyB::FrameUtils::verifySendFrame(frame);
8.5 流式帧解析的安全考虑
网络数据是分片到达的,帧可能被切散在多个 TCP 包中。FrameParser 采用流式累积策略:
RyB::FrameParser parser;
parser.setFrameCallback([](const std::vector<uint8_t>& frame) {
if (RyB::FrameUtils::verifySendFrame(frame)) {
// CRC 校验通过,处理帧
}
});
// 持续喂入收到的数据(可能是不完整的片段)
parser.feed(chunk1); // 可能只收到半个帧
parser.feed(chunk2); // 凑齐了,回调触发
TIP
📌 流式解析为什么重要:如果假设"一次 recv() 就能收到一个完整帧",在网络条件差时会丢帧。正确的做法是维护一个接收缓冲区,每次 recv() 追加数据,然后从缓冲区中扫描帧头/帧尾,提取完整帧,剩余不完整的数据留到下次。
📌 缓冲区溢出防御:帧解析器必须限制缓冲区最大长度。如果攻击者持续发送数据但永远不出现帧尾,缓冲区会无限增长导致 OOM。设置最大缓冲区大小,超过就清空重来。
8.6 命令序列与重试安全
RyBOS 的 CommandSequence 支持按顺序执行多条命令,每条命令可设置延时:
RyB::CommandSequence seq;
seq.add("0001000000A10AFF", 200, "获取固件版本");
seq.add("00010000009A00FF", 100, "获取SN");
seq.execute(sendFunction, delayFunction);
TIP
📌 命令序列的安全考虑:
- 超时机制:每条命令必须有超时,防止设备无响应时永久阻塞
- 重试限制:失败重试次数要有上限,否则可能形成"重试风暴"
- 顺序校验:敏感操作(如固件升级)应要求严格顺序,跳步应拒绝
- 幂等性:重试时确保命令幂等——执行两次和一次效果相同
:::
9 · 管道与命名管道

9.1 管道的安全模型
管道(Pipe)是 Unix 最古老的 IPC 机制。命名管道(Named Pipe / FIFO)在文件系统中有路径,可以跨进程通信。
匿名管道: 父进程 ──→ [管道] ──→ 子进程 (只能有血缘关系的进程间)
命名管道: 进程A ──→ [/tmp/myfifo] ──→ 进程B (任意进程,通过路径访问)
:::tip
📌 命名管道的安全特点:
- 同机通信:数据不经过网络,天然防窃听
- 文件权限保护:命名管道有文件权限(
rwx),可以限制哪些用户/进程能读写
- 无加密需求:因为数据不出机器,操作系统权限就是安全边界
📌 但共享机器不安全:如果攻击者已经登录同一台机器,命名管道的文件权限就是最后的防线。确保命名管道的权限只允许授权用户访问:mkfifo mypipe && chmod 600 mypipe。
9.2 命名管道的安全风险
| 风险 |
说明 |
防御 |
| 权限过宽 |
默认权限可能允许其他用户读写 |
chmod 600 限制访问 |
| 竞态条件 |
创建和设置权限之间有时间窗口 |
mkfifo 后立即 chmod,或用 umask |
| 拒绝服务 |
恶意进程打开管道但不读写,阻塞合法进程 |
非阻塞模式 + 超时 |
| 符号链接攻击 |
攻击者用符号链接替换管道路径 |
使用绝对路径 + O_NOFOLLOW |
9.3 Windows 命名管道
Windows 的命名管道比 Unix 功能更丰富,支持:
- 管道服务器可以模拟客户端身份(Impersonation)
- 管道安全描述符(Security Descriptor)
- 管道实例计数
\\.\pipe\myPipeName ← Windows 命名管道路径格式
TIP
📌 Windows 管道的安全描述符:Windows 管道支持 SECURITY_ATTRIBUTES,可以精确控制哪些用户/组能访问。比 Unix 的 chmod 粒度更细——可以指定"允许读但不允许写"、"允许特定 SID 访问"等。
📌 Impersonation 的安全意义:管道服务器可以临时"变成"客户端的身份来执行操作,这样即使服务器进程以高权限运行,实际访问资源时用的是客户端权限,实现最小权限原则。但这也带来风险——如果客户端是恶意用户,Impersonation 后可能执行越权操作。所以必须设置 Impersonation 级别(如 SecurityImpersonation 限制为同机访问)。
10 · 共享内存
10.1 共享内存的安全模型
共享内存是最快的 IPC——多个进程映射同一块物理内存,直接读写,无需内核拷贝。但速度的代价是安全责任全在开发者。
进程A的虚拟地址空间 物理内存 进程B的虚拟地址空间
0x7f...1000 ───────→ [共享内存块] ←─────── 0x7f...2000
(映射) (同一块物理页) (映射)
TIP
📌 共享内存为什么最快:其他 IPC(管道、消息队列、Socket)都需要数据从用户态→内核态→用户态的两次拷贝。共享内存绕过内核,进程直接读写同一块物理内存,零拷贝。
📌 共享内存为什么最危险:
- 无同步:两个进程同时写同一地址 → 数据损坏
- 无边界:没有"消息"概念,纯裸内存,写越界直接破坏对方数据
- 无认证:任何知道 SHM key 的进程都能 attach
- 持久性:进程退出后共享内存仍然存在,可能泄露敏感数据
:::
10.2 共享内存的同步问题
共享内存本身不提供任何同步机制。必须配合信号量、互斥锁或原子操作来保证一致性。
进程A: 写入 data → 设置 flag
进程B: 轮询 flag → 读取 data
问题: 如果进程A写到一半被调度走,进程B看到 flag 已设置但 data 不完整
解决: 用信号量或 mutex 保证"写完才能读"
:::warning
🚧 共享内存 + 信号量的经典竞态:
- 进程A获取信号量,开始写数据
- 进程A写到一半崩溃 → 信号量没释放
- 进程B永远等不到信号量 → 死锁
防御:使用 SEM_UNDO 标志(进程崩溃时自动释放信号量),或用带超时的 sem_timedwait。
10.3 共享内存安全清单
| 风险 |
防御 |
| 未授权访问 |
设置正确的权限模式(0600),限制只有创建者可访问 |
| 数据竞争 |
配合信号量/互斥锁/原子操作 |
| 进程崩溃残留 |
使用 shmctl(IPC_RMID) 清理,或用 POSIX shm + ftruncate |
| 敏感数据残留 |
进程退出前 memset / secureZero 共享内存 |
| 缓冲区溢出 |
严格校验写入偏移和长度,共享内存段大小固定 |
11 · 本地 Socket (Unix Domain Socket)
11.1 本地 Socket vs 网络 Socket
本地 Socket(Unix Domain Socket, UDS)使用文件系统路径作为地址,不经过网络协议栈,效率接近共享内存但保留了 Socket 的 API。
网络 Socket: 客户端 → TCP/IP栈 → 网卡 → 路由 → 网卡 → IP栈 → 服务器
本地 Socket: 客户端 → 内核缓冲区 → 服务器 (不经过网络栈)
TIP
📌 本地 Socket 的安全优势:
- 不经过网络 = 天然防窃听、防篡改、防冒充
- 文件权限控制 = 可以限制哪些用户/进程能连接
- 支持传递文件描述符 = 可以安全地传递资源句柄
- 支持 SO_PEERCRED = 服务器可以获取客户端的 UID/GID/PID
📌 什么时候用 UDS 而非 TCP:同一台机器上的进程间通信,优先用 UDS。不仅更安全,而且更快(省去了 TCP 握手、校验和、拥塞控制等开销)。Docker、X11、systemd 都用 UDS。
11.2 本地 Socket 的安全特性
// 服务器端:获取客户端凭证(Linux)
struct ucred cred;
socklen_t len = sizeof(cred);
getsockopt(clientFd, SOL_SOCKET, SO_PEERCRED, &cred, &len);
// cred.uid = 客户端用户ID
// cred.pid = 客户端进程ID
// → 可以据此做访问控制
TIP
📌 SO_PEERCRED 为什么重要:网络 Socket 的源 IP 可以伪造,但 UDS 的 SO_PEERCRED 由内核设置,不可伪造。服务器可以精确知道"对面是哪个用户的哪个进程",实现基于身份的访问控制。这是 UDS 独有的安全特性。
11.3 Windows Named Pipe vs Unix Domain Socket
|
Windows Named Pipe |
Unix Domain Socket |
| 地址 |
\\.\pipe\name |
文件路径 /tmp/name.sock |
| 权限控制 |
安全描述符 (SD) |
文件权限 (chmod) |
| 身份验证 |
Impersonation |
SO_PEERCRED |
| FD 传递 |
不支持 |
SCM_RIGHTS |
| API |
CreateNamedPipe |
socket(AF_UNIX) |
RyBOS 中也有对应的本地通信模块(cpp/NamedPipe 和 qt/LocalSocket),分别对应 Windows 和跨平台场景。
12 · 串口通信安全

12.1 串口通信的特点
串口(Serial Port / RS-232 / RS-485)是工业领域最基础的通信方式。与网络通信不同,串口通信有其独特的安全模型:
特点:
- 物理层通信:数据通过电平信号在铜线上传输
- 点对点(RS-232)或多点总线(RS-485)
- 无协议栈:原始字节流,没有 TCP/IP 那样的分层
- 短距离(RS-232 < 15m)或中距离(RS-485 < 1200m)
- 低速率:通常 9600~115200 bps
TIP
📌 串口通信的安全模型与网络完全不同:
- 物理安全是前提:能接触到串口线 = 能窃听/篡改。串口安全依赖物理隔离
- 无内置加密:串口协议只有起始位/停止位/校验位,没有加密层
- 无认证机制:总线上任何设备都能发送数据
- 广播介质(RS-485):总线上所有设备都能听到所有数据
📌 工业场景为什么还在用串口:简单、可靠、低成本、实时性好。在工厂环境中,设备可能没有网络接口,但一定有串口。串口不会消失,所以串口安全必须重视。
12.2 串口通信的安全威胁
| 威胁 |
说明 |
防御 |
| 物理窃听 |
并联一根线到 RS-485 总线即可抓包 |
物理隔离、线缆屏蔽 |
| 数据篡改 |
总线上注入伪造指令 |
应用层 CRC + 加密 |
| 重放攻击 |
录制合法指令,稍后重发 |
序列号 + 时间戳 |
| 总线冲突 |
多设备同时发送导致数据损坏 |
主从轮询协议 |
| 未授权设备接入 |
陌生设备接入总线 |
应用层认证握手 |
12.3 串口安全加固方案
在串口上构建安全通信,需要在应用层叠加:
┌─────────────────────────────────┐
│ 应用数据 (明文) │
├─────────────────────────────────┤
│ HMAC 校验 (防篡改) │ ← 应用层
├─────────────────────────────────┤
│ AES 加密 (保密) │ ← 应用层
├─────────────────────────────────┤
│ 帧协议 (帧头/长度/CRC/帧尾) │ ← 协议层
├─────────────────────────────────┤
│ 串口 (起始位/数据位/停止位) │ ← 物理层
└─────────────────────────────────┘
RyBOS 的 cpp/Serial 模块负责串口传输层,cpp/CommandProtocol 负责帧协议,cpp/Crypto 负责加密:
// 串口 + 帧协议 + 加密的组合
RyB::SerialPort serial;
serial.open("COM3", 115200);
// 加密命令数据
auto key = RyB::AES::deriveKeyFromString("device_secret", 32);
auto iv = RyB::AES::generateIV();
auto encrypted = RyB::AES::encrypt(rawCommand, key, iv);
// 封装为帧
auto frame = RyB::FrameBuilder::buildFromHex(RyB::Hex::encode(encrypted));
serial.write(frame.data(), frame.size());
// 接收端:流式解析 + 解密
RyB::FrameParser parser;
parser.setFrameCallback([&](const std::vector<uint8_t>& frame) {
if (!RyB::FrameUtils::verifySendFrame(frame)) return; // CRC 校验
auto decrypted = RyB::AES::decrypt(payload, key, iv); // 解密
// 处理 decrypted
});
TIP
📌 串口加密的性能考虑:串口带宽很低(通常 < 100 KB/s),AES 加密引入的计算开销可以忽略。但加密后数据膨胀(PKCS7 填充 + IV + HMAC)会增加传输时间。在 9600 bps 的链路上,每增加 16 字节就要多传约 13 毫秒。所以串口加密应尽量减少帧数量,合并小命令。
📌 RS-485 主从模式的安全意义:RS-485 总线上指定一个主设备,只有主设备能主动发起通信,从设备只能响应。这天然防止了"从设备乱发数据"的问题——即使一个从设备被攻破,它也无法主动注入指令到总线。
13 · 工业总线安全
13.1 工业通信协议全景
| 协议 |
介质 |
速率 |
实时性 |
安全机制 |
场景 |
| CAN |
两线 |
1 Mbps |
好 |
CRC-15 |
电机/传感器 |
| CAN-FD |
两线 |
8 Mbps |
好 |
CRC-17/21 |
更多数据 |
| EtherCAT |
以太网 |
100 Mbps |
极好 |
无内置安全 |
伺服/工业 |
| Modbus RTU |
RS-485 |
115 kbps |
一般 |
CRC-16 |
PLC/传感器 |
| Modbus TCP |
以太网 |
100 Mbps |
一般 |
无加密 |
工控网络 |
| RS-232 |
三线 |
115 kbps |
一般 |
无 |
调试/简单设备 |
| RS-485 |
两线 |
10 Mbps |
一般 |
无 |
长距离/多设备 |
| MQTT |
以太网 |
依赖网络 |
一般 |
TLS/用户名密码 |
IoT 消息 |
TIP
📌 工业协议为什么普遍缺乏安全机制:这些协议设计于 1980-2000 年代,当时工业网络是物理隔离的(air gap),"安全靠不上网"。但随着工业互联网和 IoT 的兴起,工业设备越来越多地接入公网,"物理隔离"不再成立。Modbus TCP 明文传输、无认证的问题在工控安全(ICS Security)中是头号风险。
📌 工控安全的"纵深防御":
- 物理层:线缆隔离、端口锁
- 网络层:防火墙、VLAN 隔离、单向网闸
- 协议层:协议级认证(如 Modbus Security)
- 应用层:加密 + 认证 + 审计
- 管理层:访问控制策略、变更管理
:::
13.2 Modbus 安全分析
Modbus 是工业领域最广泛使用的协议,也是安全问题最突出的:
Modbus RTU (串口):
[地址] [功能码] [数据] [CRC16]
1字节 1字节 N字节 2字节
Modbus TCP (以太网):
[MBAP头] [功能码] [数据]
7字节 1字节 N字节
(无CRC!TCP校验和代替)
:::warning
🚧 Modbus 的安全缺陷:
- 无认证:任何能连到 TCP 502 端口的设备都能读写寄存器
- 无加密:所有数据明文传输
- 无完整性校验(TCP 版):依赖 TCP 校验和,不防篡改
- 功能码无授权:写线圈(功能码 0x05/0x06)和读线圈(0x01/0x03)用同一认证级别
真实案例:很多工控设备暴露在公网上,通过 Shodan 搜索 "port:502" 就能找到数万台开放 Modbus TCP 的设备。攻击者可以直接修改寄存器值,导致物理设备异常。
13.3 CAN 总线安全
CAN 总线是汽车和机器人中最常用的通信总线:
CAN 帧:
[SOF] [ID(11/29位)] [RTR] [DLC] [数据(0-8字节)] [CRC] [ACK] [EOF]
TIP
📌 CAN 总线的安全特点:
- CRC-15 校验:能检测多位错误,但防不了恶意篡改
- 总线仲裁:ID 越小优先级越高,攻击者可以用低 ID 抢占总线
- 广播介质:总线上所有节点都能听到所有帧
- 无认证:任何节点都能发送任何 ID 的帧
📌 CAN 总线攻击实例:2015 年 Jeep Cherokee 黑客事件——攻击者通过车联网娱乐系统入侵 CAN 总线,远程控制方向盘、刹车、引擎。这直接推动了汽车行业对 CAN 安全的重视。
CAN 安全加固方案:
- CAN FD + 加密:CAN FD 支持最多 64 字节数据段,足够容纳 AES 加密后的短指令
- 安全车载网关:在 CAN 总线和外部网络之间部署网关,过滤非法帧
- ID 白名单:只允许预注册的 ID 通过
- 应用层认证:在数据段内嵌入 HMAC
13.4 MQTT 安全
MQTT 是 IoT 领域最流行的消息协议,基于 TCP,支持 TLS:
MQTT 连接流程:
1. TCP 连接到 Broker (默认端口 1883/8883)
2. CONNECT 报文 (含 ClientID、Username、Password)
3. CONNACK 返回连接结果
4. SUBSCRIBE/PUBLISH 消息交互
5. PINGREQ/PINGRESP 心跳保活
TIP
📌 MQTT 安全最佳实践:
- 使用 MQTT over TLS (端口 8883):防止窃听和篡改
- 用户名/密码认证:Broker 验证客户端身份
- ACL 主题过滤:限制客户端只能订阅/发布特定主题
- 客户端证书:双向 TLS 认证,比用户名密码更强
- QoS 选择:QoS 1(至少一次)比 QoS 0(最多一次)更可靠,但要注意重去重
📌 MQTT 主题通配符的安全风险:# 匹配所有子主题,+ 匹配单层。如果给客户端订阅 sensor/# 的权限,它就能收到所有传感器数据。ACL 必须精确到具体主题,避免过宽的通配符。
14 · CRC 校验与数据完整性
14.1 校验 vs 哈希 vs MAC
| 机制 |
目的 |
防误码 |
防篡改 |
需密钥 |
示例 |
| 校验和 |
检测传输错误 |
弱 |
否 |
否 |
CRC-16, 异或和 |
| 哈希 |
完整性指纹 |
强 |
否 |
否 |
MD5, SHA256 |
| MAC |
认证+完整性 |
强 |
是 |
是 |
HMAC-SHA256 |
| 数字签名 |
认证+完整性+不可否认 |
强 |
是 |
是(非对称) |
RSA-SHA256 |
TIP
📌 四者的安全级别递增:
- 校验和:只防"线缆噪声" → 攻击者重算即可绕过
- 哈希:防"意外损坏" → 攻击者可以改数据+改哈希
- MAC:防"恶意篡改" → 攻击者没有密钥就无法伪造
- 数字签名:防"发送方否认" → 私钥只有发送方有,不可抵赖
📌 工业场景为什么用 CRC 而非 MAC:工业设备资源有限(单片机可能只有几 KB RAM),CRC 计算只需几个字节状态,而 HMAC-SHA256 需要更多计算和存储。且工业总线通常物理隔离,篡改风险低于误码风险。但在接入公网时,必须升级到 MAC 或加密。
14.2 CRC 原理详解
CRC 的数学本质:把数据看作一个大多项式,对生成多项式做模 2 除法,余数就是 CRC。
数据: M(x) = x^23 + x^16 + x^5 + x^2 + 1 (即 0x01000025)
生成式: G(x) = x^16 + x^12 + x^5 + 1 (CRC-CCITT, 0x1021)
CRC = M(x) * x^16 mod G(x)
常用 CRC 变体:
| 名称 |
多项式 |
初始值 |
应用 |
| CRC-16/XMODEM |
0x1021 |
0x0000 |
通用 |
| CRC-16/MODBUS |
0x8005 |
0xFFFF |
Modbus |
| CRC-16/CCITT |
0x1021 |
0xFFFF |
通信 |
| CRC-32 |
0x04C11DB7 |
0xFFFFFFFF |
以太网、ZIP |
RyBOS 中使用 CRC-16/XMODEM 变体,初始值可配置(默认 0x8848):
RyB::CRC16 crc(0x8848); // 自定义初始值
uint16_t result = crc.calculate(data, length);
TIP
📌 初始值为什么不为 0:如果初始值为 0,前导零字节不影响 CRC 结果(因为 0 ^ 0 = 0)。这意味着 0x00 0x01 和 0x00 0x00 0x01 的 CRC 相同。非零初始值让前导零也参与运算,避免这种歧义。
📌 RyBOS 为什么用 0x8848 而非标准值:这是 CameraToolBox 项目的遗留选择。非标准初始值在某种程度上也起到了"弱混淆"的作用——攻击者即使知道用的是 CRC-16/XMODEM,但不知道初始值也无法正确伪造 CRC。当然这种"安全"非常弱,不应依赖。
14.3 CRC 的检错能力
| 错误类型 |
CRC-16 检测率 |
| 单 bit 错误 |
100% |
| 双 bit 错误 |
100% |
| 奇数个 bit 错误 |
100% |
| 突发错误 (≤16 bit) |
100% |
| 突发错误 (>16 bit) |
1 - 2^(-16) ≈ 99.998% |
| 随机错误 |
1 - 2^(-16) ≈ 99.998% |
TIP
📌 CRC 为什么能 100% 检测单 bit 错误:因为 CRC 生成多项式至少有两个项(如 x^16 + x^12 + x^5 + 1),任何单 bit 翻转都会产生一个余数不为 0 的多项式。
📌 CRC 为什么不能 100% 检测所有错误:存在极低概率的"碰撞"——两个不同数据产生相同 CRC。对于 CRC-16,碰撞概率约 1/65536。在工业控制中这个概率足够低,但在安全场景中不可接受。
附录 A · 安全速查手册

A.1 加密方案选择
| 场景 |
推荐方案 |
RyBOS 模块 |
| 轻量级混淆 |
XOR 单字节密钥 |
cpp/XorUtils |
| 文件批量混淆 |
XOR 多字节密钥 |
cpp/XorUtils |
| 正式加密 |
AES-256-CBC + 随机 IV |
cpp/Crypto |
| 密码存储 |
PBKDF2-SHA256 + 盐 |
(需自行实现) |
| 密钥硬编码保护 |
三层 XOR 混淆 |
cpp/KeyObfuscator |
| 文件混淆 |
16 字节循环 XOR |
cpp/KeyObfuscator |
| 通信加密 |
AES + HMAC |
cpp/Crypto |
| 数据完整性 |
CRC-16 |
cpp/CommandProtocol |
A.2 通信协议选择
| 场景 |
推荐协议 |
RyBOS 模块 |
| 同机进程通信 |
Unix Domain Socket / Named Pipe |
cpp/NamedPipe, qt/LocalSocket |
| 同机高速共享 |
共享内存 + 信号量 |
cpp/SharedMemory |
| 网络可靠传输 |
TCP |
cpp/TcpSocket |
| 网络实时传输 |
UDP |
cpp/UdpSocket |
| Web API |
HTTP/HTTPS |
cpp/HttpClient |
| 双向实时通信 |
WebSocket |
cpp/WebSocket |
| 工业设备控制 |
串口 + 帧协议 |
cpp/Serial + cpp/CommandProtocol |
| 设备发现 |
UDP 广播 |
cpp/UdpSocket |
| 连接保活 |
UDP 心跳 |
cpp/UdpSocket |
A.3 安全检查清单
附录 B · 常见攻击与防御
| 攻击 |
层级 |
原理 |
防御 |
| 窃听 |
网络 |
抓包查看明文 |
加密(AES/TLS) |
| 中间人 |
网络 |
冒充双方中转 |
证书认证(CA/TLS) |
| 重放 |
协议 |
录制并重发合法数据 |
序列号 + 时间戳 + Nonce |
| 篡改 |
协议 |
修改传输中的数据 |
MAC / 数字签名 |
| SYN Flood |
TCP |
耗尽半连接队列 |
SYN Cookie + 限速 |
| UDP 放大 |
UDP |
伪造源 IP 反射攻击 |
BCP38 源地址验证 |
| XSS |
HTTP |
注入恶意脚本 |
输入转义 + CSP |
| CSRF |
HTTP |
伪造用户请求 |
Token + SameSite Cookie |
| 点击劫持 |
HTTP |
iframe 覆盖欺骗 |
X-Frame-Options |
| WS 劫持 |
WebSocket |
恶意页面连 WS |
验证 Origin |
| 缓冲区溢出 |
协议 |
超长数据覆盖内存 |
长度校验 + 安全函数 |
| 竞态条件 |
IPC |
时间窗口内非法操作 |
锁 + 原子操作 |
| 符号链接 |
IPC |
替换文件路径 |
O_NOFOLLOW + 绝对路径 |
| 侧信道 |
密码学 |
通过时间/功耗推断密钥 |
常数时间操作 + 屏蔽 |
| 暴力破解 |
密码学 |
逐个尝试密钥 |
强密钥 + 慢速哈希 |
附录 C · RyBOS 通信安全模块矩阵
| 模块 |
路径 |
功能 |
安全相关 |
| Crypto |
cpp/Crypto |
Base64/Hex/MD5/SHA256/AES/XOR |
加密、哈希、编码 |
| XorUtils |
cpp/XorUtils |
XOR 加解密(文件/目录/内存) |
轻量级混淆加密 |
| KeyObfuscator |
cpp/KeyObfuscator |
密钥混淆、安全擦除 |
编译时密钥保护 |
| CommandProtocol |
cpp/CommandProtocol |
帧协议、CRC-16、流式解析 |
数据完整性、帧边界 |
| TcpSocket |
cpp/TcpSocket |
TCP 客户端/服务器 |
传输层(需叠加加密) |
| UdpSocket |
cpp/UdpSocket |
UDP 通信、广播、心跳 |
传输层(需叠加加密) |
| HttpClient |
cpp/HttpClient |
HTTP GET/POST/PUT/DELETE |
应用层(用 HTTPS) |
| WebSocket |
cpp/WebSocket |
WebSocket 客户端/服务器 |
需验证 Origin + wss |
| Serial |
cpp/Serial |
串口通信 |
需叠加帧协议+加密 |
| NamedPipe |
cpp/NamedPipe |
Windows 命名管道 |
IPC 安全(权限控制) |
| SharedMemory |
cpp/SharedMemory |
共享内存 |
IPC 安全(同步+权限) |
| LocalSocket |
qt/LocalSocket |
Qt 本地 Socket |
IPC 安全(跨平台) |