GrabBag/AppUtils/AppCommon/Src/ConfigEncryption.cpp

332 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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