1
0
mirror of synced 2024-11-23 22:40:58 +01:00

Include latest encryption keys and implement support for aes-256-cbc

This commit is contained in:
samyuu 2022-01-29 05:12:18 +01:00
parent 575031ac50
commit 1abbf3044e
4 changed files with 183 additions and 75 deletions

View File

@ -5,5 +5,30 @@
# xrefins strings used during datatable json parsing, or even easier if available,
# by looking for the imported native switch SDK crypto function(s) for AES operations (nn::crypto::DecryptAes128Cbc)
[datatable_keys]
jp_ver143 = 5739733538736843547076756A6B4A74
us_ver146 = 5752396A37644547574D697242783877
# Regular Switch Version
ver1413 = 566342346438526962324A366334394B
ver1412 = 7969767733514A5976484D33376A4C37
ver1411 = 536D3961776D4D6A683838457A663233
ver1410 = 5572777861356769564B416874695634
ver149 = 77656D70544D537635643557636E7571
ver148 = 5A416A346D4C51424142343273685164
ver147 = 5A626372466443324E68524B6E476262
ver146 = 5752396A37644547574D697242783877
ver145 = 785452385174387947655547775A4A37
ver143 = 5739733538736843547076756A6B4A74
ver142 = 563448797534664B596973354279415A
ver141 = 544A4D5035744C54515865747A754E37
ver140 = 4144396B506E48647363427436734C70
ver133 = 54424A33624C61596254474458414E54
ver132 = 716437655F4E423433645672334B3753
ver131 = 535F623378384478774B774A36294166
ver130 = 395F747A70745F3249627073744A6A78
ver127 = 75707370784B78426A71565672354A74
ver126 = 4D48675A41533739594746415F392963
ver125 = 636A37365865374846475F52395F3976
ver124 = 6C4E71505F514D5A5636515529677A39
ver120 = 58706B354548386874574D4A444E6B69
# Shitty Unity iOS Version
ptb_ver100 = 54704643596B474170554B6D487A597A
# Shitty Unity PC / XBOX Version
xb1_ver100 = 52615874395235716D565832524D4455784B7132476E5A52754C4D3943694763

View File

