最稳妥的加密方案是aes-gcm配合系统密钥库派生密钥、结构化文件头(含magic、版本、iv、盐值)及严格认证校验;须避免硬编码密钥、自研弱加密、iv复用,并全程检查openssl返回值。

加密前先明确密钥和算法选型
直接用 AES 是最稳妥的选择,ChaCha20 也行但 C++ 标准库不自带,得依赖 OpenSSL 或 libsodium。别碰自己写的“异或+移位”加密——它连防小白都做不到,更别说对抗内存 dump 或逆向。密钥不能硬编码在代码里,哪怕加了混淆,strings 命令一扫就露馅。实际做法是:运行时从系统密钥库(Windows DPAPI / macOS Keychain)或用户输入派生密钥,用 PBKDF2 或 Argon2 处理口令。
写入 dat 文件时必须分离元数据和密文
很多人把密文直接 fwrite 进去,结果解密时找不到 IV、盐值、版本号,或者搞混字节序。正确结构是固定头 + 可变载荷:struct ConfigHeader { uint32_t magic; uint8_t version; uint8_t cipher; uint16_t iv_len; uint8_t salt[16]; uint8_t iv[16]; },后面紧跟 AES-GCM 加密后的密文+认证标签。magic 用比如 0x44415445("DATE" ASCII),避免被当成普通二进制乱读。IV 必须每次生成新随机值,绝不能复用——AES-CBC 下复用 IV 等于明文可差分。
用 OpenSSL 实现加密要绕开 EVP_CIPHER_CTX 的坑
新版 OpenSSL(3.0+)废弃了老式 EVP_CIPHER_CTX_init,但直接用 EVP_CIPHER_CTX_new 后忘记 EVP_EncryptInit_ex 的第三个参数(nullptr 表示默认引擎)会导致返回 -1 却不报错。常见错误现象是加密后文件能写入,但解密时 EVP_DecryptFinal_ex 返回 0,且 ERR_print_errors_fp 显示 “bad decrypt”。关键步骤必须检查每一步返回值:
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, key, iv) != 1) { /* 失败 */ }
if (EVP_EncryptUpdate(ctx, out, &outlen, plaintext, plen) != 1) { /* 失败 */ }
if (EVP_EncryptFinal_ex(ctx, out + outlen, &final_len) != 1) { /* 失败 */ }
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag) != 1) { /* 失败 */ }
dat 文件加载时验证完整性比解密更重要
攻击者可能篡改 dat 文件的任意字节,如果跳过认证直接解密,轻则崩溃,重则泄露内存布局(比如 GCM 解密失败时部分明文仍会输出)。所以必须先校验 tag,再处理明文。用 fread 读完 header 后,立刻用 EVP_CIPHER_CTX_ctrl(... EVP_CTRL_GCM_SET_TAG ...) 设置 tag,然后才调用 EVP_DecryptUpdate 和 EVP_DecryptFinal_ex。如果 EVP_DecryptFinal_ex 返回 0,说明文件被篡改或密钥错误,此时应立即清空缓冲区并返回错误,而不是尝试解析残缺数据。
真正难的是密钥生命周期管理——密钥在哪生成、谁有权访问、过期怎么处理、用户换设备怎么迁移。这些没想清楚,加密反而会成为故障单高发点。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











