#include "licensedata.h" #include #include #include #include LicenseData::LicenseData() : productId(0), verMajor(0), verMinor(0), activeState(0) { } QByteArray LicenseData::toBinary() const { QByteArray data; data.reserve(64); QDataStream stream(&data, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::BigEndian); // 1. 主板UUID:固定16字节 QByteArray uuid = boardUuid.left(16); uuid.append(QByteArray(16 - uuid.size(), '\0')); stream.writeRawData(uuid.constData(), 16); // 2. CPU序列号:固定8字节 QByteArray cpu = cpuSerial.left(8); cpu.append(QByteArray(8 - cpu.size(), '\0')); stream.writeRawData(cpu.constData(), 8); // 3. 用户名:长度前缀(1B) + UTF-8数据 QByteArray userBytes = userName.toUtf8(); if (userBytes.size() > 255) { userBytes = userBytes.left(255); } stream << static_cast(userBytes.size()); stream.writeRawData(userBytes.constData(), userBytes.size()); // 4. 失效日期:2字节(距2000-01-01的天数) stream << dateToDays(expireDate); // 5. 产品号:1字节 stream << productId; // 6. 版本号:主版本 + 次版本,各1字节 stream << verMajor << verMinor; // 7. 激活状态:1字节 stream << activeState; return data; } bool LicenseData::fromBinary(const QByteArray &data) { if (data.size() < 30) { qWarning() << "LicenseData::fromBinary: data too short, need at least 30 bytes"; return false; } QDataStream stream(data); stream.setByteOrder(QDataStream::BigEndian); // 1. UUID boardUuid.resize(16); stream.readRawData(boardUuid.data(), 16); // 2. CPU序列号 cpuSerial.resize(8); stream.readRawData(cpuSerial.data(), 8); // 3. 用户名 quint8 nameLen = 0; stream >> nameLen; if (data.size() < 25 + nameLen + 5) { qWarning() << "LicenseData::fromBinary: insufficient data for remaining fields"; return false; } QByteArray userBytes(nameLen, '\0'); stream.readRawData(userBytes.data(), nameLen); userName = QString::fromUtf8(userBytes); // 4. 日期 quint16 days = 0; stream >> days; expireDate = daysToDate(days); // 5. 产品号 stream >> productId; // 6. 版本 stream >> verMajor >> verMinor; // 7. 激活状态 stream >> activeState; return true; } QString LicenseData::toDebugString() const { return QString("{UUID:%1, CPU:%2, User:%3, Expire:%4, Prod:%5, Ver:%6.%7, State:%8}") .arg(uuidBinaryToString(boardUuid)) .arg(binaryToHexString(cpuSerial)) .arg(userName) .arg(expireDate.toString("yyyy-MM-dd")) .arg(productId) .arg(verMajor).arg(verMinor) .arg(activeState); } // ===== V3 紧凑格式(20位许可密钥用)===== QByteArray LicenseData::machineFingerprint(const QByteArray &uuid, const QByteArray &cpu, const QByteArray &key) { // 与toBinary相同的定长规整,保证授权端和验证端对同一台机器算出同一指纹 QByteArray u = uuid.left(16); u.append(QByteArray(16 - u.size(), '\0')); QByteArray c = cpu.left(8); c.append(QByteArray(8 - c.size(), '\0')); QCryptographicHash hash(QCryptographicHash::Sha256); hash.addData(key); hash.addData(u); hash.addData(c); return hash.result().left(8); } quint16 LicenseData::crc16(const QByteArray &data) { // CRC16-CCITT (0xFFFF 初值,0x1021 多项式) quint16 crc = 0xFFFF; for (int i = 0; i < data.size(); ++i) { crc ^= static_cast(static_cast(data.at(i))) << 8; for (int b = 0; b < 8; ++b) { crc = (crc & 0x8000) ? static_cast((crc << 1) ^ 0x1021) : static_cast(crc << 1); } } return crc; } QByteArray LicenseData::toBinaryV3(const QByteArray &key) const { QByteArray data(16, '\0'); // [0..7] 机器指纹 QByteArray fp = machineFingerprint(boardUuid, cpuSerial, key); memcpy(data.data(), fp.constData(), 8); // [8..9] 失效日期(与V2同一2000-01-01基准) quint16 days = dateToDays(expireDate); data[8] = static_cast((days >> 8) & 0xFF); data[9] = static_cast(days & 0xFF); // [10] 产品号 data[10] = static_cast(productId); // [11] 版本:高4位主版本 | 低4位次版本 data[11] = static_cast(((verMajor & 0x0F) << 4) | (verMinor & 0x0F)); // [12] 标志:高4位格式版本(1),低4位激活状态 data[12] = static_cast(0x10 | (activeState & 0x0F)); // [13..14] CRC16(覆盖[0..12],随机盐不参与) quint16 crc = crc16(data.left(13)); data[13] = static_cast((crc >> 8) & 0xFF); data[14] = static_cast(crc & 0xFF); // [15] 随机盐 data[15] = static_cast(QRandomGenerator::global()->generate() & 0xFF); return data; } bool LicenseData::fromBinaryV3(const QByteArray &data) { if (data.size() != 16) { return false; } quint8 flags = static_cast(data.at(12)); if ((flags >> 4) != 0x01) { qWarning() << "LicenseData::fromBinaryV3: not a V3 payload, format version =" << (flags >> 4); return false; } quint16 expectCrc = static_cast((static_cast(data.at(13)) << 8) | static_cast(data.at(14))); if (crc16(data.left(13)) != expectCrc) { qWarning() << "LicenseData::fromBinaryV3: CRC check failed, wrong key or corrupted data"; return false; } machineFp = data.left(8); quint16 days = static_cast((static_cast(data.at(8)) << 8) | static_cast(data.at(9))); expireDate = daysToDate(days); productId = static_cast(data.at(10)); quint8 ver = static_cast(data.at(11)); verMajor = ver >> 4; verMinor = ver & 0x0F; activeState = flags & 0x0F; return true; } QString LicenseData::toDebugStringV3() const { return QString("{FP:%1, Expire:%2, Prod:%3, Ver:%4.%5, State:%6}") .arg(binaryToHexString(machineFp)) .arg(expireDate.toString("yyyy-MM-dd")) .arg(productId) .arg(verMajor).arg(verMinor) .arg(activeState); } quint16 LicenseData::dateToDays(const QDate &date) const { QDate base(BASE_YEAR, BASE_MONTH, BASE_DAY); if (!date.isValid() || date < base) { return 0; } return static_cast(base.daysTo(date)); } QDate LicenseData::daysToDate(quint16 days) const { QDate base(BASE_YEAR, BASE_MONTH, BASE_DAY); return base.addDays(days); } QByteArray LicenseData::uuidStringToBinary(const QString &uuidStr) { QString clean = uuidStr; clean.remove('-'); clean.remove('{'); clean.remove('}'); clean = clean.toUpper(); if (clean.size() != 32) { return QByteArray(); } return hexStringToBinary(clean); } QString LicenseData::uuidBinaryToString(const QByteArray &bin) { if (bin.size() != 16) { return QString(); } QString hex = binaryToHexString(bin); return QString("%1-%2-%3-%4-%5") .arg(hex.mid(0, 8)) .arg(hex.mid(8, 4)) .arg(hex.mid(12, 4)) .arg(hex.mid(16, 4)) .arg(hex.mid(20, 12)); } QByteArray LicenseData::hexStringToBinary(const QString &hexStr) { QString clean = hexStr; clean.remove(' '); QByteArray result; result.reserve(clean.size() / 2); for (int i = 0; i + 1 < clean.size(); i += 2) { bool ok; result.append(static_cast(clean.mid(i, 2).toUInt(&ok, 16))); } return result; } QString LicenseData::binaryToHexString(const QByteArray &bin) { return QString::fromLatin1(bin.toHex()).toUpper(); }