在本地应用开发中,文件加密是保护敏感数据的常见需求,结合MySQL存储加密元数据、C++实现加密逻辑,可以搭建一套完整的简单文件加密功能,既完成文件内容的加密处理,又能通过数据库管理加密文件的相关信息。

环境准备
开发前需要准备对应的依赖环境,确保后续代码可以正常运行:
- 安装MySQL数据库,并且创建好对应的测试库和测试用户,确保C++程序可以正常连接数据库
- 安装C++开发环境,比如GCC或者Visual Studio,支持C++11及以上标准
- 安装OpenSSL库,用于实现AES加密逻辑,同时安装MySQL Connector/C++库,用于C++操作MySQL数据库
数据库表设计
我们需要在MySQL中创建一张表,用来存储加密文件的相关元数据,方便后续查询和管理,表结构设计如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| file_id | INT PRIMARY KEY AUTO_INCREMENT | 加密文件唯一ID |
| file_name | VARCHAR(255) | 原始文件名称 |
| encrypt_key | VARCHAR(64) | 加密使用的密钥(实际生产环境建议加密存储) |
| encrypt_time | DATETIME | 文件加密时间 |
| file_path | VARCHAR(512) | 加密后文件的存储路径 |
创建表的SQL语句如下:
CREATE TABLE `encrypt_file_record` ( `file_id` int NOT NULL AUTO_INCREMENT, `file_name` varchar(255) NOT NULL, `encrypt_key` varchar(64) NOT NULL, `encrypt_time` datetime NOT NULL, `file_path` varchar(512) NOT NULL, PRIMARY KEY (`file_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心加密逻辑实现
我们使用AES-256-CBC模式实现文件加密,加密前需要生成对应的密钥和初始化向量,以下是加密和解密的核心函数实现:
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <fstream>
#include <vector>
#include <string>
#include <iostream>
// 生成随机的初始化向量
std::vector<unsigned char> generate_iv() {
std::vector<unsigned char> iv(AES_BLOCK_SIZE);
RAND_bytes(iv.data(), AES_BLOCK_SIZE);
return iv;
}
// AES-256-CBC加密函数,返回加密后的数据和使用的IV
std::pair<std::vector<unsigned char>, std::vector<unsigned char>> aes_encrypt(const std::vector<unsigned char>& plaintext, const std::vector<unsigned char>& key) {
// 密钥长度需要是32字节(256位)
if (key.size() != 32) {
std::cerr << "密钥长度必须为32字节" << std::endl;
return {};
}
std::vector<unsigned char> iv = generate_iv();
AES_KEY aes_key;
AES_set_encrypt_key(key.data(), 256, &aes_key);
// 计算填充后的长度
int plaintext_len = plaintext.size();
int padded_len = (plaintext_len / AES_BLOCK_SIZE + 1) * AES_BLOCK_SIZE;
std::vector<unsigned char> ciphertext(padded_len);
// PKCS7填充
int padding_len = padded_len - plaintext_len;
std::vector<unsigned char> padded_plaintext = plaintext;
padded_plaintext.insert(padded_plaintext.end(), padding_len, padding_len);
AES_cbc_encrypt(padded_plaintext.data(), ciphertext.data(), padded_plaintext.size(), &aes_key, iv.data(), AES_ENCRYPT);
return {ciphertext, iv};
}
// AES-256-CBC解密函数
std::vector<unsigned char> aes_decrypt(const std::vector<unsigned char>& ciphertext, const std::vector<unsigned char>& key, const std::vector<unsigned char>& iv) {
if (key.size() != 32) {
std::cerr << "密钥长度必须为32字节" << std::endl;
return {};
}
AES_KEY aes_key;
AES_set_decrypt_key(key.data(), 256, &aes_key);
std::vector<unsigned char> plaintext(ciphertext.size());
AES_cbc_encrypt(ciphertext.data(), plaintext.data(), ciphertext.size(), &aes_key, (unsigned char*)iv.data(), AES_DECRYPT);
// 去除PKCS7填充
int padding_len = plaintext.back();
plaintext.resize(plaintext.size() - padding_len);
return plaintext;
}
文件加密与数据库存储实现
接下来实现完整的文件加密流程,读取原始文件内容,调用加密函数,将加密后的内容写入新文件,同时将元数据存入MySQL数据库:
#include <mysql_driver.h>
#include <mysql_connection.h>
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/statement.h>
#include <cppconn/prepared_statement.h>
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
#include <ctime>
// 读取文件内容到字节数组
std::vector<unsigned char> read_file(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
if (!file.is_open()) {
std::cerr << "无法打开文件: " << file_path << std::endl;
return {};
}
file.seekg(0, std::ios::end);
size_t file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> buffer(file_size);
file.read((char*)buffer.data(), file_size);
return buffer;
}
// 将字节数组写入文件
bool write_file(const std::string& file_path, const std::vector<unsigned char>& data) {
std::ofstream file(file_path, std::ios::binary);
if (!file.is_open()) {
std::cerr << "无法创建文件: " << file_path << std::endl;
return false;
}
file.write((const char*)data.data(), data.size());
return true;
}
// 字节数组转十六进制字符串,方便存入数据库
std::string bytes_to_hex(const std::vector<unsigned char>& bytes) {
std::string hex_str;
for (unsigned char byte : bytes) {
char buf[3];
sprintf(buf, "%02x", byte);
hex_str += buf;
}
return hex_str;
}
// 十六进制字符串转字节数组
std::vector<unsigned char> hex_to_bytes(const std::string& hex_str) {
std::vector<unsigned char> bytes;
for (size_t i = 0; i < hex_str.size(); i += 2) {
std::string byte_str = hex_str.substr(i, 2);
unsigned char byte = (unsigned char)strtol(byte_str.c_str(), nullptr, 16);
bytes.push_back(byte);
}
return bytes;
}
// 执行文件加密并存储记录到MySQL
void encrypt_file_and_save_record(const std::string& original_file, const std::string& encrypted_file, sql::Connection* conn) {
// 生成32字节的随机密钥
std::vector<unsigned char> key(32);
RAND_bytes(key.data(), 32);
// 读取原始文件
std::vector<unsigned char> file_content = read_file(original_file);
if (file_content.empty()) {
return;
}
// 执行加密
auto [ciphertext, iv] = aes_encrypt(file_content, key);
if (ciphertext.empty()) {
return;
}
// 将IV和密文一起写入加密文件,方便后续解密
std::vector<unsigned char> file_to_write;
file_to_write.insert(file_to_write.end(), iv.begin(), iv.end());
file_to_write.insert(file_to_write.end(), ciphertext.begin(), ciphertext.end());
if (!write_file(encrypted_file, file_to_write)) {
return;
}
// 将记录存入MySQL
try {
sql::PreparedStatement* pstmt = conn->prepareStatement(
"INSERT INTO encrypt_file_record (file_name, encrypt_key, encrypt_time, file_path) VALUES (?, ?, NOW(), ?)"
);
pstmt->setString(1, original_file);
pstmt->setString(2, bytes_to_hex(key));
pstmt->setString(3, encrypted_file);
pstmt->executeUpdate();
delete pstmt;
std::cout << "文件加密完成,记录已存入数据库" << std::endl;
} catch (sql::SQLException& e) {
std::cerr << "数据库操作失败: " << e.what() << std::endl;
}
}
完整调用示例
以下是主函数的调用示例,包含数据库连接和加密功能的触发:
int main() {
// 连接MySQL数据库
sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();
sql::Connection* conn = driver->connect("tcp://127.0.0.1:3306", "test_user", "test_password");
conn->setSchema("test_db");
// 调用加密函数
encrypt_file_and_save_record("test.txt", "test_encrypted.bin", conn);
// 关闭连接
delete conn;
return 0;
}
注意事项
实际生产环境中需要注意以下几点:
- 本文示例中的密钥直接以明文形式存入数据库,实际使用时需要对密钥进行二次加密存储,避免密钥泄露
- 加密后的文件将IV放在文件开头,解密时需要先读取前16字节作为IV,再读取后续内容作为密文
- 文件路径建议使用绝对路径,避免程序运行目录变化导致文件找不到的问题
- 操作数据库和文件时需要添加更多的异常处理逻辑,确保程序稳定性