From 8a7bdfff4072c81f012501545e93affb0d98acc2 Mon Sep 17 00:00:00 2001 From: samyuu Date: Thu, 27 May 2021 10:54:14 +0200 Subject: [PATCH] Implement output file encryption --- .../src/EntryPoint.cpp | 74 ++++++++++++---- .../src/Utilities.cpp | 87 ++++++++++++------- TaikoSwitchDataTableDecryptor/src/Utilities.h | 8 +- 3 files changed, 120 insertions(+), 49 deletions(-) diff --git a/TaikoSwitchDataTableDecryptor/src/EntryPoint.cpp b/TaikoSwitchDataTableDecryptor/src/EntryPoint.cpp index dc116bd..a41e48f 100644 --- a/TaikoSwitchDataTableDecryptor/src/EntryPoint.cpp +++ b/TaikoSwitchDataTableDecryptor/src/EntryPoint.cpp @@ -30,7 +30,7 @@ namespace TaikoSwitchDataTableDecryptor auto decompressedBuffer = std::make_unique(MaxDecompressedGameDataTableFileSize); if (!PeepoHappy::Compression::Inflate(compressedData, compressedDataSize, decompressedBuffer.get(), MaxDecompressedGameDataTableFileSize)) { - fprintf(stderr, "Failed to decompress input file"); + fprintf(stderr, "Failed to decompress input file\n"); return false; } @@ -39,7 +39,7 @@ namespace TaikoSwitchDataTableDecryptor if (!PeepoHappy::IO::WriteEntireFile(jsonOutputFilePath, reinterpret_cast(jsonString.data()), jsonString.size())) { - fprintf(stderr, "Failed to write JSON output file"); + fprintf(stderr, "Failed to write JSON output file\n"); return false; } @@ -49,9 +49,20 @@ namespace TaikoSwitchDataTableDecryptor int ReadAndWriteEncryptedAndOrCompressedBinToJsonFile(std::string_view encryptedAndOrCompressedInputFilePath, std::string_view jsonOutputFilePath) { const auto[fileContent, fileSize] = PeepoHappy::IO::ReadEntireFile(encryptedAndOrCompressedInputFilePath); - if (fileSize <= 8) + if (fileContent == nullptr) { - fprintf(stderr, "Unexpected end of file"); + fprintf(stderr, "Failed to read input file\n"); + return EXIT_WIDEPEEPOSAD; + } + else if (fileSize <= 8) + { + fprintf(stderr, "Unexpected end of file\n"); + return EXIT_WIDEPEEPOSAD; + } + + if (fileSize >= MaxDecompressedGameDataTableFileSize) + { + fprintf(stderr, "Input file too large. DataTable files are limited to %zu bytes\n", MaxDecompressedGameDataTableFileSize); return EXIT_WIDEPEEPOSAD; } @@ -62,7 +73,7 @@ namespace TaikoSwitchDataTableDecryptor } else { - std::array iv = {}; + std::array iv = {}; memcpy(iv.data(), fileContent.get(), iv.size()); const size_t fileSizeWithoutIV = (fileSize - iv.size()); @@ -70,7 +81,7 @@ namespace TaikoSwitchDataTableDecryptor auto decryptedBuffer = std::make_unique(fileSizeWithoutIV); if (!PeepoHappy::Crypto::DecryptAes128Cbc(fileContentWithoutIV, decryptedBuffer.get(), fileSizeWithoutIV, DataTableAesKey, iv)) - fprintf(stderr, "Failed to decrypt input file"); + fprintf(stderr, "Failed to decrypt input file\n"); if (!DecompressAndWriteDataTableJsonFile(decryptedBuffer.get(), fileSizeWithoutIV, jsonOutputFilePath)) return EXIT_WIDEPEEPOSAD; @@ -79,22 +90,50 @@ namespace TaikoSwitchDataTableDecryptor return EXIT_WIDEPEEPOHAPPY; } - int ReadAndWriteJsonFileToCompressedBin(std::string_view jsonInputFilePath, std::string_view compressedOutputFilePath) + int ReadAndWriteJsonFileToCompressedAndEncryptedBin(std::string_view jsonInputFilePath, std::string_view compressedAndEncryptedOutputFilePath) { - auto compressedBuffer = std::make_unique(MaxDecompressedGameDataTableFileSize); - const auto[fileContent, fileSize] = PeepoHappy::IO::ReadEntireFile(jsonInputFilePath); - const auto compressedSize = PeepoHappy::Compression::Deflate(fileContent.get(), fileSize, compressedBuffer.get(), MaxDecompressedGameDataTableFileSize); - - if (compressedSize < 0) + if (fileContent == nullptr) { - fprintf(stderr, "Failed to compress JSON file"); + fprintf(stderr, "Failed to read input file\n"); + return EXIT_WIDEPEEPOSAD; + } + else if ((fileSize + PeepoHappy::Crypto::Aes128IVSize) >= MaxDecompressedGameDataTableFileSize) + { + fprintf(stderr, "Input file too large. DataTable files are limited to %zu bytes\n", MaxDecompressedGameDataTableFileSize); return EXIT_WIDEPEEPOSAD; } - if (!PeepoHappy::IO::WriteEntireFile(compressedOutputFilePath, compressedBuffer.get(), compressedSize)) + auto singleAllocationCombinedBuffers = std::make_unique(MaxDecompressedGameDataTableFileSize * 2); + u8* compressedBuffer = (singleAllocationCombinedBuffers.get() + 0); + u8* encryptedBufferWithIV = (singleAllocationCombinedBuffers.get() + MaxDecompressedGameDataTableFileSize); + u8* encryptedBuffer = (encryptedBufferWithIV + PeepoHappy::Crypto::Aes128IVSize); + + const size_t compressedSize = PeepoHappy::Compression::Deflate(fileContent.get(), fileSize, compressedBuffer, MaxDecompressedGameDataTableFileSize); + const size_t alignedSize = PeepoHappy::Crypto::Align(compressedSize, PeepoHappy::Crypto::Aes128Alignment); + const size_t alignedSizeWithIV = (alignedSize + PeepoHappy::Crypto::Aes128IVSize); + + if (compressedSize <= 0) { - fprintf(stderr, "Failed to write compressed output file"); + fprintf(stderr, "Failed to compress JSON file\n"); + return EXIT_WIDEPEEPOSAD; + } + + 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); + + constexpr std::array 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, DataTableAesKey, dummyIV)) + { + fprintf(stderr, "Failed to encrypt JSON file\n"); + return EXIT_WIDEPEEPOSAD; + } + + if (!PeepoHappy::IO::WriteEntireFile(compressedAndEncryptedOutputFilePath, encryptedBufferWithIV, alignedSizeWithIV)) + { + fprintf(stderr, "Failed to write encrypted output file\n"); return EXIT_WIDEPEEPOSAD; } @@ -108,7 +147,7 @@ namespace TaikoSwitchDataTableDecryptor if (argc <= 1) { printf("Description:\n"); - printf(" A program to decrypt, decompress and recompress DataTable JSON files\n"); + printf(" A program to decrypt+decompress and compress+encrypt DataTable JSON files\n"); printf(" used by the Switch release of Taiko no Tatsujin (and possibly more)\n"); printf("\n"); printf("Usage:\n"); @@ -131,9 +170,8 @@ namespace TaikoSwitchDataTableDecryptor if (PeepoHappy::IO::HasFileExtension(inputPath, ".bin")) return ReadAndWriteEncryptedAndOrCompressedBinToJsonFile(inputPath, PeepoHappy::IO::ChangeFileExtension(inputPath, ".json")); - // TODO: Actually.. it doesn't look like there are game encryption checks in place so this will have to re-encrypt too (?) if (PeepoHappy::IO::HasFileExtension(inputPath, ".json")) - return ReadAndWriteJsonFileToCompressedBin(inputPath, PeepoHappy::IO::ChangeFileExtension(inputPath, ".bin")); + return ReadAndWriteJsonFileToCompressedAndEncryptedBin(inputPath, PeepoHappy::IO::ChangeFileExtension(inputPath, ".bin")); fprintf(stderr, "Unexpected file extension\n"); return EXIT_WIDEPEEPOSAD; diff --git a/TaikoSwitchDataTableDecryptor/src/Utilities.cpp b/TaikoSwitchDataTableDecryptor/src/Utilities.cpp index 0845084..6408f8c 100644 --- a/TaikoSwitchDataTableDecryptor/src/Utilities.cpp +++ b/TaikoSwitchDataTableDecryptor/src/Utilities.cpp @@ -172,67 +172,94 @@ namespace PeepoHappy namespace Crypto { - bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, std::array key, std::array iv) + namespace Detail { - bool successful = false; - ::NTSTATUS status = {}; - ::BCRYPT_ALG_HANDLE algorithmHandle = {}; + enum class Operation { Decrypt, Encrypt }; - status = ::BCryptOpenAlgorithmProvider(&algorithmHandle, BCRYPT_AES_ALGORITHM, nullptr, 0); - if (NT_SUCCESS(status)) + bool BCryptAes128Cbc(Operation operation, const u8* inData, size_t inDataSize, u8* outData, size_t outDataSize, u8* key, u8* iv) { - status = ::BCryptSetProperty(algorithmHandle, BCRYPT_CHAINING_MODE, reinterpret_cast(const_cast(BCRYPT_CHAIN_MODE_CBC)), sizeof(BCRYPT_CHAIN_MODE_CBC), 0); + bool successful = false; + ::NTSTATUS status = {}; + ::BCRYPT_ALG_HANDLE algorithmHandle = {}; + + status = ::BCryptOpenAlgorithmProvider(&algorithmHandle, BCRYPT_AES_ALGORITHM, nullptr, 0); if (NT_SUCCESS(status)) { - ULONG keyObjectSize = {}; - ULONG copiedDataSize = {}; - - status = ::BCryptGetProperty(algorithmHandle, BCRYPT_OBJECT_LENGTH, reinterpret_cast(&keyObjectSize), sizeof(ULONG), &copiedDataSize, 0); + status = ::BCryptSetProperty(algorithmHandle, BCRYPT_CHAINING_MODE, reinterpret_cast(const_cast(BCRYPT_CHAIN_MODE_CBC)), sizeof(BCRYPT_CHAIN_MODE_CBC), 0); if (NT_SUCCESS(status)) { - ::BCRYPT_KEY_HANDLE symmetricKeyHandle = {}; - auto keyObject = std::make_unique(keyObjectSize); + ULONG keyObjectSize = {}; + ULONG copiedDataSize = {}; - status = ::BCryptGenerateSymmetricKey(algorithmHandle, &symmetricKeyHandle, keyObject.get(), keyObjectSize, key.data(), static_cast(key.size()), 0); + status = ::BCryptGetProperty(algorithmHandle, BCRYPT_OBJECT_LENGTH, reinterpret_cast(&keyObjectSize), sizeof(ULONG), &copiedDataSize, 0); if (NT_SUCCESS(status)) { - status = ::BCryptDecrypt(symmetricKeyHandle, const_cast(inEncryptedData), static_cast(inOutDataSize), nullptr, iv.data(), static_cast(iv.size()), outDecryptedData, static_cast(inOutDataSize), &copiedDataSize, 0); + ::BCRYPT_KEY_HANDLE symmetricKeyHandle = {}; + auto keyObject = std::make_unique(keyObjectSize); + + status = ::BCryptGenerateSymmetricKey(algorithmHandle, &symmetricKeyHandle, keyObject.get(), keyObjectSize, key, static_cast(Aes128KeySize), 0); if (NT_SUCCESS(status)) { - successful = true; + if (operation == Operation::Decrypt) + { + status = ::BCryptDecrypt(symmetricKeyHandle, const_cast(inData), static_cast(inDataSize), nullptr, iv, static_cast(Aes128IVSize), outData, static_cast(outDataSize), &copiedDataSize, 0); + if (NT_SUCCESS(status)) + successful = true; + else + fprintf(stderr, "BCryptDecrypt() failed with 0x%X\n", status); + } + else if (operation == Operation::Encrypt) + { + status = ::BCryptEncrypt(symmetricKeyHandle, const_cast(inData), static_cast(inDataSize), nullptr, iv, static_cast(Aes128IVSize), outData, static_cast(outDataSize), &copiedDataSize, 0); + if (NT_SUCCESS(status)) + successful = true; + else + fprintf(stderr, "BCryptEncrypt() failed with 0x%X\n", status); + } + else + { + assert(false); + } + + if (symmetricKeyHandle) + ::BCryptDestroyKey(symmetricKeyHandle); } else { - fprintf(stderr, "BCryptDecrypt() failed with 0x%X", status); + fprintf(stderr, "BCryptGenerateSymmetricKey() failed with 0x%X\n", status); } - - if (symmetricKeyHandle) - ::BCryptDestroyKey(symmetricKeyHandle); } else { - fprintf(stderr, "BCryptGenerateSymmetricKey() failed with 0x%X", status); + fprintf(stderr, "BCryptGetProperty(BCRYPT_OBJECT_LENGTH) failed with 0x%X\n", status); } } else { - fprintf(stderr, "BCryptGetProperty(BCRYPT_OBJECT_LENGTH) failed with 0x%X", status); + fprintf(stderr, "BCryptSetProperty(BCRYPT_CHAINING_MODE) failed with 0x%X\n", status); } + + if (algorithmHandle) + ::BCryptCloseAlgorithmProvider(algorithmHandle, 0); } else { - fprintf(stderr, "BCryptSetProperty(BCRYPT_CHAINING_MODE) failed with 0x%X", status); + fprintf(stderr, "BCryptOpenAlgorithmProvider(BCRYPT_AES_ALGORITHM) failed with 0x%X\n", status); } - if (algorithmHandle) - ::BCryptCloseAlgorithmProvider(algorithmHandle, 0); - } - else - { - fprintf(stderr, "BCryptOpenAlgorithmProvider(BCRYPT_AES_ALGORITHM) failed with 0x%X", status); + return successful; } + } - return successful; + bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, std::array key, std::array iv) + { + return Detail::BCryptAes128Cbc(Detail::Operation::Decrypt, inEncryptedData, inOutDataSize, outDecryptedData, inOutDataSize, key.data(), iv.data()); + } + + bool EncryptAes128Cbc(const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, std::array key, std::array iv) + { + assert(Align(inOutDataSize, Aes128Alignment) == inOutDataSize); + return Detail::BCryptAes128Cbc(Detail::Operation::Encrypt, inDecryptedData, inOutDataSize, outEncryptedData, inOutDataSize, key.data(), iv.data()); } } diff --git a/TaikoSwitchDataTableDecryptor/src/Utilities.h b/TaikoSwitchDataTableDecryptor/src/Utilities.h index de5dd49..8e26de6 100644 --- a/TaikoSwitchDataTableDecryptor/src/Utilities.h +++ b/TaikoSwitchDataTableDecryptor/src/Utilities.h @@ -47,7 +47,13 @@ namespace PeepoHappy namespace Crypto { constexpr size_t Aes128KeySize = 16; - bool DecryptAes128Cbc(const u8* inEncryptedData, u8* outDecryptedData, size_t inOutDataSize, std::array key, std::array iv); + constexpr size_t Aes128IVSize = 16; + constexpr size_t Aes128Alignment = 16; + + 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, std::array key, std::array iv); + bool EncryptAes128Cbc(const u8* inDecryptedData, u8* outEncryptedData, size_t inOutDataSize, std::array key, std::array iv); } namespace Compression