@ -8,15 +8,41 @@ namespace TaikoSwitchDataTableDecryptor
constexpr std::string_view EncrpytionKeysIniFileName = "TaikoSwitchDataTableEncrpytionKeys.ini";
struct NamedDataTableKey
struct NamedEncryptionKey
{
std::string_view Name;
PeepoHappy::Crypto::Aes128KeyBytes Key;
size_t KeyByteSize;
PeepoHappy::Crypto::Aes128KeyBytes Key128;
PeepoHappy::Crypto::Aes256KeyBytes Key256;
};
std::vector<NamedDataTableKey> ReadAndParseEncrpytionKeysIniFile(std::unique_ptr<u8[]>& outIniFileContent)
bool DecryptUsingNamedKey(const NamedEncryptionKey& namedKey, const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, PeepoHappy::Crypto::AesIVBytes iv)
{
std::vector<NamedDataTableKey> namedKeys;
if (namedKey.KeyByteSize == namedKey.Key128.size())
return PeepoHappy::Crypto::DecryptAes128Cbc(inEncryptedData, outDecryptedData, inOutDataSize, namedKey.Key128, iv);
else if (namedKey.KeyByteSize == namedKey.Key256.size())
return PeepoHappy::Crypto::DecryptAes256Cbc(inEncryptedData, outDecryptedData, inOutDataSize, namedKey.Key256, iv);
else
assert(false);
return false;
}
bool EncryptUsingNamedKey(const NamedEncryptionKey& namedKey, const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, PeepoHappy::Crypto::AesIVBytes iv)
{
if (namedKey.KeyByteSize == namedKey.Key128.size())
return PeepoHappy::Crypto::EncryptAes128Cbc(inDecryptedData, outEncryptedData, inOutDataSize, namedKey.Key128, iv);
else if (namedKey.KeyByteSize == namedKey.Key256.size())
return PeepoHappy::Crypto::EncryptAes256Cbc(inDecryptedData, outEncryptedData, inOutDataSize, namedKey.Key256, iv);
else
assert(false);
return false;
}
std::vector<NamedEncryptionKey> ReadAndParseEncrpytionKeysIniFile(std::unique_ptr<u8[]>& outIniFileContent)
{
std::vector<NamedEncryptionKey> namedKeys;
#if 1 // NOTE: I think this makes more sense here, don't wanna fail to load the file just because of a different working directory
auto[iniFileContent, iniFileSize] = PeepoHappy::IO::ReadEntireFile(PeepoHappy::UTF8::GetExecutableDirectory() + "/" + std::string(EncrpytionKeysIniFileName));
@ -31,10 +57,20 @@ namespace TaikoSwitchDataTableDecryptor
if (!PeepoHappy::UTF8::AppearsToUse8BitCodeUnits(iniFileStringView.substr(0, std::min<size_t>(32, iniFileStringView.size()))))
fprintf(stderr, "INI file does not appear to be UTF-8 encoded\n");
PeepoHappy::IO::ParseIniFileContent(iniFileStringView, [&namedKeys](std::string_view section, std::string_view key, std::string_view value)
PeepoHappy::IO::ParseIniFileContent(iniFileStringView, [&namedKeys](std::string_view iniSection, std::string_view iniKey, std::string_view iniValue)
{
if (section == "datatable_keys")
namedKeys.push_back({ key, PeepoHappy::Crypto::ParseAes128KeyHexByteString(value) });
if (iniSection == "datatable_keys")
{
NamedEncryptionKey newKey;
newKey.Name = iniKey;
newKey.Key128 = PeepoHappy::Crypto::ParseAes128KeyHexByteString(iniValue);
newKey.Key256 = PeepoHappy::Crypto::ParseAes256KeyHexByteString(iniValue);
const bool upperHalfOf256KeyAllZeros = std::all_of(newKey.Key256.begin() + (PeepoHappy::Crypto::Aes256KeySize / 2), newKey.Key256.end(), [](u8 byte) { return byte == 0x00; });
newKey.KeyByteSize = upperHalfOf256KeyAllZeros ? PeepoHappy::Crypto::Aes128KeySize : PeepoHappy::Crypto::Aes256KeySize;
namedKeys.push_back(std::move(newKey));
}
});
if (namedKeys.empty())
@ -50,7 +86,7 @@ namespace TaikoSwitchDataTableDecryptor
}
// NOTE: { "datatable/musicinfo.bin", NamedKey{"jp_ver169", ...} } -> "datatable/musicinfo jp_ver169.json"
std::string FormatJsonOutputFilePathUsingNamedKey(std::string_view binFilePath, const NamedDataTableKey* key)
std::string FormatJsonOutputFilePathUsingNamedKey(std::string_view binFilePath, const NamedEncryptionKey* key)
{
std::string formattedFilePath { PeepoHappy::Path::TrimFileExtension(binFilePath) };
if (key != nullptr && !key->Name.empty())
@ -63,7 +99,7 @@ namespace TaikoSwitchDataTableDecryptor
}
// NOTE: ("datatable/musicinfo jp_ver169.json") -> { "datatable/musicinfo.bin", NamedKey{"jp_ver169", ...} }
std::pair<std::string, const NamedDataTableKey*> ParseJsonInputFilePathUsingNamedKeysAndFormatBinOutputFilePath(std::string_view jsonFilePath, const std::vector<NamedDataTableKey>& namedKeys)
std::pair<std::string, const NamedEncryptionKey*> ParseJsonInputFilePathUsingNamedKeysAndFormatBinOutputFilePath(std::string_view jsonFilePath, const std::vector<NamedEncryptionKey>& namedKeys)
{
const auto fileNameWithoutExtension = PeepoHappy::Path::GetFileName(jsonFilePath, false);
const auto filePathWithoutExtension = PeepoHappy::Path::TrimFileExtension(jsonFilePath);
@ -79,7 +115,7 @@ namespace TaikoSwitchDataTableDecryptor
return { std::string(PeepoHappy::Path::TrimFileExtension(jsonFilePath)) + ".bin", nullptr };
}
const NamedDataTableKey* TryOutAllAvailableEncrpytionKeysUntilGZipHeaderIsFound(const u8* encryptedFileContent, size_t fileSize, PeepoHappy::Crypto::Aes128IVBytes iv, const std::vector<NamedDataTableKey>& namedKeys)
const NamedEncryptionKey* TryOutAllAvailableEncrpytionKeysUntilGZipHeaderIsFound(const u8* encryptedFileContent, size_t fileSize, PeepoHappy::Crypto::AesIVBytes iv, const std::vector<NamedEncryptionKey>& namedKeys)
{
std::array<u8, 16> decryptedHeaderBuffer = {};
if (fileSize <= decryptedHeaderBuffer.size())
@ -88,18 +124,19 @@ namespace TaikoSwitchDataTableDecryptor
return nullptr;
}
// NOTE: Backwards because newer version keys which are more likely to be used are most likely defined last
for (auto namedKeyIt = namedKeys.rbegin(); namedKeyIt != namedKeys.rend(); namedKeyIt++)
// NOTE: ~~Backwards because newer version keys which are more likely to be used are most likely defined last~~
// turns out everyone already got into the habbit of placing new ones at the top
for (const NamedEncryptionKey& namedKey : namedKeys)
{
decryptedHeaderBuffer = {};
if (!PeepoHappy::Crypto::DecryptAes128Cbc(encryptedFileContent, decryptedHeaderBuffer.data(), decryptedHeaderBuffer.size(), namedKeyIt->Key, iv))
if (!DecryptUsingNamedKey(namedKey, encryptedFileContent, decryptedHeaderBuffer.data(), decryptedHeaderBuffer.size(), iv))
{
fprintf(stderr, "Failed to decrypt input file header\n");
return nullptr;
}
if (PeepoHappy::Compression::HasValidGZipHeader(decryptedHeaderBuffer.data(), decryptedHeaderBuffer.size()))
return &(*namedKeyIt);
return &namedKey;
}
return nullptr;
@ -115,7 +152,7 @@ namespace TaikoSwitchDataTableDecryptor
}
const size_t jsonLength = strnlen(reinterpret_cast<const char*>(decompressedBuffer.get()), MaxDecompressedGameDataTableFileSize);
const auto jsonString = std::string_view(reinterpret_cast<const char*>(decompressedBuffer.get()), jsonLength);
const std::string_view jsonString = std::string_view(reinterpret_cast<const char*>(decompressedBuffer.get()), jsonLength);
if (jsonLength <= 0)
{
@ -132,7 +169,7 @@ namespace TaikoSwitchDataTableDecryptor
return true;
}
int ReadAndWriteEncryptedAndOrCompressedBinToJsonFile(std::string_view binInputFilePath, const std::vector<NamedDataTableKey>& namedKeys)
int ReadAndWriteEncryptedAndOrCompressedBinToJsonFile(std::string_view binInputFilePath, const std::vector<NamedEncryptionKey>& namedKeys)
{
const auto[binFileContent, binFileSize] = PeepoHappy::IO::ReadEntireFile(binInputFilePath);
if (binFileContent == nullptr)
@ -160,13 +197,13 @@ namespace TaikoSwitchDataTableDecryptor
}
else
{
PeepoHappy::Crypto::Aes128IVBytes iv = {};
PeepoHappy::Crypto::AesIVBytes iv = {};
memcpy(iv.data(), binFileContent.get(), iv.size());
const size_t binFileSizeWithoutIV = (binFileSize - iv.size());
const u8* binFileContentWithoutIV = (binFileContent.get() + iv.size());
const auto* foundNamedKey = TryOutAllAvailableEncrpytionKeysUntilGZipHeaderIsFound(binFileContentWithoutIV, binFileSizeWithoutIV, iv, namedKeys);
const NamedEncryptionKey* foundNamedKey = TryOutAllAvailableEncrpytionKeysUntilGZipHeaderIsFound(binFileContentWithoutIV, binFileSizeWithoutIV, iv, namedKeys);
if (foundNamedKey == nullptr)
{
printf("No matching encrpytion key definition found for input file\n");
@ -174,7 +211,7 @@ namespace TaikoSwitchDataTableDecryptor
}
auto decryptedBuffer = std::make_unique<u8[]>(binFileSizeWithoutIV);
if (!PeepoHappy::Crypto::DecryptAes128Cbc(binFileContentWithoutIV, decryptedBuffer.get(), binFileSizeWithoutIV, foundNamedKey->Key, iv))
if (!DecryptUsingNamedKey(*foundNamedKey, binFileContentWithoutIV, decryptedBuffer.get(), binFileSizeWithoutIV, iv))
fprintf(stderr, "Failed to decrypt input file\n");
if (!DecompressAndWriteDataTableJsonFile(decryptedBuffer.get(), binFileSizeWithoutIV, FormatJsonOutputFilePathUsingNamedKey(binInputFilePath, foundNamedKey)))
@ -184,7 +221,7 @@ namespace TaikoSwitchDataTableDecryptor
return EXIT_WIDEPEEPOHAPPY;
}
int ReadAndWriteJsonToCompressedAndOrEncryptedBinFile(std::string_view jsonInputFilePath, const std::vector<NamedDataTableKey>& namedKeys)
int ReadAndWriteJsonToCompressedAndOrEncryptedBinFile(std::string_view jsonInputFilePath, const std::vector<NamedEncryptionKey>& namedKeys)
{
const auto[jsonFileContent, jsonFileSize] = PeepoHappy::IO::ReadEntireFile(jsonInputFilePath);
if (jsonFileContent == nullptr)
@ -192,20 +229,22 @@ namespace TaikoSwitchDataTableDecryptor
fprintf(stderr, "Failed to read input file\n");
return EXIT_WIDEPEEPOSAD;
}
else if ((jsonFileSize + PeepoHappy::Crypto::Aes128IVSize) >= MaxDecompressedGameDataTableFileSize)
else if ((jsonFileSize + PeepoHappy::Crypto::AesIVSize) >= MaxDecompressedGameDataTableFileSize)
{
fprintf(stderr, "Input file too large. DataTable files are limited to %zu bytes\n", MaxDecompressedGameDataTableFileSize);
return EXIT_WIDEPEEPOSAD;
}
auto singleAllocationCombinedBuffers = std::make_unique<u8[]>(MaxDecompressedGameDataTableFileSize * 2);
u8* compressedBuffer = (singleAllocationCombinedBuffers.get() + 0);
u8* encryptedBufferWithIV = (singleAllocationCombinedBuffers.get() + MaxDecompressedGameDataTableFileSize);
u8* encryptedBuffer = (encryptedBufferWithIV + PeepoHappy::Crypto::Aes128IVSize);
u8* encryptedBuffer = (encryptedBufferWithIV + PeepoHappy::Crypto::AesIVSize);
const size_t compressedSize = PeepoHappy::Compression::Deflate(jsonFileContent.get(), jsonFileSize, compressedBuffer, MaxDecompressedGameDataTableFileSize);
const size_t alignedSize = PeepoHappy::Crypto::Align(compressedSize, PeepoHappy::Crypto::Aes128Alignment);
const size_t alignedSizeWithIV = (alignedSize + PeepoHappy::Crypto::Aes128IVSize);
const size_t alignedSize = PeepoHappy::Crypto::Align(compressedSize, PeepoHappy::Crypto::AesBlockAlignment);
const size_t alignedSizeWithIV = (alignedSize + PeepoHappy::Crypto::AesIVSize);
const size_t numberOfAlignmentBytesAdded = (alignedSize - compressedSize);
if (compressedSize <= 0)
{
@ -226,12 +265,20 @@ namespace TaikoSwitchDataTableDecryptor
else
{
assert(compressedSize < MaxDecompressedGameDataTableFileSize && "The compressed data could technically be larger... but that seems highly unlikely for plain text JSON");
assert((alignedSize + PeepoHappy::Crypto::Aes128IVSize) <= MaxDecompressedGameDataTableFileSize);
assert((alignedSize + PeepoHappy::Crypto::AesIVSize) <= MaxDecompressedGameDataTableFileSize);
constexpr PeepoHappy::Crypto::Aes128IVBytes dummyIV = { 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC };
constexpr PeepoHappy::Crypto::AesIVBytes dummyIV = { 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC };
memcpy(encryptedBufferWithIV, dummyIV.data(), dummyIV.size());
if (!PeepoHappy::Crypto::EncryptAes128Cbc(compressedBuffer, encryptedBuffer, alignedSize, keyUsedForInitialDecrpytion->Key, dummyIV))
#if 1 // HACK: Manually add PKCS7 padding (?)
if (keyUsedForInitialDecrpytion->KeyByteSize == keyUsedForInitialDecrpytion->Key256.size())
{
for (size_t i = 0; i < numberOfAlignmentBytesAdded; i++)
compressedBuffer[compressedSize + i] = static_cast<u8>(numberOfAlignmentBytesAdded);
}
#endif
if (!EncryptUsingNamedKey(*keyUsedForInitialDecrpytion, compressedBuffer, encryptedBuffer, alignedSize, dummyIV))
{
fprintf(stderr, "Failed to encrypt JSON file\n");
return EXIT_WIDEPEEPOSAD;
@ -272,7 +319,6 @@ namespace TaikoSwitchDataTableDecryptor
printf(" because of fixed size buffers used by the game during decompression.\n");
printf("\n");
printf("Credits:\n");
printf(" Programmed and reverse engineered by samyuu\n");
printf(" This program is licensed under the MIT License and makes use of the zlib library.\n");
printf(" The source code is available at " "https://github.com/samyuu/TaikoSwitchDataTableDecryptor" "\n");
printf("\n");
@ -280,9 +326,9 @@ namespace TaikoSwitchDataTableDecryptor
}
std::unique_ptr<u8[]> stringViewOwningIniFileContent = nullptr;
auto namedKeys = ReadAndParseEncrpytionKeysIniFile(stringViewOwningIniFileContent);
std::vector<NamedEncryptionKey> namedKeys = ReadAndParseEncrpytionKeysIniFile(stringViewOwningIniFileContent);
const auto inputFilePath = std::string_view(argv[1]);
const std::string_view inputFilePath = std::string_view(argv[1]);
if (PeepoHappy::Path::HasFileExtension(inputFilePath, ".bin"))
return ReadAndWriteEncryptedAndOrCompressedBinToJsonFile(inputFilePath, namedKeys);

View File

@ -260,7 +260,7 @@ namespace PeepoHappy
{
enum class Operation { Decrypt, Encrypt };
bool BCryptAes128Cbc(Operation operation, const u8* inData, size_t inDataSize, u8* outData, size_t outDataSize, u8* key, u8* iv)
bool BCryptAesCbc(Operation operation, const u8* inData, size_t inDataSize, u8* outData, size_t outDataSize, u8* key, size_t keySize, u8* iv)
{
bool successful = false;
::NTSTATUS status = {};
@ -281,12 +281,12 @@ namespace PeepoHappy
::BCRYPT_KEY_HANDLE symmetricKeyHandle = {};
auto keyObject = std::make_unique<u8[]>(keyObjectSize);
status = ::BCryptGenerateSymmetricKey(algorithmHandle, &symmetricKeyHandle, keyObject.get(), keyObjectSize, key, static_cast<ULONG>(Aes128KeySize), 0);
status = ::BCryptGenerateSymmetricKey(algorithmHandle, &symmetricKeyHandle, keyObject.get(), keyObjectSize, key, static_cast<ULONG>(keySize), 0);
if (NT_SUCCESS(status))
{
if (operation == Operation::Decrypt)
{
status = ::BCryptDecrypt(symmetricKeyHandle, const_cast<u8*>(inData), static_cast<ULONG>(inDataSize), nullptr, iv, static_cast<ULONG>(Aes128IVSize), outData, static_cast<ULONG>(outDataSize), &copiedDataSize, 0);
status = ::BCryptDecrypt(symmetricKeyHandle, const_cast<u8*>(inData), static_cast<ULONG>(inDataSize), nullptr, iv, static_cast<ULONG>(AesIVSize), outData, static_cast<ULONG>(outDataSize), &copiedDataSize, 0);
if (NT_SUCCESS(status))
successful = true;
else
@ -294,7 +294,7 @@ namespace PeepoHappy
}
else if (operation == Operation::Encrypt)
{
status = ::BCryptEncrypt(symmetricKeyHandle, const_cast<u8*>(inData), static_cast<ULONG>(inDataSize), nullptr, iv, static_cast<ULONG>(Aes128IVSize), outData, static_cast<ULONG>(outDataSize), &copiedDataSize, 0);
status = ::BCryptEncrypt(symmetricKeyHandle, const_cast<u8*>(inData), static_cast<ULONG>(inDataSize), nullptr, iv, static_cast<ULONG>(AesIVSize), outData, static_cast<ULONG>(outDataSize), &copiedDataSize, 0);
if (NT_SUCCESS(status))
successful = true;
else
@ -333,49 +333,76 @@ namespace PeepoHappy
return successful;
}
bool ParseHexByteString(std::string_view hexByteString, u8* outBytes, size_t outByteSize)
{
constexpr size_t hexDigitsPerByte = 2;
constexpr size_t byteBufferSize = 64;
assert(outByteSize < byteBufferSize);
char upperCaseHexChars[(byteBufferSize * hexDigitsPerByte) + sizeof('\0')] = {};
size_t hexCharsWrittenSoFar = 0;
for (size_t charIndex = 0; charIndex < hexByteString.size(); charIndex++)
{
if (ASCII::IsWhiteSpace(hexByteString[charIndex]))
continue;
const char upperCaseChar = ASCII::ToUpperCase(hexByteString[charIndex]);
upperCaseHexChars[hexCharsWrittenSoFar++] = ((upperCaseChar >= '0' && upperCaseChar <= '9') || (upperCaseChar >= 'A' && upperCaseChar <= 'F')) ? upperCaseChar : '0';
if (hexCharsWrittenSoFar >= std::size(upperCaseHexChars))
break;
}
for (size_t byteIndex = 0; byteIndex < outByteSize; byteIndex++)
{
auto upperCaseHexCharToNibble = [](char c) -> u8 { return (c >= '0' && c <= '9') ? (c - '0') : (c >= 'A' && c <= 'F') ? (0xA + (c - 'A')) : 0x0; };
u8 combinedByte = 0x00;
combinedByte |= (upperCaseHexCharToNibble(upperCaseHexChars[(byteIndex * hexDigitsPerByte) + 0]) << 4);
combinedByte |= (upperCaseHexCharToNibble(upperCaseHexChars[(byteIndex * hexDigitsPerByte) + 1]) << 0);
outBytes[byteIndex] = combinedByte;
}
return true;
}
}
bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, Aes128KeyBytes key, Aes128IVBytes iv)
bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, Aes128KeyBytes key, AesIVBytes iv)
{
return Detail::BCryptAes128Cbc(Detail::Operation::Decrypt, inEncryptedData, inOutDataSize, outDecryptedData, inOutDataSize, key.data(), iv.data());
return Detail::BCryptAesCbc(Detail::Operation::Decrypt, inEncryptedData, inOutDataSize, outDecryptedData, inOutDataSize, key.data(), key.size(), iv.data());
}
bool EncryptAes128Cbc(const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, Aes128KeyBytes key, Aes128IVBytes iv)
bool EncryptAes128Cbc(const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, Aes128KeyBytes key, AesIVBytes iv)
{
assert(Align(inOutDataSize, Aes128Alignment) == inOutDataSize);
return Detail::BCryptAes128Cbc(Detail::Operation::Encrypt, inDecryptedData, inOutDataSize, outEncryptedData, inOutDataSize, key.data(), iv.data());
assert(Align(inOutDataSize, AesBlockAlignment) == inOutDataSize);
return Detail::BCryptAesCbc(Detail::Operation::Encrypt, inDecryptedData, inOutDataSize, outEncryptedData, inOutDataSize, key.data(), key.size(), iv.data());
}
bool DecryptAes256Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, Aes256KeyBytes key, AesIVBytes iv)
{
return Detail::BCryptAesCbc(Detail::Operation::Decrypt, inEncryptedData, inOutDataSize, outDecryptedData, inOutDataSize, key.data(), key.size(), iv.data());
}
bool EncryptAes256Cbc(const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, Aes256KeyBytes key, AesIVBytes iv)
{
assert(Align(inOutDataSize, AesBlockAlignment) == inOutDataSize);
return Detail::BCryptAesCbc(Detail::Operation::Encrypt, inDecryptedData, inOutDataSize, outEncryptedData, inOutDataSize, key.data(), key.size(), iv.data());
}
Aes128KeyBytes ParseAes128KeyHexByteString(std::string_view hexByteString)
{
constexpr size_t hexDigitsPerByte = 2;
Aes128KeyBytes result = {};
Detail::ParseHexByteString(hexByteString, result.data(), result.size());
return result;
}
char upperCaseHexChars[(Aes128KeySize * hexDigitsPerByte) + sizeof('\0')] = {};
size_t hexCharsWrittenSoFar = 0;
for (size_t charIndex = 0; charIndex < hexByteString.size(); charIndex++)
{
if (ASCII::IsWhiteSpace(hexByteString[charIndex]))
continue;
const char upperCaseChar = ASCII::ToUpperCase(hexByteString[charIndex]);
upperCaseHexChars[hexCharsWrittenSoFar++] = ((upperCaseChar >= '0' && upperCaseChar <= '9') || (upperCaseChar >= 'A' && upperCaseChar <= 'F')) ? upperCaseChar : '0';
if (hexCharsWrittenSoFar >= std::size(upperCaseHexChars))
break;
}
Aes128KeyBytes resultKeyBytes = {};
for (size_t byteIndex = 0; byteIndex < resultKeyBytes.size(); byteIndex++)
{
auto upperCaseHexCharToNibble = [](char c) -> u8 { return (c >= '0' && c <= '9') ? (c - '0') : (c >= 'A' && c <= 'F') ? (0xA + (c - 'A')) : 0x0; };
u8 combinedByte = 0x00;
combinedByte |= (upperCaseHexCharToNibble(upperCaseHexChars[(byteIndex * hexDigitsPerByte) + 0]) << 4);
combinedByte |= (upperCaseHexCharToNibble(upperCaseHexChars[(byteIndex * hexDigitsPerByte) + 1]) << 0);
resultKeyBytes[byteIndex] = combinedByte;
}
return resultKeyBytes;
Aes256KeyBytes ParseAes256KeyHexByteString(std::string_view hexByteString)
{
Aes256KeyBytes result = {};
Detail::ParseHexByteString(hexByteString, result.data(), result.size());
return result;
}
}
@ -406,11 +433,15 @@ namespace PeepoHappy
const GZipHeader* header = reinterpret_cast<const GZipHeader*>(fileContent);
// NOTE: This is by no means comprehensive but should be enough for datatable files and (not falsely) detecting encrypted data
#if 1
return (header->Magic[0] == 0x1F && header->Magic[1] == 0x8B) &&
(header->CompressionMethod == Z_DEFLATED) &&
(header->Flags == 0) &&
(header->Timestamp == 0) &&
(header->ExtraFlags == 0);
#else // DEBUG: Less precise check for FArc testing
return (header->Magic[0] == 0x1F && header->Magic[1] == 0x8B) && (header->CompressionMethod == Z_DEFLATED);
#endif
}
bool Inflate(const u8* inCompressedData, size_t inDataSize, u8* outDecompressedData, size_t outDataSize)
@ -431,7 +462,7 @@ namespace PeepoHappy
const int inflateResult = inflate(&zStream, Z_FINISH);
// BUG: I remember there being some edge case where it would report "incorrect end" or something desprite having already decompressed everything correctly..?
// Don't really wanna risk falsely reporting an error here...
assert(inflateResult == Z_STREAM_END && zStream.msg == nullptr);
// assert(inflateResult == Z_STREAM_END && zStream.msg == nullptr);
const int endResult = inflateEnd(&zStream);
if (endResult != Z_OK)

