| licensedata.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| licensedata.h | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| mainwindow.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| shortcrypto.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| shortcrypto.h | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
licensedata.cpp
@@ -1,5 +1,7 @@ #include "licensedata.h" #include <QDataStream> #include <QCryptographicHash> #include <QRandomGenerator> #include <QtDebug> LicenseData::LicenseData() @@ -105,6 +107,116 @@ .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<quint16>(static_cast<unsigned char>(data.at(i))) << 8; for (int b = 0; b < 8; ++b) { crc = (crc & 0x8000) ? static_cast<quint16>((crc << 1) ^ 0x1021) : static_cast<quint16>(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<char>((days >> 8) & 0xFF); data[9] = static_cast<char>(days & 0xFF); // [10] 产品号 data[10] = static_cast<char>(productId); // [11] 版本:高4位主版本 | 低4位次版本 data[11] = static_cast<char>(((verMajor & 0x0F) << 4) | (verMinor & 0x0F)); // [12] 标志:高4位格式版本(1),低4位激活状态 data[12] = static_cast<char>(0x10 | (activeState & 0x0F)); // [13..14] CRC16(覆盖[0..12],随机盐不参与) quint16 crc = crc16(data.left(13)); data[13] = static_cast<char>((crc >> 8) & 0xFF); data[14] = static_cast<char>(crc & 0xFF); // [15] 随机盐 data[15] = static_cast<char>(QRandomGenerator::global()->generate() & 0xFF); return data; } bool LicenseData::fromBinaryV3(const QByteArray &data) { if (data.size() != 16) { return false; } quint8 flags = static_cast<quint8>(data.at(12)); if ((flags >> 4) != 0x01) { qWarning() << "LicenseData::fromBinaryV3: not a V3 payload, format version =" << (flags >> 4); return false; } quint16 expectCrc = static_cast<quint16>((static_cast<quint8>(data.at(13)) << 8) | static_cast<quint8>(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<quint16>((static_cast<quint8>(data.at(8)) << 8) | static_cast<quint8>(data.at(9))); expireDate = daysToDate(days); productId = static_cast<quint8>(data.at(10)); quint8 ver = static_cast<quint8>(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); licensedata.h
@@ -38,6 +38,31 @@ bool fromBinary(const QByteArray &data); QString toDebugString() const; /** * @brief V3 紧凑格式(28位纯大写许可密钥用,恰好16字节) * * 二进制布局(大端序): * [0..7] 8B 机器指纹 = SHA256(密钥‖UUID(16B)‖CPU(8B)) 截断前8字节 * [8..9] 2B 失效日期(距2000-01-01的天数,uint16,与V2同基准) * [10] 1B 产品号 * [11] 1B 版本号(高4位主版本 | 低4位次版本) * [12] 1B 标志(高4位格式版本=1,低4位激活状态) * [13..14] 2B CRC16-CCITT(覆盖[0..12]) * [15] 1B 随机盐(避免同机同日期密文完全相同) * * 与V2的区别:UUID/CPU不再明文随密钥携带,只带不可逆指纹; * 设备ID仍用V2格式(需要还原机器信息),仅许可密钥用V3。 */ QByteArray toBinaryV3(const QByteArray &key) const; bool fromBinaryV3(const QByteArray &data); QString toDebugStringV3() const; QByteArray machineFp; // V3解出的机器指纹(8字节);V2中为空 // 机器指纹:授权端与验证端必须用同一算法、同一密钥 static QByteArray machineFingerprint(const QByteArray &uuid, const QByteArray &cpu, const QByteArray &key); static quint16 crc16(const QByteArray &data); // 辅助转换 static QByteArray uuidStringToBinary(const QString &uuidStr); static QString uuidBinaryToString(const QByteArray &bin); mainwindow.cpp
@@ -279,7 +279,6 @@ QString encryptlocalid = ui->textEdit_localid->toPlainText(); // 一键解密 lic2.fromBinary(ShortCrypto::decrypt(encryptlocalid, key)); qDebug()<<"on_pushButton_decrypt_clicked"<<lic2.toDebugString(); //与之前的加密对应,且去掉冗余信息 localinfo.Baseboard_Uuid = LicenseData::uuidBinaryToString(lic2.boardUuid); localinfo.CPU_Processorid = LicenseData::binaryToHexString(lic2.cpuSerial); @@ -536,13 +535,18 @@ lic2.expireDate = QDate::fromString(licensedatestr,"yyyy-MM-dd"); // lic2.expireDate = lic2.expireDate.addMonths(time); lic2.activeState = 1; QString cipher = ShortCrypto::encrypt(lic2.toBinary(), key); // V3紧凑格式(28位纯大写):机器信息以指纹形式绑定 QString cipher = ShortCrypto::encryptRaw(lic2.toBinaryV3(key), key); ui->textEdit_licensekey->clear(); ui->textEdit_licensekey->setPlainText(cipher); // 一键解密 // 加密后自检 LicenseData lic3; lic3.fromBinary(ShortCrypto::decrypt(cipher, key)); qDebug()<<"change"<<cipher<<lic3.toDebugString()<<cipher.size(); if (lic3.fromBinaryV3(ShortCrypto::decryptRaw(cipher, key))) { qDebug() << "V3自检通过:" << lic3.toDebugStringV3(); } else { qWarning() << "V3自检失败"; } //更新licenseinfo,只需要更新LicensedDuration_end、LicensedDuration_start、ActiveState licenseinfo.LicensedDuration_end = newlicensedate.toString("yyyy-MM-dd"); licenseinfo.LicensedDuration_start = licensedate.toString("yyyy-MM-dd"); @@ -791,7 +795,7 @@ // ui->textEdit_licensekey->setPlainText(getEncrypt(QString(testid),key)); lic2.productId = ui->lineEdit_setproid->text().toUInt(); QString cipher = ShortCrypto::encrypt(lic2.toBinary(), key); QString cipher = ShortCrypto::encryptRaw(lic2.toBinaryV3(key), key); ui->textEdit_licensekey->setPlainText(cipher); // setLicenseinfo(licensekey_Obj); @@ -817,7 +821,7 @@ QString version = ui->lineEdit_setversion->text(); lic2.verMajor = version.split(".").at(0).toUInt(); lic2.verMinor = version.split(".").at(1).toUInt(); QString cipher = ShortCrypto::encrypt(lic2.toBinary(), key); QString cipher = ShortCrypto::encryptRaw(lic2.toBinaryV3(key), key); ui->textEdit_licensekey->setPlainText(cipher); //這裡也只需要更新Versionid licenseinfo.Versionid = ui->lineEdit_setversion->text(); shortcrypto.cpp
@@ -1,10 +1,16 @@ #include "shortcrypto.h" #include "tinyaes.h" #include <QtDebug> #include <QtMath> #include <algorithm> // Base85 字符集:85个可打印字符,避开容易在URL/JSON里出问题的字符 const char *ShortCrypto::BASE85_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; // V3 字符集:纯大写字母(26字符),许可密钥只含 A-Z,28位 const char *ShortCrypto::V3_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; namespace { QByteArray pkcs7Pad(const QByteArray &data, int blockSize) { @@ -105,6 +111,129 @@ return pkcs7Unpad(decrypted); } QString ShortCrypto::encryptRaw(const QByteArray &plaintext, const QByteArray &key) { if (key.size() != 16) { qWarning() << "ShortCrypto::encryptRaw: key must be 16 bytes for AES-128"; return QString(); } if (plaintext.isEmpty() || plaintext.size() % 16 != 0) { qWarning() << "ShortCrypto::encryptRaw: plaintext size must be a non-zero multiple of 16, got" << plaintext.size(); return QString(); } // 单块时全零IV的CBC等价于ECB;多块时为CBC链接 QByteArray adjustedIv(16, '\0'); QByteArray encrypted = plaintext; struct AES_ctx ctx; AES_init_ctx_iv(&ctx, reinterpret_cast<const uint8_t*>(key.constData()), reinterpret_cast<const uint8_t*>(adjustedIv.constData())); AES_CBC_encrypt_buffer(&ctx, reinterpret_cast<uint8_t*>(encrypted.data()), encrypted.size()); // 纯大写字母编码:16字节密文 → 28字符(若允许数字换36字符集,长度降到25) QByteArray encoded = baseNEncode(encrypted, V3_CHARS, 26); return QString::fromLatin1(encoded); } QByteArray ShortCrypto::decryptRaw(const QString &ciphertext, const QByteArray &key) { if (key.size() != 16) { qWarning() << "ShortCrypto::decryptRaw: key must be 16 bytes for AES-128"; return QByteArray(); } // 优先按纯字母表解码;若含表外字符(早期Base85版V3密钥)则回退Base85 QByteArray encrypted = baseNDecode(ciphertext, V3_CHARS, 26); if (encrypted.isEmpty()) { encrypted = base85Decode(ciphertext); } if (encrypted.isEmpty()) { qWarning() << "ShortCrypto::decryptRaw: decode failed"; return QByteArray(); } if (encrypted.size() % 16 != 0) { qWarning() << "ShortCrypto::decryptRaw: decoded data size not aligned to block size:" << encrypted.size(); return QByteArray(); } QByteArray adjustedIv(16, '\0'); QByteArray decrypted = encrypted; struct AES_ctx ctx; AES_init_ctx_iv(&ctx, reinterpret_cast<const uint8_t*>(key.constData()), reinterpret_cast<const uint8_t*>(adjustedIv.constData())); AES_CBC_decrypt_buffer(&ctx, reinterpret_cast<uint8_t*>(decrypted.data()), decrypted.size()); return decrypted; } QByteArray ShortCrypto::baseNEncode(const QByteArray &data, const char *alphabet, int base) { // 把整段大端字节序当作一个大整数,逐次除以base取余数 QByteArray num = data; QByteArray remainders; bool isZero = false; while (!isZero) { isZero = true; unsigned int carry = 0; for (int i = 0; i < num.size(); ++i) { unsigned int cur = (carry << 8) | static_cast<unsigned char>(num.at(i)); num[i] = static_cast<char>(cur / static_cast<unsigned int>(base)); carry = cur % static_cast<unsigned int>(base); if (num.at(i) != 0) isZero = false; } if (isZero && remainders.isEmpty()) break; // 全零输入 remainders.append(alphabet[static_cast<int>(carry)]); if (isZero) break; } if (remainders.isEmpty()) remainders.append(alphabet[0]); // 还原为高位在前,并用alphabet[0]补齐固定位数(前导零) QByteArray result = remainders; std::reverse(result.begin(), result.end()); int minimalLen = static_cast<int>(qCeil(data.size() * 8.0 * qLn(2.0) / qLn(static_cast<double>(base)))); if (result.size() < minimalLen) { result.prepend(QByteArray(minimalLen - result.size(), alphabet[0])); } return result; } QByteArray ShortCrypto::baseNDecode(const QString &text, const char *alphabet, int base) { // 建立字符索引表 int charToVal[256]; memset(charToVal, -1, sizeof(charToVal)); for (int i = 0; i < base; ++i) { charToVal[static_cast<unsigned char>(alphabet[i])] = i; } // Horner法则:num = num * base + v(大端字节序) QByteArray num(16, '\0'); QByteArray input = text.toLatin1(); for (int i = 0; i < input.size(); ++i) { int v = charToVal[static_cast<unsigned char>(input.at(i))]; if (v < 0) { qWarning() << "ShortCrypto::baseNDecode: invalid character:" << QChar(input.at(i)); return QByteArray(); } unsigned int carry = static_cast<unsigned int>(v); for (int b = num.size() - 1; b >= 0; --b) { unsigned int cur = (static_cast<unsigned int>(static_cast<unsigned char>(num.at(b))) * static_cast<unsigned int>(base)) + carry; num[b] = static_cast<char>(cur & 0xFF); carry = cur >> 8; } if (carry != 0) { qWarning() << "ShortCrypto::baseNDecode: value overflows 128 bits"; return QByteArray(); } } // 保持16字节定宽(前导零有意义),调用方按固定块长处理 return num; } QByteArray ShortCrypto::base85Encode(const QByteArray &data) { QByteArray result; shortcrypto.h
@@ -29,12 +29,37 @@ */ static QByteArray decrypt(const QString &ciphertext, const QByteArray &key, const QByteArray &iv = QByteArray()); /** * @brief 无填充加密(V3专用) * @param plaintext 原始明文,长度必须是16的倍数(16字节明文 → 16字节密文) * @param key 16字节密钥(AES-128) * @return 纯大写字母编码的密文字符串(A-Z,26字符集),16字节明文恰好输出28字符 * @note 不做PKCS7填充:16字节明文若走填充会扩成32字节 * @note 若想缩短到25字符,把V3_CHARS换成"A-Z0-9"并把base改为36即可 */ static QString encryptRaw(const QByteArray &plaintext, const QByteArray &key); /** * @brief 无填充解密(V3专用) * @param ciphertext 纯大写字母编码的密文(兼容早期Base85/纯字母版V3密文,自动回退) * @param key 16字节密钥(AES-128) * @return 原始明文(长度与密文一致),完整性由上层CRC校验保证 */ static QByteArray decryptRaw(const QString &ciphertext, const QByteArray &key); private: // Base85 字符集(RFC 1924 变体,不含引号/逗号/句点/斜杠,避免JSON/XML转义问题) static const char *BASE85_CHARS; // V3 密文字符集:纯大写字母,不含数字/小写/特殊字符(26字符集,16字节→28字符) static const char *V3_CHARS; static QByteArray base85Encode(const QByteArray &data); static QByteArray base85Decode(const QString &text); // 任意进制的大数编解码(整段字节序作为大整数,前导零以alphabet[0]补齐固定位数) static QByteArray baseNEncode(const QByteArray &data, const char *alphabet, int base); static QByteArray baseNDecode(const QString &text, const char *alphabet, int base); }; #endif // SHORTCRYPTO_H