真正该加密的只有api_key、db_password、jwt_secret、private_rsa_key等敏感值;AES-256-GCM最实用,密钥须从系统凭据库读取或派生,nonce每次随机生成,解密失败应静默退出并降级启动,内存中密钥需及时清零。

加密前必须明确:哪些数据真需要加密
私有配置信息不是所有字段都值得加密。比如 log_level、max_retry 这类不影响安全的参数,加密反而增加维护成本和启动开销。真正该加密的只有:api_key、db_password、jwt_secret、private_rsa_key 等直接暴露即失守的敏感值。其他字段建议明文存于 JSON 或 INI 中,与加密段分离——这样调试时能快速验证非敏感逻辑是否正常。
用 AES-256-GCM 做对称加密最实用
AES-256-GCM 是目前 C++ 生产环境最平衡的选择:硬件加速支持好、有认证标签防篡改、单次加密即可得密文+tag。别用自己实现的 XOR 或简单移位,也别用已淘汰的 ECB 模式。OpenSSL 和 libsodium 都提供稳定接口,推荐后者(API 更简洁,内存安全更严格)。
关键实操点:
-
sodium_init()必须在首次加密前调用,否则crypto_aead_aes256gcm_encrypt()会返回 -1 - 密钥不能硬编码,应从系统凭据库(Windows DPAPI / macOS Keychain / Linux secret-service)读取,或由主程序运行时派生(如用
PBKDF2_HMAC_SHA256对用户口令 + salt 迭代 10 万次) - 每次加密必须用新生成的 12 字节随机 nonce,和密文一起写入
.dat文件头部,否则重放攻击可绕过 GCM 认证 - 加密后二进制结构建议固定为:
[4B magic][4B version][12B nonce][X B ciphertext][16B tag],方便后续版本兼容升级
读取时校验失败要静默退出,不报详细错误
解密失败常见原因:密钥错、nonce 被截断、文件被篡改、版本号不匹配。但程序绝不能在日志里输出 "AES decryption failed: bad tag" 或 "wrong key" 这类信息——这等于告诉攻击者“你猜对了一半”。正确做法是:
立即学习“C++免费学习笔记(深入)”;
- 调用
crypto_aead_aes256gcm_decrypt()后检查返回值是否为 0,不是则直接return nullptr或抛出泛化异常std::runtime_error("config load failed") - 绝不记录解密中间状态(如 tag 比较结果、填充长度)到磁盘日志
- 若配置加载失败,让主逻辑走降级路径(如用内置默认值启动,但禁用需密钥的功能模块)
示例片段(libsodium):
unsigned char decrypted[1024];
long long decrypted_len;
if (crypto_aead_aes256gcm_decrypt(decrypted, &decrypted_len,
nullptr, // aad
cipher_buf, cipher_len,
mac_tag, // 16B tag
nonce, // 12B
key) != 0) {
return std::nullopt; // 不 log,不 throw 具体原因
}
dat 文件本身不防逆向,要靠运行时保护
把配置加密成 config.dat 只解决静态窃取问题。一旦进程内存被 dump,密钥和解密后的内容仍在 RAM 中。所以关键不在“文件多难破解”,而在“密钥多难提取”:
- 密钥解出后立即调用
sodium_memzero(key, sizeof(key))清零,别等析构 - 敏感字符串变量声明为
std::vector<unsigned char>而非std::string,避免 std::string 内部缓存或 copy-on-write 带来的残留 - Windows 下可用
VirtualLock()锁定解密内存页,防止被换出到页面文件;Linux 下设mlock()+RLIMIT_MEMLOCK - 不要把整个配置对象设计成单例全局访问,改为按需解密、用完即焚的局部作用域对象
真正的防护边界在进程生命周期内,而非 .dat 文件的二进制形态。



















