ccy
6 天以前 5e1e4a4b57e298806dba97175fa1fbd5c771c7cd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#include "licensedata.h"
#include <QDataStream>
#include <QCryptographicHash>
#include <QRandomGenerator>
#include <QtDebug>
 
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<quint8>(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<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);
    if (!date.isValid() || date < base) {
        return 0;
    }
    return static_cast<quint16>(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<char>(clean.mid(i, 2).toUInt(&ok, 16)));
    }
    return result;
}
 
QString LicenseData::binaryToHexString(const QByteArray &bin)
{
    return QString::fromLatin1(bin.toHex()).toUpper();
}