在c++程序开发中,私有配置信息如数据库连接参数、接口密钥等内容如果直接明文存储,存在被篡改和泄露的风险。将配置信息加密后保存为带头部校验的二进制dat文件,既能提升存储安全性,又能通过头部校验快速判断文件是否被损坏或篡改。

核心实现思路
整个实现流程分为三个核心部分:首先定义包含校验信息和配置数据的结构体,其次选择合适的加密算法对配置内容进行加密,最后按照固定格式将头部和加密后的数据写入二进制dat文件。读取时先校验头部信息,再解密数据得到原始配置。
基础结构定义
我们首先定义dat文件的头部结构,用于存储校验信息,再定义配置信息结构,包含需要加密的私有配置内容。
#include <iostream>
#include <fstream>
#include <cstring>
#include <vector>
#include <openssl/aes.h>
#include <openssl/rand.h>
// 头部校验结构,固定长度32字节
struct DatHeader {
char magic[4]; // 固定魔数,用于标识文件类型,设为"CDAT"
uint32_t version; // 文件版本号,当前设为1
uint32_t data_size; // 加密后数据的长度
uint32_t crc32; // 加密数据的CRC32校验值
uint8_t reserved[20];// 预留字段,用于后续扩展
};
// 私有配置结构,存储需要加密的配置信息
struct PrivateConfig {
char db_host[64]; // 数据库主机地址
uint16_t db_port; // 数据库端口
char db_user[32]; // 数据库用户名
char db_password[64];// 数据库密码
char api_key[128]; // 接口密钥
};
// CRC32计算函数,用于校验数据完整性
uint32_t calculate_crc32(const uint8_t* data, size_t length) {
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < length; ++i) {
crc ^= data[i];
for (int j = 0; j < 8; ++j) {
if (crc & 1) {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
}
return crc ^ 0xFFFFFFFF;
}
AES加密实现
这里选择AES-256-CBC加密算法对配置信息进行加密,该算法安全性较高,适合存储私有配置。加密前需要对数据进行PKCS7填充,保证数据长度是AES块大小的整数倍。
// AES-256-CBC加密函数,返回加密后的数据
std::vector<uint8_t> aes_encrypt(const uint8_t* plaintext, size_t plaintext_len, const uint8_t* key, const uint8_t* iv) {
AES_KEY aes_key;
AES_set_encrypt_key(key, 256, &aes_key);
// PKCS7填充,AES块大小为16字节
size_t block_size = AES_BLOCK_SIZE;
size_t padding_len = block_size - (plaintext_len % block_size);
size_t ciphertext_len = plaintext_len + padding_len;
std::vector<uint8_t> ciphertext(ciphertext_len);
// 复制原始数据并填充
memcpy(ciphertext.data(), plaintext, plaintext_len);
for (size_t i = 0; i < padding_len; ++i) {
ciphertext[plaintext_len + i] = padding_len;
}
// 执行加密
AES_cbc_encrypt(ciphertext.data(), ciphertext.data(), ciphertext_len, &aes_key, (uint8_t*)iv, AES_ENCRYPT);
return ciphertext;
}
// AES-256-CBC解密函数,返回解密后的数据
std::vector<uint8_t> aes_decrypt(const uint8_t* ciphertext, size_t ciphertext_len, const uint8_t* key, const uint8_t* iv) {
AES_KEY aes_key;
AES_set_decrypt_key(key, 256, &aes_key);
std::vector<uint8_t> plaintext(ciphertext_len);
AES_cbc_encrypt(ciphertext, plaintext.data(), ciphertext_len, &aes_key, (uint8_t*)iv, AES_DECRYPT);
// 去除PKCS7填充
uint8_t padding_len = plaintext.back();
plaintext.resize(ciphertext_len - padding_len);
return plaintext;
}
配置加密保存为dat文件
接下来实现将配置信息加密并写入带头部校验的dat文件的逻辑,步骤如下:初始化头部信息、加密配置数据、计算校验值、写入头部和加密数据。
bool save_config_to_dat(const PrivateConfig& config, const char* dat_path, const uint8_t* aes_key) {
// 初始化头部信息
DatHeader header;
memcpy(header.magic, "CDAT", 4);
header.version = 1;
header.data_size = 0;
memset(header.reserved, 0, sizeof(header.reserved));
// 生成随机IV
uint8_t iv[AES_BLOCK_SIZE];
RAND_bytes(iv, AES_BLOCK_SIZE);
// 加密配置数据,将配置结构转为字节流加密
uint8_t* config_bytes = (uint8_t*)&config;
size_t config_len = sizeof(PrivateConfig);
std::vector<uint8_t> encrypted_data = aes_encrypt(config_bytes, config_len, aes_key, iv);
// 计算加密数据的CRC32校验值
header.data_size = encrypted_data.size();
header.crc32 = calculate_crc32(encrypted_data.data(), encrypted_data.size());
// 写入文件,先写头部,再写IV,最后写加密数据
std::ofstream ofs(dat_path, std::ios::binary);
if (!ofs.is_open()) {
std::cerr << "无法打开文件写入: " << dat_path << std::endl;
return false;
}
ofs.write((char*)&header, sizeof(DatHeader));
ofs.write((char*)iv, AES_BLOCK_SIZE);
ofs.write((char*)encrypted_data.data(), encrypted_data.size());
ofs.close();
return true;
}
从dat文件读取并解密配置
读取dat文件时,先校验头部的魔数和版本,再校验加密数据的CRC32值,确认文件完整后解密数据得到原始配置。
bool load_config_from_dat(PrivateConfig& config, const char* dat_path, const uint8_t* aes_key) {
std::ifstream ifs(dat_path, std::ios::binary);
if (!ifs.is_open()) {
std::cerr << "无法打开文件读取: " << dat_path << std::endl;
return false;
}
// 读取头部信息
DatHeader header;
ifs.read((char*)&header, sizeof(DatHeader));
if (memcmp(header.magic, "CDAT", 4) != 0 || header.version != 1) {
std::cerr << "文件格式错误或版本不匹配" << std::endl;
return false;
}
// 读取IV
uint8_t iv[AES_BLOCK_SIZE];
ifs.read((char*)iv, AES_BLOCK_SIZE);
// 读取加密数据
std::vector<uint8_t> encrypted_data(header.data_size);
ifs.read((char*)encrypted_data.data(), header.data_size);
// 校验CRC32
uint32_t calc_crc = calculate_crc32(encrypted_data.data(), encrypted_data.size());
if (calc_crc != header.crc32) {
std::cerr << "文件数据校验失败,可能被篡改或损坏" << std::endl;
return false;
}
// 解密数据
std::vector<uint8_t> decrypted_data = aes_decrypt(encrypted_data.data(), encrypted_data.size(), aes_key, iv);
if (decrypted_data.size() != sizeof(PrivateConfig)) {
std::cerr << "解密后数据长度异常" << std::endl;
return false;
}
// 将解密后的数据复制到配置结构
memcpy(&config, decrypted_data.data(), sizeof(PrivateConfig));
return true;
}
使用示例
以下是完整的使用示例,演示如何初始化配置、保存为dat文件、再从文件读取配置。
int main() {
// 初始化AES密钥,实际使用中可存储在安全位置,不要硬编码
uint8_t aes_key[32];
RAND_bytes(aes_key, 32);
// 初始化配置信息
PrivateConfig config;
strcpy(config.db_host, "127.0.0.1");
config.db_port = 3306;
strcpy(config.db_user, "root");
strcpy(config.db_password, "test_password_123");
strcpy(config.api_key, "sk_1234567890abcdef");
// 保存到dat文件
if (save_config_to_dat(config, "config.dat", aes_key)) {
std::cout << "配置保存成功" << std::endl;
}
// 从dat文件读取配置
PrivateConfig loaded_config;
if (load_config_from_dat(loaded_config, "config.dat", aes_key)) {
std::cout << "配置读取成功" << std::endl;
std::cout << "数据库地址: " << loaded_config.db_host << std::endl;
std::cout << "数据库端口: " << loaded_config.db_port << std::endl;
}
return 0;
}
注意事项
- AES密钥需要妥善保管,不要硬编码在程序中,可通过环境变量或安全密钥管理服务获取。
- CRC32校验只能检测数据是否被篡改,无法防止恶意篡改,若需要更高安全性可结合数字签名实现。
- 编译时需要链接OpenSSL库,编译命令示例:g++ -o config_demo config_demo.cpp -lssl -lcrypto。
- 头部结构使用固定长度字段,避免不同编译器对齐规则差异导致的读写错误。