RB_ningyang/licensemanager.cpp

507 lines
15 KiB
C++
Raw Normal View History

2026-01-22 11:08:28 +00:00
#include "licensemanager.h"
#include <QProcess>
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QCryptographicHash>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QDir>
#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QTcpSocket>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
// ==================== 静态成员初始化 ====================
// 加密密钥
const QByteArray LicenseManager::encryptKey = "GetonAgain2026SecretKey!@#$%";
// 客户专用密码 - 根据不同客户修改此值
const QString LicenseManager::customerPassword = "nuobo123";
// ==================== LicenseManager 实现 ====================
LicenseManager::LicenseManager(QObject *parent) : QObject(parent)
{
}
QString LicenseManager::getMachineUUID()
{
QString uuid;
#ifdef Q_OS_WIN
// Windows: 通过WMI获取主板UUID
QProcess process;
process.start("wmic", QStringList() << "csproduct" << "get" << "UUID");
process.waitForFinished(5000);
QString output = QString::fromLocal8Bit(process.readAllStandardOutput());
// 解析输出获取UUID
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
for (const QString &line : lines)
{
QString trimmed = line.trimmed();
if (!trimmed.isEmpty() && trimmed != "UUID" && trimmed.contains('-'))
{
uuid = trimmed;
break;
}
}
#else
// Linux/Mac: 读取machine-id
QFile file("/etc/machine-id");
if (file.open(QIODevice::ReadOnly))
{
uuid = QString::fromUtf8(file.readAll()).trimmed();
file.close();
}
#endif
if (uuid.isEmpty())
{
// 备用方案:使用机器名+用户名的哈希
QString fallback = QSysInfo::machineHostName() + QDir::homePath();
uuid = QString::fromLatin1(QCryptographicHash::hash(fallback.toUtf8(), QCryptographicHash::Md5).toHex());
}
return uuid;
}
QString LicenseManager::getLicenseFilePath()
{
// 激活文件放在程序目录下
return QCoreApplication::applicationDirPath() + "/license.gal";
}
QString LicenseManager::encrypt(const QString &plainText)
{
QByteArray data = plainText.toUtf8();
// 第一步计算明文的SHA256哈希用于完整性校验
QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256);
// 第二步:构造加密数据 = 哈希前8字节 + 原始数据
QByteArray toEncrypt;
toEncrypt.append(hash.left(8)); // 8字节校验头
toEncrypt.append(data);
// 第三步多层XOR加密
QByteArray encrypted;
for (int i = 0; i < toEncrypt.size(); ++i)
{
unsigned char c = static_cast<unsigned char>(toEncrypt[i]);
// 第一层与主密钥XOR
c = c ^ static_cast<unsigned char>(encryptKey[i % encryptKey.size()]);
// 第二层:字节置换(非线性变换)
c = static_cast<unsigned char>((c * 7 + 13) % 256);
// 第三层:与位置相关的混淆
c = c ^ static_cast<unsigned char>((i * 31 + 17) % 256);
encrypted.append(static_cast<char>(c));
}
// 第四步Base64编码
return QString::fromLatin1(encrypted.toBase64());
}
QString LicenseManager::decrypt(const QString &encryptedText)
{
// Base64解码
QByteArray encrypted = QByteArray::fromBase64(encryptedText.toLatin1());
if (encrypted.size() < 9) // 至少8字节校验头 + 1字节数据
{
return QString();
}
// 逆向解密
QByteArray decrypted;
for (int i = 0; i < encrypted.size(); ++i)
{
unsigned char c = static_cast<unsigned char>(encrypted[i]);
// 逆向第三层:位置混淆
c = c ^ static_cast<unsigned char>((i * 31 + 17) % 256);
// 逆向第二层:字节置换的逆运算
// 原式: c' = (c * 7 + 13) % 256
// 需要找到 7 在 mod 256 下的逆元
// 7 * 183 = 1281 = 5 * 256 + 1, 所以 7^(-1) ≡ 183 (mod 256)
c = static_cast<unsigned char>(((c + 256 - 13) * 183) % 256);
// 逆向第一层与主密钥XOR
c = c ^ static_cast<unsigned char>(encryptKey[i % encryptKey.size()]);
decrypted.append(static_cast<char>(c));
}
// 提取校验头和原始数据
QByteArray storedHash = decrypted.left(8);
QByteArray originalData = decrypted.mid(8);
// 验证哈希
QByteArray computedHash = QCryptographicHash::hash(originalData, QCryptographicHash::Sha256);
if (computedHash.left(8) != storedHash)
{
qDebug() << "License integrity check failed - data may be corrupted or tampered";
return QString(); // 返回空表示解密失败
}
return QString::fromUtf8(originalData);
}
QString LicenseManager::generateActivationCode(const QString &uuid)
{
// 生成激活码格式: encrypt(UUID:password)
// 这个方法供管理员/供应商使用,为特定设备生成激活码
QString plainText = uuid + ":" + customerPassword;
return encrypt(plainText);
}
bool LicenseManager::readAndVerifyLicense()
{
QString filePath = getLicenseFilePath();
QFile file(filePath);
if (!file.exists())
{
qDebug() << "License file not found:" << filePath;
return false;
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
qDebug() << "Cannot open license file";
return false;
}
QTextStream in(&file);
QString encryptedContent = in.readAll().trimmed();
file.close();
if (encryptedContent.isEmpty())
{
qDebug() << "License file is empty";
return false;
}
// 解密
QString decrypted = decrypt(encryptedContent);
qDebug() << "Decrypted license content (debug)";
// 解析格式: UUID:password
// 使用 lastIndexOf 因为UUID中也可能包含冒号虽然通常不会
int separatorIndex = decrypted.lastIndexOf(':');
if (separatorIndex == -1)
{
qDebug() << "Invalid license format: no separator";
return false;
}
QString licenseUUID = decrypted.left(separatorIndex);
QString password = decrypted.mid(separatorIndex + 1);
// 获取本机真实UUID
QString currentUUID = getMachineUUID();
qDebug() << "License UUID:" << licenseUUID;
qDebug() << "Current UUID:" << currentUUID;
// 验证UUID是否匹配
if (licenseUUID != currentUUID)
{
qDebug() << "UUID mismatch - license not valid for this machine";
return false;
}
// 验证密码是否正确
if (password != customerPassword)
{
qDebug() << "Password mismatch";
return false;
}
qDebug() << "License verification successful";
return true;
}
bool LicenseManager::saveLicenseFile(const QString &activationCode)
{
QString filePath = getLicenseFilePath();
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
qDebug() << "Cannot save license file to:" << filePath;
return false;
}
QTextStream out(&file);
out << activationCode;
file.close();
qDebug() << "License file saved to:" << filePath;
return true;
}
bool LicenseManager::showActivationDialog(QWidget *parent)
{
ActivationDialog dialog(parent);
while (true)
{
if (dialog.exec() != QDialog::Accepted)
{
return false; // 用户取消
}
QString serverAddress = dialog.getServerAddress();
QString activationCode = dialog.getActivationCode();
// 解析服务器地址和端口
QString host;
int port = 8080;
if (serverAddress.contains(':'))
{
QStringList parts = serverAddress.split(':');
host = parts[0];
port = parts[1].toInt();
}
else
{
host = serverAddress;
}
// 显示连接中提示
QMessageBox waitMsg(parent);
waitMsg.setWindowTitle("请稍候");
waitMsg.setText("正在连接激活服务器...");
waitMsg.setStandardButtons(QMessageBox::NoButton);
waitMsg.show();
QApplication::processEvents();
// 连接服务器
QTcpSocket socket;
socket.connectToHost(host, port);
bool connected = socket.waitForConnected(10000); // 10秒超时
if (!connected)
{
waitMsg.close();
QMessageBox::warning(parent, "连接失败",
QString("无法连接到服务器 %1:%2\n请检查网络连接和服务器地址。").arg(host).arg(port));
continue;
}
// 构建激活请求
QJsonObject requestObj;
requestObj["uuid"] = getMachineUUID();
requestObj["code"] = activationCode;
requestObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
QJsonDocument doc(requestObj);
QByteArray requestData = doc.toJson(QJsonDocument::Compact);
// 发送请求
socket.write(requestData);
socket.write("\n");
socket.flush();
// 等待响应
if (!socket.waitForReadyRead(15000)) // 15秒超时
{
waitMsg.close();
socket.close();
QMessageBox::warning(parent, "激活失败", "服务器响应超时,请重试。");
continue;
}
QByteArray response = socket.readAll();
socket.close();
waitMsg.close();
// 解析响应
QJsonDocument responseDoc = QJsonDocument::fromJson(response);
if (responseDoc.isNull() || !responseDoc.isObject())
{
QMessageBox::warning(parent, "激活失败", "服务器响应格式错误。");
continue;
}
QJsonObject responseObj = responseDoc.object();
QString status = responseObj["status"].toString();
if (status == "success")
{
QString license = responseObj["license"].toString();
// 保存激活文件
if (saveLicenseFile(license))
{
// 验证保存的激活文件
if (readAndVerifyLicense())
{
QMessageBox::information(parent, "成功", "软件激活成功!");
return true;
}
}
QMessageBox::warning(parent, "错误", "保存激活文件失败!");
}
else
{
QString message = responseObj["message"].toString();
if (message.isEmpty())
message = "激活失败,请检查激活码是否正确。";
QMessageBox::StandardButton retry = QMessageBox::warning(
parent, "激活失败", message,
QMessageBox::Retry | QMessageBox::Cancel);
if (retry != QMessageBox::Retry)
{
return false;
}
}
}
}
// ==================== ActivationDialog 实现 ====================
ActivationDialog::ActivationDialog(QWidget *parent) : QDialog(parent)
{
setWindowTitle("软件激活");
setFixedSize(420, 240);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
// 确保对话框接收键盘输入
setAttribute(Qt::WA_InputMethodEnabled, true);
setupUI();
}
QString ActivationDialog::getServerAddress() const
{
return serverEdit->text().trimmed();
}
QString ActivationDialog::getActivationCode() const
{
return codeEdit->text().trimmed();
}
void ActivationDialog::setupUI()
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(25, 20, 25, 20);
mainLayout->setSpacing(12);
// 标题
QLabel *titleLabel = new QLabel("软件激活");
titleLabel->setStyleSheet("font-size: 20px; font-weight: bold; color: #333;");
titleLabel->setAlignment(Qt::AlignCenter);
mainLayout->addWidget(titleLabel);
mainLayout->addSpacing(8);
// 提示信息
QLabel *infoLabel = new QLabel("请输入激活服务器地址和激活码");
infoLabel->setStyleSheet("font-size: 12px; color: #666;");
infoLabel->setAlignment(Qt::AlignCenter);
mainLayout->addWidget(infoLabel);
mainLayout->addSpacing(8);
// 服务器地址输入 - 使用 QFormLayout 风格
QHBoxLayout *serverLayout = new QHBoxLayout();
serverLayout->setSpacing(10);
QLabel *serverLabel = new QLabel("服务器地址:");
serverLabel->setFixedWidth(80);
serverLabel->setStyleSheet("font-size: 13px;");
serverEdit = new QLineEdit();
serverEdit->setMinimumHeight(32);
serverEdit->setPlaceholderText("例如: 192.168.1.100:8080");
serverEdit->setStyleSheet("font-size: 13px; padding: 4px 8px;");
serverLayout->addWidget(serverLabel);
serverLayout->addWidget(serverEdit, 1);
mainLayout->addLayout(serverLayout);
// 激活码输入
QHBoxLayout *codeLayout = new QHBoxLayout();
codeLayout->setSpacing(10);
QLabel *codeLabel = new QLabel("激 活 码:");
codeLabel->setFixedWidth(80);
codeLabel->setStyleSheet("font-size: 13px;");
codeEdit = new QLineEdit();
codeEdit->setMinimumHeight(32);
codeEdit->setPlaceholderText("例如: RB2026-XXXX-XXXX-XXXX");
codeEdit->setStyleSheet("font-size: 13px; padding: 4px 8px;");
codeLayout->addWidget(codeLabel);
codeLayout->addWidget(codeEdit, 1);
mainLayout->addLayout(codeLayout);
mainLayout->addSpacing(15);
// 按钮
QHBoxLayout *btnLayout = new QHBoxLayout();
btnLayout->addStretch();
confirmBtn = new QPushButton("激 活");
confirmBtn->setFixedSize(100, 36);
confirmBtn->setStyleSheet(
"QPushButton { background-color: #4CAF50; color: white; font-size: 14px; font-weight: bold; border-radius: 4px; }"
"QPushButton:hover { background-color: #45a049; }"
"QPushButton:pressed { background-color: #3d8b40; }");
connect(confirmBtn, &QPushButton::clicked, this, &ActivationDialog::onConfirmClicked);
cancelBtn = new QPushButton("退 出");
cancelBtn->setFixedSize(100, 36);
cancelBtn->setStyleSheet(
"QPushButton { background-color: #f44336; color: white; font-size: 14px; font-weight: bold; border-radius: 4px; }"
"QPushButton:hover { background-color: #da3c30; }"
"QPushButton:pressed { background-color: #c62828; }");
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
btnLayout->addWidget(confirmBtn);
btnLayout->addSpacing(20);
btnLayout->addWidget(cancelBtn);
btnLayout->addStretch();
mainLayout->addLayout(btnLayout);
// 确保可以Tab切换
setTabOrder(serverEdit, codeEdit);
setTabOrder(codeEdit, confirmBtn);
setTabOrder(confirmBtn, cancelBtn);
}
void ActivationDialog::onConfirmClicked()
{
if (serverEdit->text().trimmed().isEmpty())
{
QMessageBox::warning(this, "提示", "请输入服务器地址!");
serverEdit->setFocus();
return;
}
if (codeEdit->text().trimmed().isEmpty())
{
QMessageBox::warning(this, "提示", "请输入激活码!");
codeEdit->setFocus();
return;
}
accept();
}