| | |
| | | #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) { |
| | |
| | | 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; |