332 lines
10 KiB
C++
332 lines
10 KiB
C++
|
|
#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
|