XOR加密是最简可行方案,需手动序列化结构体字段、避免填充字节和std::string直接写入,密钥不可硬编码,建议用路径哈希生成,并添加magic header和版本号以保障兼容性。

加密保存配置前,先确认是否真需要“不可读”
很多场景下所谓“不可读”,其实只是防普通用户直接用记事本打开看明文。C++ 没有内置的“安全加密存储”机制,std::ofstream 写二进制本身不等于加密——它只是不换行、不转义,但原始字节仍可能被 hex 编辑器轻易还原。真要防逆向,得引入密钥和算法;只图防手抖打开,用 XOR 或 base64 就够用。
常见错误现象:std::ofstream("config.dat", std::ios::binary) 直接写 struct,结果文件能被 xxd 一眼看出字段名和字符串常量。
- 使用场景:桌面工具、本地客户端、非联网嵌入式设备的轻量配置保护
- 别对“加密”有幻觉:没有密钥管理、无防内存 dump、无反调试,就别标榜“安全”
- 性能影响几乎为零,但加解密逻辑会增加启动时的一次 CPU 开销(微秒级)
用 XOR 加密 struct 成二进制 dat 文件(最简可行方案)
XOR 是唯一能兼顾可逆性、零依赖、易验证的入门手段。只要密钥不硬编码在字符串里(比如从环境变量或硬件 ID 衍生),它对 casual inspection 就够用。
假设你有配置结构:
立即学习“C++免费学习笔记(深入)”;
struct Config {
int version;
char server[64];
bool auto_start;
};
实操建议:
- 别直接
write(reinterpret_cast<const char*>(&c), sizeof(c))——struct可能有填充字节,导致每次编译结果不一致 - 手动序列化字段:先
write(&c.version, sizeof(c.version)),再write(c.server, sizeof(c.server)),最后write(&c.auto_start, sizeof(c.auto_start)) - 写入前逐字节 XOR 密钥(比如
key = 0x5a):char encrypted = data[i] ^ key; - 读取时做同样 XOR,顺序和 key 必须完全一致,否则全乱
避免用 std::string 直接写进二进制文件
std::string 对象本身是小对象(含指针+size+capacity),直接 write 它只会把内存地址和长度写进去,不是你想要的字符串内容。下次读出来就是野指针或空内容。
常见错误现象:配置文件里看到一串乱码或全是 \x00,但 server 字段明明填了 “api.example.com”。
- 正确做法:写入
c.server这种 C 风格数组;若必须用std::string,则先write(s.c_str(), s.length()),再额外写一个uint32_t len = s.length()用于读取时分配空间 - 兼容性注意:Windows 和 Linux 下
sizeof(bool)都是 1,但某些旧编译器可能不同,建议统一用uint8_t替代bool - 路径如
"./config.dat"要检查目录是否存在,std::ofstream不会自动创建父目录
密钥怎么放才不算“写死”
把密钥写成 const uint8_t KEY = 0x9e; 就等于没加密——反编译一眼可见。但也不能真去调系统密钥库(那已超出“保存配置”的范畴)。
折中且实用的做法:
- 从程序路径生成哈希片段:用
GetModuleFileName(Windows)或/proc/self/exe(Linux)取路径,SHA256 后取第 3–6 字节当 XOR key - 用编译时间戳异或:定义
#define BUILD_KEY ((int)__TIME__[0] ^ (int)__DATE__[3]),每次构建 key 都变,但需确保重编译后旧配置仍能读(所以不推荐单独用这个) - 最稳妥:让用户首次运行时输入一个短口令,用 PBKDF2 衍生出 key 并缓存到注册表或 ~/.config/yourapp/keyseed —— 这已是完整密码学流程,别省略 salt 和迭代次数
容易被忽略的是:加密后的 config.dat 文件头没有任何标识,如果某天你换了算法,老版本程序会静默读错、行为异常,连报错都没有。加个 4 字节 magic header(比如 0x43464701)和版本号,能早几秒发现问题。



















