最可靠方案是用OpenSSL EVP接口实现AES-256-CBC加密:必须随机生成16字节IV并随密文保存,密钥需通过PKCS5_PBKDF2_HMAC派生,采用PKCS#7填充,流式处理大文件,严格校验EVP_EncryptFinal_ex返回值,并优先选用AES-256-GCM以保障完整性。

用 OpenSSL 的 EVP_EncryptInit_ex 做 AES 加密最可靠
直接调用系统级加密库比手写算法安全得多,OpenSSL 是当前 C++ 项目里最常用、审核最充分的选择。别碰自己实现的 XOR 或简单移位——哪怕只是测试,也容易因密钥管理或填充方式出错导致数据不可恢复。
关键点在于:必须用 AES-256-CBC 模式(不是 ECB),手动处理 IV(初始化向量)并随密文一起保存,且必须使用 PKCS#7 填充。常见错误是把 IV 写死或忽略它,结果同一文件每次加密结果一样,或者解密时崩溃。
-
IV必须每次随机生成(用RAND_bytes),长度固定为 16 字节(AES 块大小) - 密钥不能直接用字符串字面量,要用
EVP_BytesToKey或更推荐的PKCS5_PBKDF2_HMAC衍生(加盐 + 迭代) - 加密后,把
IV放在密文前 16 字节,再写密文主体,解密时先读出前 16 字节作IV - 务必检查
EVP_EncryptFinal_ex返回值,它可能因填充失败返回 0
读写大文件时别一次性 fread 到内存
加密 1GB 文件时如果用 std::ifstream::rdbuf()->pubseekoff 读全量到 std::vector<uint8_t></uint8_t>,会触发 OOM 或卡死。得流式处理:每次读 4096 字节(或 16KB),加密后立刻写入输出文件。
注意 EVP_EncryptUpdate 的输入长度不一定要是块对齐的,但最后一次必须调用 EVP_EncryptFinal_ex 补齐填充;而 EVP_EncryptUpdate 输出缓冲区要预留额外 EVP_MAX_BLOCK_LENGTH 字节空间,否则可能溢出。
立即学习“C++免费学习笔记(深入)”;
- 输入缓冲区大小建议设为
4096,输出缓冲区大小设为4096 + EVP_MAX_BLOCK_LENGTH - 用
std::ofstream以std::ios::binary打开输出文件,避免 Windows 下\n被误转 - 不要用
std::string存二进制密文——它会把\0当结束符截断
编译时链接 libcrypto 和 libssl 容易漏掉 -ldl
Linux 下用 g++ -o enc enc.cpp -lssl -lcrypto 编译,运行时报 undefined symbol: dlopen 就是缺 -ldl。这是因为 OpenSSL 内部用 dlopen 加载引擎模块,即使你没显式启用引擎,链接器仍需要符号解析。
macOS 更麻烦:从 OpenSSL 3.0 开始默认禁用旧 API(如 EVP_enc_null),若用 Homebrew 安装,需确认是否启用了 legacy-provider;否则 EVP_get_cipherbyname("aes-256-cbc") 返回空指针,后续所有函数都失效。
- Linux 链接命令应为:
g++ -o enc enc.cpp -lssl -lcrypto -ldl - macOS 上运行前需设环境变量:
export OPENSSL_CONF=/opt/homebrew/etc/openssl@3/openssl.cnf(路径按实际安装调整) - Windows 下用 vcpkg 安装时,确保选的是
openssl:x64-windows而非openssl-universal,后者不包含静态链接所需符号
忘记校验解密后的明文完整性等于没加密
单纯 AES 加密不防篡改。攻击者修改密文第 3 块,解密后可能只损坏第 3 和第 4 块明文,但程序照常返回“解密成功”。真实场景必须加 MAC,推荐用 EVP_aes_256_gcm 替代 CBC,它内置认证标签(tag)。
GCM 模式下,加密后需调用 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag) 取出 16 字节 tag,和密文一起存;解密时先用 EVP_CTRL_GCM_SET_TAG 设置 tag,再调用 EVP_DecryptFinal_ex —— 它会返回 -1 表示认证失败。
- IV(nonce)在 GCM 中必须唯一但不必保密,长度建议 12 字节(省去额外计算)
- 不能复用同一密钥+IV 加密两个不同文件,否则 GCM 安全性完全崩溃
- 如果必须用 CBC,至少在加密后追加一个
HMAC-SHA256(用独立密钥),并验证后再解密


















