GrabBag/AppUtils/AppCommon/Src/ConfigEncryption.cpp

332 lines
10 KiB
C++
Raw Normal View History

#include "ConfigEncryption.h"
#include <QCryptographicHash>
#include <QFile>
#include <QRandomGenerator>
#include <QDateTime>
#include "VrLog.h"
#ifdef _WIN32
#include <windows.h>
#include <aclapi.h>
#include <sddl.h>
// 取消 Windows API 宏定义,避免与我们的函数名冲突
#ifdef EncryptFile
#undef EncryptFile
#endif
#ifdef DecryptFile
#undef DecryptFile
#endif
#else
// Linux/Unix 平台头文件
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#endif
const char* ConfigEncryption::MAGIC_HEADER = "VRENC1.0";
QByteArray ConfigEncryption::GenerateKey(const QString& password, const QByteArray& salt)
{
// 使用 PBKDF2 简化版本:多轮 SHA-256 哈希
QByteArray key = password.toUtf8() + salt;
// 进行多轮哈希增强安全性
for (int i = 0; i < 10000; i++) {
key = QCryptographicHash::hash(key, QCryptographicHash::Sha256);
}
return key;
}
QByteArray ConfigEncryption::EncryptConfig(const QByteArray& plainData, const QString& password)
{
if (plainData.isEmpty() || password.isEmpty()) {
LOG_ERROR("加密失败:数据或密码为空\n");
return QByteArray();
}
try {
// 生成随机盐值
QByteArray salt;
salt.resize(SALT_SIZE);
for (int i = 0; i < SALT_SIZE; i++) {
salt[i] = static_cast<char>(QRandomGenerator::global()->bounded(256));
}
// 生成加密密钥
QByteArray key = GenerateKey(password, salt);
// 加密数据(使用流密码方式:异或操作)
QByteArray encryptedData;
encryptedData.reserve(plainData.size());
for (int i = 0; i < plainData.size(); i++) {
// 使用密钥循环异或
encryptedData.append(plainData[i] ^ key[i % key.size()]);
}
// 构建最终格式:魔术头 + 盐值 + 加密数据
QByteArray result;
result.append(MAGIC_HEADER, 8); // 8字节魔术头
result.append(salt); // 16字节盐值
result.append(encryptedData); // 加密数据
// 添加校验和最后4字节
QByteArray checksum = QCryptographicHash::hash(result, QCryptographicHash::Md5);
result.append(checksum.left(4));
LOG_INFO("配置数据加密成功,原始大小: %d, 加密后大小: %d\n",
plainData.size(), result.size());
return result;
}
catch (const std::exception& e) {
LOG_ERROR("加密过程发生异常: %s\n", e.what());
return QByteArray();
}
}
QByteArray ConfigEncryption::DecryptConfig(const QByteArray& encryptedData, const QString& password)
{
if (encryptedData.isEmpty() || password.isEmpty()) {
LOG_ERROR("解密失败:数据或密码为空\n");
return QByteArray();
}
try {
// 检查最小长度:魔术头(8) + 盐值(16) + 校验和(4) = 28字节
if (encryptedData.size() < 28) {
LOG_ERROR("解密失败:数据长度不足\n");
return QByteArray();
}
// 验证魔术头
QByteArray header = encryptedData.left(8);
if (header != QByteArray(MAGIC_HEADER, 8)) {
LOG_ERROR("解密失败:文件格式错误(魔术头不匹配)\n");
return QByteArray();
}
// 验证校验和
QByteArray dataWithoutChecksum = encryptedData.left(encryptedData.size() - 4);
QByteArray storedChecksum = encryptedData.right(4);
QByteArray calculatedChecksum = QCryptographicHash::hash(dataWithoutChecksum,
QCryptographicHash::Md5).left(4);
if (storedChecksum != calculatedChecksum) {
LOG_ERROR("解密失败:数据完整性校验失败\n");
return QByteArray();
}
// 提取盐值
QByteArray salt = encryptedData.mid(8, SALT_SIZE);
// 提取加密数据
QByteArray encData = encryptedData.mid(8 + SALT_SIZE,
encryptedData.size() - 8 - SALT_SIZE - 4);
// 生成解密密钥
QByteArray key = GenerateKey(password, salt);
// 解密数据(使用相同的异或操作)
QByteArray plainData;
plainData.reserve(encData.size());
for (int i = 0; i < encData.size(); i++) {
plainData.append(encData[i] ^ key[i % key.size()]);
}
LOG_INFO("配置数据解密成功,解密后大小: %d\n", plainData.size());
return plainData;
}
catch (const std::exception& e) {
LOG_ERROR("解密过程发生异常: %s\n", e.what());
return QByteArray();
}
}
bool ConfigEncryption::EncryptFile(const QString& filePath, const QString& password)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
LOG_ERROR("无法打开文件进行加密: %s\n", filePath.toStdString().c_str());
return false;
}
QByteArray plainData = file.readAll();
file.close();
if (plainData.isEmpty()) {
LOG_ERROR("文件为空,无法加密: %s\n", filePath.toStdString().c_str());
return false;
}
// 加密数据
QByteArray encryptedData = ConfigEncryption::EncryptConfig(plainData, password);
if (encryptedData.isEmpty()) {
LOG_ERROR("加密数据失败: %s\n", filePath.toStdString().c_str());
return false;
}
// 写入加密数据
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LOG_ERROR("无法打开文件进行写入: %s\n", filePath.toStdString().c_str());
return false;
}
qint64 written = file.write(encryptedData);
file.close();
if (written != encryptedData.size()) {
LOG_ERROR("写入加密数据失败: %s\n", filePath.toStdString().c_str());
return false;
}
LOG_INFO("文件加密成功: %s\n", filePath.toStdString().c_str());
return true;
}
bool ConfigEncryption::DecryptFile(const QString& filePath, const QString& password)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
LOG_ERROR("无法打开文件进行解密: %s\n", filePath.toStdString().c_str());
return false;
}
QByteArray encryptedData = file.readAll();
file.close();
if (encryptedData.isEmpty()) {
LOG_ERROR("文件为空,无法解密: %s\n", filePath.toStdString().c_str());
return false;
}
// 解密数据
QByteArray plainData = ConfigEncryption::DecryptConfig(encryptedData, password);
if (plainData.isEmpty()) {
LOG_ERROR("解密数据失败: %s\n", filePath.toStdString().c_str());
return false;
}
// 写入解密数据
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LOG_ERROR("无法打开文件进行写入: %s\n", filePath.toStdString().c_str());
return false;
}
qint64 written = file.write(plainData);
file.close();
if (written != plainData.size()) {
LOG_ERROR("写入解密数据失败: %s\n", filePath.toStdString().c_str());
return false;
}
LOG_INFO("文件解密成功: %s\n", filePath.toStdString().c_str());
return true;
}
bool ConfigEncryption::VerifyPassword(const QByteArray& encryptedData, const QString& password)
{
// 尝试解密,如果成功则密码正确
QByteArray decrypted = DecryptConfig(encryptedData, password);
return !decrypted.isEmpty();
}
#ifdef _WIN32
bool ConfigEncryption::SetDirectoryPermissions(const QString& directoryPath)
{
LOG_INFO("设置目录权限: %s\n", directoryPath.toStdString().c_str());
// 转换为宽字符
std::wstring wPath = directoryPath.toStdWString();
// 构建安全描述符字符串SDDL
// D: - DACL
// (D;;GA;;;WD) - 拒绝所有用户的通用访问
// (A;;GA;;;BA) - 允许管理员组的通用访问
// (A;;GA;;;SY) - 允许系统的通用访问
LPCWSTR sddl = L"D:(D;;GA;;;WD)(A;;GA;;;BA)(A;;GA;;;SY)";
PSECURITY_DESCRIPTOR pSD = NULL;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl,
SDDL_REVISION_1,
&pSD,
NULL)) {
DWORD error = GetLastError();
LOG_ERROR("创建安全描述符失败,错误代码: %lu\n", error);
return false;
}
// 设置目录的安全描述符
DWORD result = SetNamedSecurityInfoW(
const_cast<LPWSTR>(wPath.c_str()),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
NULL,
NULL,
NULL,
NULL);
if (result != ERROR_SUCCESS) {
LOG_ERROR("设置目录权限失败,错误代码: %lu\n", result);
LocalFree(pSD);
return false;
}
// 应用 DACL
PACL pDacl = NULL;
BOOL bDaclPresent = FALSE;
BOOL bDaclDefaulted = FALSE;
if (!GetSecurityDescriptorDacl(pSD, &bDaclPresent, &pDacl, &bDaclDefaulted)) {
LOG_ERROR("获取 DACL 失败\n");
LocalFree(pSD);
return false;
}
if (bDaclPresent) {
result = SetNamedSecurityInfoW(
const_cast<LPWSTR>(wPath.c_str()),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
NULL,
NULL,
pDacl,
NULL);
if (result != ERROR_SUCCESS) {
LOG_ERROR("应用 DACL 失败,错误代码: %lu\n", result);
LocalFree(pSD);
return false;
}
}
LocalFree(pSD);
LOG_INFO("目录权限设置成功\n");
return true;
}
#else
bool ConfigEncryption::SetDirectoryPermissions(const QString& directoryPath)
{
LOG_INFO("设置目录权限Linux: %s\n", directoryPath.toStdString().c_str());
// 转换路径为标准 C 字符串
std::string path = directoryPath.toStdString();
// 设置目录权限为 700 (仅所有者可读写执行)
// S_IRUSR | S_IWUSR | S_IXUSR = 0700
if (chmod(path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) {
LOG_ERROR("设置目录权限失败: %s (errno: %d)\n",
directoryPath.toStdString().c_str(), errno);
return false;
}
LOG_INFO("目录权限设置成功(权限: 700\n");
return true;
}
#endif