diff --git a/CMakeLists.txt b/CMakeLists.txt index 67e0141..f562527 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(kgg-dec VERSION 0.6.0 LANGUAGES CXX) option(USE_WIN_SQLITE3 "Use Windows SQLite3 (MSVC Only)" ${MSVC}) +option(USE_WIN_CRYPTO "Use Windows Crypto API" ${WIN32}) add_subdirectory(third-party/aes) add_subdirectory(third-party/md5) + include(cmake/FindWinSQLite3.cmake) if (NOT WinSQLite3_Found) message("including sqlite3 to the build") @@ -42,3 +44,7 @@ else () endif () target_compile_definitions(kgg-dec PRIVATE NOMINMAX) target_compile_definitions(kgg-dec PRIVATE KGGDEC_PROJECT_VERSION="${PROJECT_VERSION}") + +if (USE_WIN_CRYPTO) + target_compile_definitions(kgg-dec PRIVATE USE_WIN_CRYPTO=1) +endif () \ No newline at end of file diff --git a/src/infra/infra.cpp b/src/infra/infra.cpp index e6d0b4b..f8f9675 100644 --- a/src/infra/infra.cpp +++ b/src/infra/infra.cpp @@ -95,10 +95,12 @@ int load_db(std::vector& db_data, const std::filesystem::path& db_path) if (std::equal(kSQLiteDatabaseHeader.cbegin(), kSQLiteDatabaseHeader.cend(), p_page)) { ifs_db.read(reinterpret_cast(p_page + kPageSize), static_cast(db_size - kPageSize)); + AES_cleanup(&ctx_aes); return SQLITE_OK; // no encryption } if (!is_valid_page_1_header(p_page)) { + AES_cleanup(&ctx_aes); db_data.clear(); return SQLITE_CORRUPT; // header validation failed } @@ -114,6 +116,7 @@ int load_db(std::vector& db_data, const std::filesystem::path& db_path) } else { AES_CBC_decrypt_buffer(&ctx_aes, p_page, kPageSize); } + AES_cleanup(&ctx_aes); } return SQLITE_OK; diff --git a/third-party/aes/CMakeLists.txt b/third-party/aes/CMakeLists.txt index 9f024e9..f619c2f 100644 --- a/third-party/aes/CMakeLists.txt +++ b/third-party/aes/CMakeLists.txt @@ -5,10 +5,22 @@ project(libaes VERSION 0.0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Tiny AES in C (https://github.com/kokke/tiny-AES-c/) is licensed under the Unlicense license. -add_library(libaes STATIC aes.cpp) +set(SOURCES) +if (USE_WIN_CRYPTO) + list(APPEND SOURCES aes_win32.cpp) +else () + # Tiny AES in C (https://github.com/kokke/tiny-AES-c/) + # is licensed under the Unlicense license. + list(APPEND SOURCES aes.cpp) +endif () + +add_library(libaes STATIC ${SOURCES}) target_include_directories(libaes PUBLIC "$" "$" ) +if (USE_WIN_CRYPTO) + target_link_libraries(libaes PRIVATE bcrypt) + target_compile_definitions(libaes PRIVATE USE_WIN_CRYPTO=1) +endif () diff --git a/third-party/aes/aes.cpp b/third-party/aes/aes.cpp index adf1db9..77df885 100644 --- a/third-party/aes/aes.cpp +++ b/third-party/aes/aes.cpp @@ -129,9 +129,10 @@ void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) { } } -void AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) { +bool AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) { KeyExpansion(ctx->RoundKey, key); memcpy(ctx->Iv, iv, kBlockLen); + return true; } // This function adds the round key to state. @@ -331,7 +332,7 @@ inline void XorWithIv(uint8_t* buf, const uint8_t* Iv) { } } -void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { +size_t AES_CBC_encrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length) { uint8_t* Iv = ctx->Iv; for (size_t i = 0; i < length; i += kBlockLen) { XorWithIv(buf, Iv); @@ -341,9 +342,10 @@ void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { } /* store Iv in ctx for next call */ memcpy(ctx->Iv, Iv, kBlockLen); + return length; } -void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { +size_t AES_CBC_decrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length) { for (size_t i = 0; i < length; i += kBlockLen) { uint8_t storeNextIv[kBlockLen]; memcpy(storeNextIv, buf, kBlockLen); @@ -352,6 +354,7 @@ void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { memcpy(ctx->Iv, storeNextIv, kBlockLen); buf += kBlockLen; } + return length; } } // namespace AES diff --git a/third-party/aes/aes.h b/third-party/aes/aes.h index c09e151..932c8b1 100644 --- a/third-party/aes/aes.h +++ b/third-party/aes/aes.h @@ -2,6 +2,12 @@ #include +#if USE_WIN_CRYPTO +#include + +#include +#endif + namespace AES { constexpr size_t kKeyLen = 16; // Key length in bytes @@ -9,17 +15,29 @@ constexpr size_t kKeyExpansionSize = 176; constexpr size_t kBlockLen = 16; // Block length in bytes - AES is 128b block only struct AES_ctx { +#if USE_WIN_CRYPTO + BCRYPT_ALG_HANDLE hAlg; + BCRYPT_KEY_HANDLE hKey; + uint8_t iv[0x10]; +#else uint8_t RoundKey[kKeyExpansionSize]; uint8_t Iv[16]; +#endif }; -void AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +bool AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); // buffer size MUST be mutile of AES_BLOCKLEN; // Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme // NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() // no IV should ever be reused with the same key -void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); -void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +size_t AES_CBC_encrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length); +size_t AES_CBC_decrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length); + +#if USE_WIN_CRYPTO +bool AES_cleanup(AES_ctx* ctx); +#else +inline bool AES_cleanup(AES_ctx* ctx) {return true;} +#endif } // namespace AES diff --git a/third-party/aes/aes_win32.cpp b/third-party/aes/aes_win32.cpp new file mode 100644 index 0000000..0a32003 --- /dev/null +++ b/third-party/aes/aes_win32.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +#include "aes.h" + +namespace AES { +// ReSharper disable CppCStyleCast + +bool AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) { + memset(ctx, 0, sizeof(AES_ctx)); + + if (BCryptOpenAlgorithmProvider(&ctx->hAlg, BCRYPT_AES_ALGORITHM, nullptr, 0) != 0) { + return false; + } + + if (BCryptSetProperty(ctx->hAlg, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), + 0) != 0) { + BCryptCloseAlgorithmProvider(ctx->hAlg, 0); + return false; + } + + if (BCryptGenerateSymmetricKey(ctx->hAlg, &ctx->hKey, nullptr, 0, (PUCHAR)key, 0x10, 0) != 0) { + BCryptCloseAlgorithmProvider(ctx->hAlg, 0); + return false; + } + + std::copy_n(iv, 0x10, ctx->iv); + return true; +} + +// buffer size MUST be mutile of AES_BLOCKLEN; +// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +size_t AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { + // not implemented + return 0; +} +size_t AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { + ULONG cbData{0}; + const auto len = static_cast(length); + if (BCryptDecrypt(ctx->hKey, buf, len, nullptr, ctx->iv, sizeof(ctx->iv), buf, len, &cbData, 0) != 0) { + assert(false && "BCryptDecrypt failed"); + return 0; + } + + assert(cbData == len && "AES_CBC_decrypt_buffer: cbData != length"); + return cbData; +} + +bool AES_cleanup(AES_ctx* ctx) { + BCryptDestroyKey(ctx->hKey); + BCryptCloseAlgorithmProvider(ctx->hAlg, 0); + memset(ctx, 0, sizeof(AES_ctx)); + return true; +} + +} // namespace AES diff --git a/third-party/md5/CMakeLists.txt b/third-party/md5/CMakeLists.txt index eb87679..e61698d 100644 --- a/third-party/md5/CMakeLists.txt +++ b/third-party/md5/CMakeLists.txt @@ -5,11 +5,23 @@ project(md5 VERSION 0.0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm": -# https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c -add_library(libmd5 STATIC md5.cpp) +set(SOURCES) +if (USE_WIN_CRYPTO) + list(APPEND SOURCES md5_win32.cpp) +else () + # Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm": + # https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c + list(APPEND SOURCES md5.cpp) +endif () + +add_library(libmd5 STATIC ${SOURCES}) target_include_directories(libmd5 PUBLIC "$" "$" ) + +if (USE_WIN_CRYPTO) + target_link_libraries(libmd5 PRIVATE crypt32) + target_compile_definitions(libmd5 PRIVATE USE_WIN_CRYPTO=1) +endif () diff --git a/third-party/md5/md5.h b/third-party/md5/md5.h index 0ebf3bc..872e6e4 100644 --- a/third-party/md5/md5.h +++ b/third-party/md5/md5.h @@ -1,7 +1,10 @@ #pragma once -// Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm": -// src: https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c +#if USE_WIN_CRYPTO +#include + +#include +#endif #include @@ -9,15 +12,23 @@ #define MD5_DIGEST_LENGTH 16 #define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1) -/* MD5 context. */ struct MD5_CTX { +#if USE_WIN_CRYPTO + HCRYPTPROV hProv; + HCRYPTHASH hHash; +#else uint64_t count; /* number of bits, modulo 2^64 (lsb first) */ uint32_t state[4]; /* state (ABCD) */ unsigned char buffer[64]; /* input buffer */ +#endif }; +#if USE_WIN_CRYPTO +bool md5_init(MD5_CTX* context); +bool md5_cleanup(MD5_CTX* ctx); +#else /* MD5 initialization. Begins an MD5 operation, writing a new context. */ -inline void md5_init(MD5_CTX* context) { +inline bool md5_init(MD5_CTX* context) { context->count = 0; /* Load magic initialization constants. */ @@ -25,22 +36,31 @@ inline void md5_init(MD5_CTX* context) { context->state[1] = 0xefcdab89; context->state[2] = 0x98badcfe; context->state[3] = 0x10325476; + + return true; } +inline bool md5_cleanup(MD5_CTX* ctx) { + return true; +} +#endif + void md5_update(MD5_CTX* ctx, const uint8_t* in, size_t len); void md5_final(MD5_CTX* ctx, uint8_t* digest); inline void md5(uint8_t* digest, const uint8_t* in, const size_t len) { - MD5_CTX ctx; + MD5_CTX ctx{}; md5_init(&ctx); md5_update(&ctx, in, len); md5_final(&ctx, digest); + md5_cleanup(&ctx); } inline void md5(uint8_t* digest, const uint8_t* in, const size_t len, const uint8_t* in2, size_t len2) { - MD5_CTX ctx; + MD5_CTX ctx{}; md5_init(&ctx); md5_update(&ctx, in, len); md5_update(&ctx, in2, len2); md5_final(&ctx, digest); + md5_cleanup(&ctx); } diff --git a/third-party/md5/md5_win32.cpp b/third-party/md5/md5_win32.cpp new file mode 100644 index 0000000..f504f55 --- /dev/null +++ b/third-party/md5/md5_win32.cpp @@ -0,0 +1,35 @@ +#include "md5.h" + +bool md5_init(MD5_CTX* ctx) { + if (!CryptAcquireContext(&ctx->hProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + return false; + } + + // Create the hash object + if (!CryptCreateHash(ctx->hProv, CALG_MD5, 0, 0, &ctx->hHash)) { + CryptReleaseContext(ctx->hProv, 0); + return false; + } + + return true; +} + +void md5_update(MD5_CTX* ctx, const uint8_t* in, size_t len) { + CryptHashData(ctx->hHash, in, static_cast(len), 0); +} + +void md5_final(MD5_CTX* ctx, uint8_t* digest) { + DWORD dataLen = MD5_DIGEST_LENGTH; + CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &dataLen, 0); + CryptDestroyHash(ctx->hHash); + ctx->hHash = 0; +} + +bool md5_cleanup(MD5_CTX* ctx) { + if (ctx->hHash) { + CryptDestroyHash(ctx->hHash); + } + CryptReleaseContext(ctx->hProv, 0); + memset(ctx, 0, sizeof(MD5_CTX)); + return true; +}