From 52eb850a7e054dff4ef0c8ec835f03b8c32c2b00 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 4 Apr 2025 20:39:36 -0400 Subject: [PATCH] Update encryption logic. --- libs/encryption/ecrypt-config.h | 2 +- libs/encryption/encryption.cpp | 439 +++++++++++++++++++++++++++++++ libs/encryption/encryption.h | 440 +------------------------------- libs/encryption/encryption.pro | 2 +- libs/encryption/sha1.h | 3 +- 5 files changed, 456 insertions(+), 430 deletions(-) diff --git a/libs/encryption/ecrypt-config.h b/libs/encryption/ecrypt-config.h index 096358a..f388224 100644 --- a/libs/encryption/ecrypt-config.h +++ b/libs/encryption/ecrypt-config.h @@ -257,7 +257,7 @@ #ifdef _UI64_MAX -#if (_UI64_MAX / 0xFFFFFFFFui64 > 0xFFFFFFFFui64) +#if (_UI64_MAX / 0xFFFFFFFFuLL > 0xFFFFFFFFuLL) #ifndef I64T #define I64T __int64 #define U64C(v) (v##ui64) diff --git a/libs/encryption/encryption.cpp b/libs/encryption/encryption.cpp index 49dfdd9..613ea2e 100644 --- a/libs/encryption/encryption.cpp +++ b/libs/encryption/encryption.cpp @@ -1 +1,440 @@ +#include "encryption.h" +#include "QtZlib/zlib.h" #include "ecrypt-sync.h" + +void Encryption::Convert32BitTo8Bit(quint32 value, quint8 *array) { + array[0] = static_cast(value >> 0); + array[1] = static_cast(value >> 8); + array[2] = static_cast(value >> 16); + array[3] = static_cast(value >> 24); +} + +quint32 Encryption::ConvertArrayTo32Bit(const QByteArray &array) { + return ((static_cast(static_cast(array[0])) << 0) | + (static_cast(static_cast(array[1])) << 8) | + (static_cast(static_cast(array[2])) << 16) | + (static_cast(static_cast(array[3])) << 24)); +} + +quint32 Encryption::Rotate(quint32 value, quint32 numBits) { + return (value << numBits) | (value >> (32 - numBits)); +} + +QByteArray Encryption::InitIVTable(const QByteArray &feed) { + const int tableSize = 0xFB0; + QByteArray table; + table.resize(tableSize); + int ptr = 0; + for (int i = 0; i < 200; ++i) { + for (int x = 0; x < 5; ++x) { + if (static_cast(feed.at(ptr)) == 0x00) + ptr = 0; + int base = i * 20 + x * 4; + table[base] = feed.at(ptr); + table[base + 1] = feed.at(ptr); + table[base + 2] = feed.at(ptr); + table[base + 3] = feed.at(ptr); + ++ptr; + } + } + // Copy block numbers [1,0,0,0] into the last 16 bytes + QByteArray oneBlock; + oneBlock.append(char(1)); oneBlock.append(char(0)); oneBlock.append(char(0)); oneBlock.append(char(0)); + table.replace(0xFA0, 4, oneBlock); + table.replace(0xFA4, 4, oneBlock); + table.replace(0xFA8, 4, oneBlock); + table.replace(0xFAC, 4, oneBlock); + return table; +} + +int Encryption::unk(quint64 arg1, quint8 arg2) { + if (arg2 >= 0x40) + return 0; + return static_cast(arg1 >> arg2); +} + +QByteArray Encryption::GetIV(const QByteArray &table, int index) { + int num1 = 0xFA0 + index; + int num2 = unk(0x51EB851FLL * num1, 0x20); + int adjust = ((num2 >> 6) + (num2 >> 31)); + int startIndex = 20 * (num1 - 200 * adjust); + // Return 8 bytes from that location. + return table.mid(startIndex, 8); +} + +void Encryption::UpdateIVTable(QByteArray &table, int index, const QByteArray §ionHash) { + int blockNumIndex = index % 4; + int baseOffset = 0xFA0 + blockNumIndex * 4; + quint32 blockNumVal = (static_cast(table.at(baseOffset)) ) | + (static_cast(table.at(baseOffset + 1)) << 8 ) | + (static_cast(table.at(baseOffset + 2)) << 16) | + (static_cast(table.at(baseOffset + 3)) << 24); + int blockNum = blockNumVal * 4 + index; + int num2 = unk(0x51EB851FLL * blockNum, 0x20); + int adjust = ((num2 >> 6) + (num2 >> 31)); + int startIndex = 20 * (blockNum - 200 * adjust) + 1; + int hashIndex = 0; + for (int x = 0; x < 4; ++x) { + table[startIndex - 1] = table.at(startIndex - 1) ^ sectionHash.at(hashIndex); + table[startIndex] = table.at(startIndex) ^ sectionHash.at(hashIndex + 1); + table[startIndex + 1] = table.at(startIndex + 1) ^ sectionHash.at(hashIndex + 2); + table[startIndex + 2] = table.at(startIndex + 2) ^ sectionHash.at(hashIndex + 3); + table[startIndex + 3] = table.at(startIndex + 3) ^ sectionHash.at(hashIndex + 4); + startIndex += 5; + hashIndex += 5; + } +} + +quint32 Encryption::ToUInt32(const QByteArray &data, int offset) { + // Converts 4 bytes (starting at offset) from data into a 32-bit unsigned integer (little-endian) + return ((static_cast(static_cast(data[offset])) ) | + (static_cast(static_cast(data[offset+1])) << 8 ) | + (static_cast(static_cast(data[offset+2])) << 16) | + (static_cast(static_cast(data[offset+3])) << 24)); +} + +QByteArray Encryption::salsa20DecryptSection(const QByteArray §ionData, const QByteArray &key, const QByteArray &iv, int blockSize) +{ + // Choose the appropriate constant based on key length. + QByteArray constants; + if (key.size() == 32) + constants = "expand 32-byte k"; + else if (key.size() == 16) + constants = "expand 16-byte k"; + else { + qWarning() << "Invalid key size:" << key.size() << "; expected 16 or 32 bytes."; + return QByteArray(); + } + + QVector state(VECTOR_SIZE); + + // Set state[0] using the first 4 bytes of the constant. + state[0] = ConvertArrayTo32Bit(constants.mid(0, 4)); + + // state[1] through state[4] come from the first 16 bytes of the key. + state[1] = ToUInt32(key, 0); + state[2] = ToUInt32(key, 4); + state[3] = ToUInt32(key, 8); + state[4] = ToUInt32(key, 12); + + // state[5] comes from the next 4 bytes of the constant. + state[5] = ConvertArrayTo32Bit(constants.mid(4, 4)); + + // state[6] and state[7] come from the IV (which must be 8 bytes). + state[6] = ConvertArrayTo32Bit(iv.mid(0, 4)); + state[7] = ConvertArrayTo32Bit(iv.mid(4, 4)); + + // state[8] and state[9] are the 64-bit block counter (start at 0). + state[8] = 0; + state[9] = 0; + + // state[10] comes from the next 4 bytes of the constant. + state[10] = ConvertArrayTo32Bit(constants.mid(8, 4)); + + // For state[11] through state[14]: + // If the key is 32 bytes, use bytes 16..31; if 16 bytes, reuse the first 16 bytes. + if (key.size() == 32) { + state[11] = ToUInt32(key, 16); + state[12] = ToUInt32(key, 20); + state[13] = ToUInt32(key, 24); + state[14] = ToUInt32(key, 28); + } else { // key.size() == 16 + state[11] = ToUInt32(key, 0); + state[12] = ToUInt32(key, 4); + state[13] = ToUInt32(key, 8); + state[14] = ToUInt32(key, 12); + } + + // state[15] comes from the last 4 bytes of the constant. + state[15] = ConvertArrayTo32Bit(constants.mid(12, 4)); + + // Prepare the output buffer. + QByteArray output(sectionData.size(), Qt::Uninitialized); + int numBlocks = sectionData.size() / blockSize; + int remainder = sectionData.size() % blockSize; + + // Process each full block. + for (int blockIndex = 0; blockIndex < numBlocks; ++blockIndex) { + QVector x = state; // make a copy of the current state for this block + + // Run 20 rounds (10 iterations) of Salsa20. + for (int round = 20; round > 0; round -= 2) { + x[4] ^= Rotate(x[0] + x[12], 7); + x[8] ^= Rotate(x[4] + x[0], 9); + x[12] ^= Rotate(x[8] + x[4], 13); + x[0] ^= Rotate(x[12] + x[8], 18); + + x[9] ^= Rotate(x[5] + x[1], 7); + x[13] ^= Rotate(x[9] + x[5], 9); + x[1] ^= Rotate(x[13] + x[9], 13); + x[5] ^= Rotate(x[1] + x[13], 18); + + x[14] ^= Rotate(x[10] + x[6], 7); + x[2] ^= Rotate(x[14] + x[10], 9); + x[6] ^= Rotate(x[2] + x[14], 13); + x[10] ^= Rotate(x[6] + x[2], 18); + + x[3] ^= Rotate(x[15] + x[11], 7); + x[7] ^= Rotate(x[3] + x[15], 9); + x[11] ^= Rotate(x[7] + x[3], 13); + x[15] ^= Rotate(x[11] + x[7], 18); + + x[1] ^= Rotate(x[0] + x[3], 7); + x[2] ^= Rotate(x[1] + x[0], 9); + x[3] ^= Rotate(x[2] + x[1], 13); + x[0] ^= Rotate(x[3] + x[2], 18); + + x[6] ^= Rotate(x[5] + x[4], 7); + x[7] ^= Rotate(x[6] + x[5], 9); + x[4] ^= Rotate(x[7] + x[6], 13); + x[5] ^= Rotate(x[4] + x[7], 18); + + x[11] ^= Rotate(x[10] + x[9], 7); + x[8] ^= Rotate(x[11] + x[10], 9); + x[9] ^= Rotate(x[8] + x[11], 13); + x[10] ^= Rotate(x[9] + x[8], 18); + + x[12] ^= Rotate(x[15] + x[14], 7); + x[13] ^= Rotate(x[12] + x[15], 9); + x[14] ^= Rotate(x[13] + x[12], 13); + x[15] ^= Rotate(x[14] + x[13], 18); + } + + // Produce the 64-byte keystream block by adding the original state. + QVector keyStreamBlock(blockSize); + for (int i = 0; i < VECTOR_SIZE; ++i) { + x[i] += state[i]; + Convert32BitTo8Bit(x[i], keyStreamBlock.data() + 4 * i); + } + + // XOR the keystream block with the corresponding block of sectionData. + const uchar* inBlock = reinterpret_cast(sectionData.constData()) + blockIndex * blockSize; + uchar* outBlock = reinterpret_cast(output.data()) + blockIndex * blockSize; + for (int j = 0; j < blockSize; ++j) { + outBlock[j] = inBlock[j] ^ keyStreamBlock[j]; + } + // Increment the 64-bit block counter. + state[8]++; + if (state[8] == 0) + state[9]++; + } + + // Process any remaining bytes. + if (remainder > 0) { + QVector x = state; + for (int round = 20; round > 0; round -= 2) { + x[4] ^= Rotate(x[0] + x[12], 7); + x[8] ^= Rotate(x[4] + x[0], 9); + x[12] ^= Rotate(x[8] + x[4], 13); + x[0] ^= Rotate(x[12] + x[8], 18); + + x[9] ^= Rotate(x[5] + x[1], 7); + x[13] ^= Rotate(x[9] + x[5], 9); + x[1] ^= Rotate(x[13] + x[9], 13); + x[5] ^= Rotate(x[1] + x[13], 18); + + x[14] ^= Rotate(x[10] + x[6], 7); + x[2] ^= Rotate(x[14] + x[10], 9); + x[6] ^= Rotate(x[2] + x[14], 13); + x[10] ^= Rotate(x[6] + x[2], 18); + + x[3] ^= Rotate(x[15] + x[11], 7); + x[7] ^= Rotate(x[3] + x[15], 9); + x[11] ^= Rotate(x[7] + x[3], 13); + x[15] ^= Rotate(x[11] + x[7], 18); + + x[1] ^= Rotate(x[0] + x[3], 7); + x[2] ^= Rotate(x[1] + x[0], 9); + x[3] ^= Rotate(x[2] + x[1], 13); + x[0] ^= Rotate(x[3] + x[2], 18); + + x[6] ^= Rotate(x[5] + x[4], 7); + x[7] ^= Rotate(x[6] + x[5], 9); + x[4] ^= Rotate(x[7] + x[6], 13); + x[5] ^= Rotate(x[4] + x[7], 18); + + x[11] ^= Rotate(x[10] + x[9], 7); + x[8] ^= Rotate(x[11] + x[10], 9); + x[9] ^= Rotate(x[8] + x[11], 13); + x[10] ^= Rotate(x[9] + x[8], 18); + + x[12] ^= Rotate(x[15] + x[14], 7); + x[13] ^= Rotate(x[12] + x[15], 9); + x[14] ^= Rotate(x[13] + x[12], 13); + x[15] ^= Rotate(x[14] + x[13], 18); + } + QVector keyStreamBlock(blockSize); + for (int i = 0; i < VECTOR_SIZE; ++i) { + x[i] += state[i]; + Convert32BitTo8Bit(x[i], keyStreamBlock.data() + 4 * i); + } + const uchar* inBlock = reinterpret_cast(sectionData.constData()) + numBlocks * blockSize; + uchar* outBlock = reinterpret_cast(output.data()) + numBlocks * blockSize; + for (int j = 0; j < remainder; ++j) + outBlock[j] = inBlock[j] ^ keyStreamBlock[j]; + } + + return output; +} + +void Encryption::fillIVTable(QByteArray fastFileData, QByteArray &ivTable, quint32 ivTableLength) +{ + QDataStream stream(fastFileData); + stream.skipRawData(24); + + quint32 nameKeyLength = 0; + for (int i = 0; i < 32 && !stream.atEnd(); i++) { + if (!stream.atEnd() && stream.device()->peek(1).toHex() != "00") { + nameKeyLength++; + stream.skipRawData(1); + } else { + break; + } + } + + if (nameKeyLength < 32) { + stream.skipRawData(32 - nameKeyLength); + } + + if (ivTableLength < 16) { + qWarning() << "IV table length too small!"; + return; + } + + for (quint32 i = 0; i < ivTableLength - 16; i++) { + if (stream.atEnd()) { + qWarning() << "Stream ended while filling IV table!"; + return; + } + quint8 ivVal; + stream >> ivVal; + ivTable[i] = ivVal; + } +} + +void Encryption::fillIV(int index, QByteArray &ivPtr, const QByteArray &ivTable, const QVector &ivCounter) +{ + if (index < 0 || index >= ivCounter.size()) { + qWarning() << "Invalid IV index: " << index; + return; + } + + int ivOffset = ((index + 4 * (ivCounter[index] - 1)) % 800) * 20; + + if (ivOffset + 8 > ivTable.size()) { + qWarning() << "IV offset out of bounds! Offset: " << ivOffset; + return; + } + + ivPtr = ivTable.mid(ivOffset, 8); +} + +void Encryption::generateNewIV(int index, const QByteArray &hash, QByteArray &ivTable, QVector &ivCounter) +{ + if (index < 0 || index >= ivCounter.size()) { + qWarning() << "Invalid index: " << index; + return; + } + + quint32 safeCounter = fmin(ivCounter[index], 800u - 1); + int ivOffset = (index + 4 * safeCounter) % 800 * 5; + + for (int i = 0; i < 20; i++) { + if (ivOffset + i >= ivTable.size()) { + qWarning() << "Index out of bounds for IV table!"; + return; + } + ivTable[ivOffset + i] ^= hash[i]; + } + + ivCounter[index]++; +} + +QByteArray Encryption::decryptFastFile(const QByteArray &fastFileData) +{ + const QByteArray bo2_salsa20_key = QByteArray::fromHex("641D8A2FE31D3AA63622BBC9CE8587229D42B0F8ED9B924130BF88B65EDC50BE"); + + QByteArray fileData = fastFileData; + QByteArray finalFastFile; + + QByteArray ivTable(16000, 0); + fillIVTable(fileData, ivTable, 16000 - 1); + + QVector ivCounter(4, 1); + QDataStream stream(fileData); + stream.setByteOrder(QDataStream::LittleEndian); + stream.skipRawData(0x138); + + QByteArray sha1Hash(20, 0); + QByteArray ivPtr(8, 0); + int chunkIndex = 0; + + while (!stream.atEnd()) { + quint32 dataLength; + stream >> dataLength; + + if (dataLength == 0 || dataLength > fileData.size() - stream.device()->pos()) { + qWarning() << "Invalid data length at offset: " << stream.device()->pos(); + break; + } + + fillIV(chunkIndex % 4, ivPtr, ivTable, ivCounter); + + ECRYPT_ctx x; + ECRYPT_keysetup(&x, reinterpret_cast(bo2_salsa20_key.constData()), 256, 0); + ECRYPT_ivsetup(&x, reinterpret_cast(ivPtr.constData())); + + QByteArray encryptedBlock = fileData.mid(stream.device()->pos(), dataLength); + QByteArray decryptedBlock; + decryptedBlock.resize(dataLength); + + ECRYPT_decrypt_bytes(&x, reinterpret_cast(encryptedBlock.constData()), + reinterpret_cast(decryptedBlock.data()), dataLength); + + QCryptographicHash sha1(QCryptographicHash::Sha1); + sha1.addData(decryptedBlock); + sha1Hash = sha1.result(); + + z_stream strm = {}; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = static_cast(decryptedBlock.size()); + strm.next_in = reinterpret_cast(decryptedBlock.data()); + + QByteArray decompressedData; + decompressedData.resize(fmax(dataLength * 2, 4096)); + strm.avail_out = decompressedData.size(); + strm.next_out = reinterpret_cast(decompressedData.data()); + + int zReturn = inflateInit2(&strm, -15); + if (zReturn != Z_OK) { + qWarning() << "inflateInit2 failed with error code" << zReturn; + break; + } + + zReturn = inflate(&strm, Z_FINISH); + inflateEnd(&strm); + + if (zReturn != Z_STREAM_END) { + qDebug() << "Error decompressing at offset: " << stream.device()->pos() << " : " << zReturn; + decompressedData.clear(); + } else { + decompressedData.resize(strm.total_out); + } + + finalFastFile.append(decompressedData); + + generateNewIV(chunkIndex % 4, sha1Hash, ivTable, ivCounter); + + if (stream.device()->pos() + static_cast(dataLength) > fileData.size()) { + qWarning() << "Skipping past file size!"; + break; + } + + stream.skipRawData(dataLength); + chunkIndex++; + } + + return finalFastFile; +} diff --git a/libs/encryption/encryption.h b/libs/encryption/encryption.h index 6157bbe..6b36041 100644 --- a/libs/encryption/encryption.h +++ b/libs/encryption/encryption.h @@ -6,9 +6,6 @@ #include #include -#include "ecrypt-sync.h" -#include "QtZlib/zlib.h" - class Encryption { public: static const int VECTOR_SIZE = 16; // 16 32-bit words @@ -17,450 +14,39 @@ public: //-------------------------------------------------------------------- // Helper functions (assuming little–endian order) - static void Convert32BitTo8Bit(quint32 value, quint8* array) { - array[0] = static_cast(value >> 0); - array[1] = static_cast(value >> 8); - array[2] = static_cast(value >> 16); - array[3] = static_cast(value >> 24); - } + static void Convert32BitTo8Bit(quint32 value, quint8* array); - static quint32 ConvertArrayTo32Bit(const QByteArray &array) { - return ((static_cast(static_cast(array[0])) << 0) | - (static_cast(static_cast(array[1])) << 8) | - (static_cast(static_cast(array[2])) << 16) | - (static_cast(static_cast(array[3])) << 24)); - } + static quint32 ConvertArrayTo32Bit(const QByteArray &array); - static quint32 Rotate(quint32 value, quint32 numBits) { - return (value << numBits) | (value >> (32 - numBits)); - } + static quint32 Rotate(quint32 value, quint32 numBits); // Build the IV table from a 0x20–byte feed. The table is 0xFB0 bytes. - static QByteArray InitIVTable(const QByteArray &feed) { - const int tableSize = 0xFB0; - QByteArray table; - table.resize(tableSize); - int ptr = 0; - for (int i = 0; i < 200; ++i) { - for (int x = 0; x < 5; ++x) { - if (static_cast(feed.at(ptr)) == 0x00) - ptr = 0; - int base = i * 20 + x * 4; - table[base] = feed.at(ptr); - table[base + 1] = feed.at(ptr); - table[base + 2] = feed.at(ptr); - table[base + 3] = feed.at(ptr); - ++ptr; - } - } - // Copy block numbers [1,0,0,0] into the last 16 bytes - QByteArray oneBlock; - oneBlock.append(char(1)); oneBlock.append(char(0)); oneBlock.append(char(0)); oneBlock.append(char(0)); - table.replace(0xFA0, 4, oneBlock); - table.replace(0xFA4, 4, oneBlock); - table.replace(0xFA8, 4, oneBlock); - table.replace(0xFAC, 4, oneBlock); - return table; - } + static QByteArray InitIVTable(const QByteArray &feed); // "unk" function as in the C# code. - static int unk(quint64 arg1, quint8 arg2) { - if (arg2 >= 0x40) - return 0; - return static_cast(arg1 >> arg2); - } + static int unk(quint64 arg1, quint8 arg2); // Compute the IV for a given section index using the IV table. - static QByteArray GetIV(const QByteArray &table, int index) { - int num1 = 0xFA0 + index; - int num2 = unk(0x51EB851FLL * num1, 0x20); - int adjust = ((num2 >> 6) + (num2 >> 31)); - int startIndex = 20 * (num1 - 200 * adjust); - // Return 8 bytes from that location. - return table.mid(startIndex, 8); - } + static QByteArray GetIV(const QByteArray &table, int index); // Update the IV table given the section's SHA1 hash. - static void UpdateIVTable(QByteArray &table, int index, const QByteArray §ionHash) { - int blockNumIndex = index % 4; - int baseOffset = 0xFA0 + blockNumIndex * 4; - quint32 blockNumVal = (static_cast(table.at(baseOffset)) ) | - (static_cast(table.at(baseOffset + 1)) << 8 ) | - (static_cast(table.at(baseOffset + 2)) << 16) | - (static_cast(table.at(baseOffset + 3)) << 24); - int blockNum = blockNumVal * 4 + index; - int num2 = unk(0x51EB851FLL * blockNum, 0x20); - int adjust = ((num2 >> 6) + (num2 >> 31)); - int startIndex = 20 * (blockNum - 200 * adjust) + 1; - int hashIndex = 0; - for (int x = 0; x < 4; ++x) { - table[startIndex - 1] = table.at(startIndex - 1) ^ sectionHash.at(hashIndex); - table[startIndex] = table.at(startIndex) ^ sectionHash.at(hashIndex + 1); - table[startIndex + 1] = table.at(startIndex + 1) ^ sectionHash.at(hashIndex + 2); - table[startIndex + 2] = table.at(startIndex + 2) ^ sectionHash.at(hashIndex + 3); - table[startIndex + 3] = table.at(startIndex + 3) ^ sectionHash.at(hashIndex + 4); - startIndex += 5; - hashIndex += 5; - } - } + static void UpdateIVTable(QByteArray &table, int index, const QByteArray §ionHash); - static quint32 ToUInt32(const QByteArray &data, int offset) { - // Converts 4 bytes (starting at offset) from data into a 32-bit unsigned integer (little-endian) - return ((static_cast(static_cast(data[offset])) ) | - (static_cast(static_cast(data[offset+1])) << 8 ) | - (static_cast(static_cast(data[offset+2])) << 16) | - (static_cast(static_cast(data[offset+3])) << 24)); - } + static quint32 ToUInt32(const QByteArray &data, int offset); //-------------------------------------------------------------------- // Salsa20 decryption for one section. // This function resets the counter for each section. - static QByteArray salsa20DecryptSection(const QByteArray §ionData, const QByteArray &key, const QByteArray &iv, int blockSize = 64) - { - // Choose the appropriate constant based on key length. - QByteArray constants; - if (key.size() == 32) - constants = "expand 32-byte k"; - else if (key.size() == 16) - constants = "expand 16-byte k"; - else { - qWarning() << "Invalid key size:" << key.size() << "; expected 16 or 32 bytes."; - return QByteArray(); - } + static QByteArray salsa20DecryptSection(const QByteArray §ionData, const QByteArray &key, const QByteArray &iv, int blockSize = 64); - QVector state(VECTOR_SIZE); + static void fillIVTable(QByteArray fastFileData, QByteArray& ivTable, quint32 ivTableLength); - // Set state[0] using the first 4 bytes of the constant. - state[0] = ConvertArrayTo32Bit(constants.mid(0, 4)); + static void fillIV(int index, QByteArray& ivPtr, const QByteArray& ivTable, const QVector& ivCounter); - // state[1] through state[4] come from the first 16 bytes of the key. - state[1] = ToUInt32(key, 0); - state[2] = ToUInt32(key, 4); - state[3] = ToUInt32(key, 8); - state[4] = ToUInt32(key, 12); - // state[5] comes from the next 4 bytes of the constant. - state[5] = ConvertArrayTo32Bit(constants.mid(4, 4)); + static void generateNewIV(int index, const QByteArray& hash, QByteArray& ivTable, QVector& ivCounter); - // state[6] and state[7] come from the IV (which must be 8 bytes). - state[6] = ConvertArrayTo32Bit(iv.mid(0, 4)); - state[7] = ConvertArrayTo32Bit(iv.mid(4, 4)); - - // state[8] and state[9] are the 64-bit block counter (start at 0). - state[8] = 0; - state[9] = 0; - - // state[10] comes from the next 4 bytes of the constant. - state[10] = ConvertArrayTo32Bit(constants.mid(8, 4)); - - // For state[11] through state[14]: - // If the key is 32 bytes, use bytes 16..31; if 16 bytes, reuse the first 16 bytes. - if (key.size() == 32) { - state[11] = ToUInt32(key, 16); - state[12] = ToUInt32(key, 20); - state[13] = ToUInt32(key, 24); - state[14] = ToUInt32(key, 28); - } else { // key.size() == 16 - state[11] = ToUInt32(key, 0); - state[12] = ToUInt32(key, 4); - state[13] = ToUInt32(key, 8); - state[14] = ToUInt32(key, 12); - } - - // state[15] comes from the last 4 bytes of the constant. - state[15] = ConvertArrayTo32Bit(constants.mid(12, 4)); - - // Prepare the output buffer. - QByteArray output(sectionData.size(), Qt::Uninitialized); - int numBlocks = sectionData.size() / blockSize; - int remainder = sectionData.size() % blockSize; - - // Process each full block. - for (int blockIndex = 0; blockIndex < numBlocks; ++blockIndex) { - QVector x = state; // make a copy of the current state for this block - - // Run 20 rounds (10 iterations) of Salsa20. - for (int round = 20; round > 0; round -= 2) { - x[4] ^= Rotate(x[0] + x[12], 7); - x[8] ^= Rotate(x[4] + x[0], 9); - x[12] ^= Rotate(x[8] + x[4], 13); - x[0] ^= Rotate(x[12] + x[8], 18); - - x[9] ^= Rotate(x[5] + x[1], 7); - x[13] ^= Rotate(x[9] + x[5], 9); - x[1] ^= Rotate(x[13] + x[9], 13); - x[5] ^= Rotate(x[1] + x[13], 18); - - x[14] ^= Rotate(x[10] + x[6], 7); - x[2] ^= Rotate(x[14] + x[10], 9); - x[6] ^= Rotate(x[2] + x[14], 13); - x[10] ^= Rotate(x[6] + x[2], 18); - - x[3] ^= Rotate(x[15] + x[11], 7); - x[7] ^= Rotate(x[3] + x[15], 9); - x[11] ^= Rotate(x[7] + x[3], 13); - x[15] ^= Rotate(x[11] + x[7], 18); - - x[1] ^= Rotate(x[0] + x[3], 7); - x[2] ^= Rotate(x[1] + x[0], 9); - x[3] ^= Rotate(x[2] + x[1], 13); - x[0] ^= Rotate(x[3] + x[2], 18); - - x[6] ^= Rotate(x[5] + x[4], 7); - x[7] ^= Rotate(x[6] + x[5], 9); - x[4] ^= Rotate(x[7] + x[6], 13); - x[5] ^= Rotate(x[4] + x[7], 18); - - x[11] ^= Rotate(x[10] + x[9], 7); - x[8] ^= Rotate(x[11] + x[10], 9); - x[9] ^= Rotate(x[8] + x[11], 13); - x[10] ^= Rotate(x[9] + x[8], 18); - - x[12] ^= Rotate(x[15] + x[14], 7); - x[13] ^= Rotate(x[12] + x[15], 9); - x[14] ^= Rotate(x[13] + x[12], 13); - x[15] ^= Rotate(x[14] + x[13], 18); - } - - // Produce the 64-byte keystream block by adding the original state. - QVector keyStreamBlock(blockSize); - for (int i = 0; i < VECTOR_SIZE; ++i) { - x[i] += state[i]; - Convert32BitTo8Bit(x[i], keyStreamBlock.data() + 4 * i); - } - - // XOR the keystream block with the corresponding block of sectionData. - const uchar* inBlock = reinterpret_cast(sectionData.constData()) + blockIndex * blockSize; - uchar* outBlock = reinterpret_cast(output.data()) + blockIndex * blockSize; - for (int j = 0; j < blockSize; ++j) { - outBlock[j] = inBlock[j] ^ keyStreamBlock[j]; - } - // Increment the 64-bit block counter. - state[8]++; - if (state[8] == 0) - state[9]++; - } - - // Process any remaining bytes. - if (remainder > 0) { - QVector x = state; - for (int round = 20; round > 0; round -= 2) { - x[4] ^= Rotate(x[0] + x[12], 7); - x[8] ^= Rotate(x[4] + x[0], 9); - x[12] ^= Rotate(x[8] + x[4], 13); - x[0] ^= Rotate(x[12] + x[8], 18); - - x[9] ^= Rotate(x[5] + x[1], 7); - x[13] ^= Rotate(x[9] + x[5], 9); - x[1] ^= Rotate(x[13] + x[9], 13); - x[5] ^= Rotate(x[1] + x[13], 18); - - x[14] ^= Rotate(x[10] + x[6], 7); - x[2] ^= Rotate(x[14] + x[10], 9); - x[6] ^= Rotate(x[2] + x[14], 13); - x[10] ^= Rotate(x[6] + x[2], 18); - - x[3] ^= Rotate(x[15] + x[11], 7); - x[7] ^= Rotate(x[3] + x[15], 9); - x[11] ^= Rotate(x[7] + x[3], 13); - x[15] ^= Rotate(x[11] + x[7], 18); - - x[1] ^= Rotate(x[0] + x[3], 7); - x[2] ^= Rotate(x[1] + x[0], 9); - x[3] ^= Rotate(x[2] + x[1], 13); - x[0] ^= Rotate(x[3] + x[2], 18); - - x[6] ^= Rotate(x[5] + x[4], 7); - x[7] ^= Rotate(x[6] + x[5], 9); - x[4] ^= Rotate(x[7] + x[6], 13); - x[5] ^= Rotate(x[4] + x[7], 18); - - x[11] ^= Rotate(x[10] + x[9], 7); - x[8] ^= Rotate(x[11] + x[10], 9); - x[9] ^= Rotate(x[8] + x[11], 13); - x[10] ^= Rotate(x[9] + x[8], 18); - - x[12] ^= Rotate(x[15] + x[14], 7); - x[13] ^= Rotate(x[12] + x[15], 9); - x[14] ^= Rotate(x[13] + x[12], 13); - x[15] ^= Rotate(x[14] + x[13], 18); - } - QVector keyStreamBlock(blockSize); - for (int i = 0; i < VECTOR_SIZE; ++i) { - x[i] += state[i]; - Convert32BitTo8Bit(x[i], keyStreamBlock.data() + 4 * i); - } - const uchar* inBlock = reinterpret_cast(sectionData.constData()) + numBlocks * blockSize; - uchar* outBlock = reinterpret_cast(output.data()) + numBlocks * blockSize; - for (int j = 0; j < remainder; ++j) - outBlock[j] = inBlock[j] ^ keyStreamBlock[j]; - } - - return output; - } - - static void fillIVTable(QByteArray fastFileData, QByteArray& ivTable, quint32 ivTableLength) - { - QDataStream stream(fastFileData); - stream.skipRawData(24); - - quint32 nameKeyLength = 0; - for (int i = 0; i < 32 && !stream.atEnd(); i++) { - if (!stream.atEnd() && stream.device()->peek(1).toHex() != "00") { - nameKeyLength++; - stream.skipRawData(1); - } else { - break; - } - } - - if (nameKeyLength < 32) { - stream.skipRawData(32 - nameKeyLength); - } - - if (ivTableLength < 16) { - qWarning() << "IV table length too small!"; - return; - } - - for (quint32 i = 0; i < ivTableLength - 16; i++) { - if (stream.atEnd()) { - qWarning() << "Stream ended while filling IV table!"; - return; - } - quint8 ivVal; - stream >> ivVal; - ivTable[i] = ivVal; - } - } - - static void fillIV(int index, QByteArray& ivPtr, const QByteArray& ivTable, const QVector& ivCounter) - { - if (index < 0 || index >= ivCounter.size()) { - qWarning() << "Invalid IV index: " << index; - return; - } - - int ivOffset = ((index + 4 * (ivCounter[index] - 1)) % 800) * 20; - - if (ivOffset + 8 > ivTable.size()) { - qWarning() << "IV offset out of bounds! Offset: " << ivOffset; - return; - } - - ivPtr = ivTable.mid(ivOffset, 8); - } - - - static void generateNewIV(int index, const QByteArray& hash, QByteArray& ivTable, QVector& ivCounter) - { - if (index < 0 || index >= ivCounter.size()) { - qWarning() << "Invalid index: " << index; - return; - } - - quint32 safeCounter = fmin(ivCounter[index], 800u - 1); - int ivOffset = (index + 4 * safeCounter) % 800 * 5; - - for (int i = 0; i < 20; i++) { - if (ivOffset + i >= ivTable.size()) { - qWarning() << "Index out of bounds for IV table!"; - return; - } - ivTable[ivOffset + i] ^= hash[i]; - } - - ivCounter[index]++; - } - - static QByteArray decryptFastFile(const QByteArray& fastFileData) - { - const QByteArray bo2_salsa20_key = QByteArray::fromHex("641D8A2FE31D3AA63622BBC9CE8587229D42B0F8ED9B924130BF88B65EDC50BE"); - - QByteArray fileData = fastFileData; - QByteArray finalFastFile; - - QByteArray ivTable(16000, 0); - fillIVTable(fileData, ivTable, 16000 - 1); - - QVector ivCounter(4, 1); - QDataStream stream(fileData); - stream.setByteOrder(QDataStream::LittleEndian); - stream.skipRawData(0x138); - - QByteArray sha1Hash(20, 0); - QByteArray ivPtr(8, 0); - int chunkIndex = 0; - - while (!stream.atEnd()) { - quint32 dataLength; - stream >> dataLength; - - if (dataLength == 0 || dataLength > fileData.size() - stream.device()->pos()) { - qWarning() << "Invalid data length at offset: " << stream.device()->pos(); - break; - } - - fillIV(chunkIndex % 4, ivPtr, ivTable, ivCounter); - - ECRYPT_ctx x; - ECRYPT_keysetup(&x, reinterpret_cast(bo2_salsa20_key.constData()), 256, 0); - ECRYPT_ivsetup(&x, reinterpret_cast(ivPtr.constData())); - - QByteArray encryptedBlock = fileData.mid(stream.device()->pos(), dataLength); - QByteArray decryptedBlock; - decryptedBlock.resize(dataLength); - - ECRYPT_decrypt_bytes(&x, reinterpret_cast(encryptedBlock.constData()), - reinterpret_cast(decryptedBlock.data()), dataLength); - - QCryptographicHash sha1(QCryptographicHash::Sha1); - sha1.addData(decryptedBlock); - sha1Hash = sha1.result(); - - z_stream strm = {}; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = static_cast(decryptedBlock.size()); - strm.next_in = reinterpret_cast(decryptedBlock.data()); - - QByteArray decompressedData; - decompressedData.resize(fmax(dataLength * 2, 4096)); - strm.avail_out = decompressedData.size(); - strm.next_out = reinterpret_cast(decompressedData.data()); - - int zReturn = inflateInit2(&strm, -15); - if (zReturn != Z_OK) { - qWarning() << "inflateInit2 failed with error code" << zReturn; - break; - } - - zReturn = inflate(&strm, Z_FINISH); - inflateEnd(&strm); - - if (zReturn != Z_STREAM_END) { - qDebug() << "Error decompressing at offset: " << stream.device()->pos() << " : " << zReturn; - decompressedData.clear(); - } else { - decompressedData.resize(strm.total_out); - } - - finalFastFile.append(decompressedData); - - generateNewIV(chunkIndex % 4, sha1Hash, ivTable, ivCounter); - - if (stream.device()->pos() + static_cast(dataLength) > fileData.size()) { - qWarning() << "Skipping past file size!"; - break; - } - - stream.skipRawData(dataLength); - chunkIndex++; - } - - return finalFastFile; - } + static QByteArray decryptFastFile(const QByteArray& fastFileData); }; #endif // ENCRYPTION_H diff --git a/libs/encryption/encryption.pro b/libs/encryption/encryption.pro index 4c7d8a0..28d2cfb 100644 --- a/libs/encryption/encryption.pro +++ b/libs/encryption/encryption.pro @@ -1,6 +1,6 @@ QT += core TEMPLATE = lib -CONFIG += staticlib c++17 debug +CONFIG += staticlib c++17 SOURCES += \ salsa20.cpp \ diff --git a/libs/encryption/sha1.h b/libs/encryption/sha1.h index 055a3db..fd308b3 100644 --- a/libs/encryption/sha1.h +++ b/libs/encryption/sha1.h @@ -2,6 +2,7 @@ /* this file is in the public domain */ #include "config_win32.h" +#include #ifndef __SHA1_H #define __SHA1_H @@ -13,7 +14,7 @@ extern "C" { typedef struct { uint32_t state[5]; uint32_t count[2]; - uint8_t buffer[64]; + uint8_t buffer[64]; } SHA1_CTX; #define SHA1_DIGEST_SIZE 20