View File

@ -94,18 +94,24 @@ namespace PeepoHappy
namespace Crypto
{
constexpr size_t Aes128KeySize = 16;
constexpr size_t Aes128IVSize = 16;
constexpr size_t Aes128Alignment = 16;
constexpr size_t Aes256KeySize = 32;
constexpr size_t AesIVSize = 16;
constexpr size_t AesBlockAlignment = 16;
using Aes128KeyBytes = std::array<u8, Aes128KeySize>;
using Aes128IVBytes = std::array<u8, Aes128IVSize>;
using Aes256KeyBytes = std::array<u8, Aes256KeySize>;
using AesIVBytes = std::array<u8, AesIVSize>;
constexpr size_t Align(size_t value, size_t alignment) { return (value + (alignment - 1)) & ~(alignment - 1); }
bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, Aes128KeyBytes key, Aes128IVBytes iv);
bool EncryptAes128Cbc(const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, Aes128KeyBytes key, Aes128IVBytes iv);
bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, Aes128KeyBytes key, AesIVBytes iv);
bool EncryptAes128Cbc(const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, Aes128KeyBytes key, AesIVBytes iv);
Aes128KeyBytes ParseAes128KeyHexByteString(std::string_view hexString);
bool DecryptAes256Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, Aes256KeyBytes key, AesIVBytes iv);
bool EncryptAes256Cbc(const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, Aes256KeyBytes key, AesIVBytes iv);
Aes128KeyBytes ParseAes128KeyHexByteString(std::string_view hexByteString);
Aes256KeyBytes ParseAes256KeyHexByteString(std::string_view hexByteString);
}
namespace Compression