RB_ningyang/licensemanager.cpp
2026-01-22 19:08:28 +08:00

507 lines
15 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 "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();
}