diff --git a/Source/codec.cpp b/Source/codec.cpp index d8e929419..c175a7943 100644 --- a/Source/codec.cpp +++ b/Source/codec.cpp @@ -4,6 +4,7 @@ * Implementation of save game encryption algorithm. */ +#include #include #include "appfat.h" @@ -21,18 +22,16 @@ struct CodecSignature { uint16_t unused; }; -#define BlockSize 64 +// https://stackoverflow.com/a/45172360 - helper to make up for not having an implicit initializer for std::byte +template +std::array make_bytes(Ts &&...args) noexcept +{ + return { byte(std::forward(args))... }; +} void CodecInitKey(const char *pszPassword) { - byte key[136]; // last 64 bytes are the SHA1 - uint32_t randState = 0x7058; - for (auto ¬ch : key) { - randState = randState * 214013 + 2531011; - notch = static_cast(randState >> 16); // Downcasting to byte keeps the 2 least-significant bytes - } - - byte pw[64]; // Repeat password until 64 char long + byte pw[BlockSize]; // Repeat password until 64 char long std::size_t j = 0; for (std::size_t i = 0; i < sizeof(pw); i++, j++) { if (pszPassword[j] == '\0') @@ -44,21 +43,31 @@ void CodecInitKey(const char *pszPassword) SHA1Reset(0); SHA1Calculate(0, pw, digest); SHA1Clear(); + + // declaring key as a std::array to make the initialization easier, otherwise we would need to explicitly + // declare every value as a byte on platforms that use std::byte. + std::array key = make_bytes( // clang-format off + 0xbf, 0x2f, 0x63, 0xad, 0xd0, 0x56, 0x27, 0xf7, 0x6e, 0x43, 0x47, 0x27, 0x70, 0xc7, 0x5b, 0x42, + 0x58, 0xac, 0x1e, 0xea, 0xca, 0x50, 0x7d, 0x28, 0x43, 0x93, 0xee, 0x68, 0x07, 0xf3, 0x03, 0xc5, + 0x5b, 0xf6, 0x3f, 0x87, 0xf5, 0xc9, 0x28, 0xea, 0xb1, 0x26, 0x9d, 0x22, 0x85, 0x7a, 0x6a, 0x9b, + 0xb7, 0x48, 0x1a, 0x85, 0x4d, 0xc8, 0x0d, 0x90, 0xc6, 0xd5, 0xca, 0xf4, 0x07, 0x06, 0x95, 0xb5 + ); // clang-format on + for (std::size_t i = 0; i < sizeof(key); i++) - key[i] ^= digest[i % SHA1HashSize]; + key[i] ^= digest[(i + 12) % SHA1HashSize]; memset(pw, 0, sizeof(pw)); memset(digest, 0, sizeof(digest)); for (int n = 0; n < 3; ++n) { SHA1Reset(n); - SHA1Calculate(n, &key[72], nullptr); + SHA1Calculate(n, key.data(), nullptr); } - memset(key, 0, sizeof(key)); + memset(key.data(), 0, sizeof(key)); } } // namespace std::size_t codec_decode(byte *pbSrcDst, std::size_t size, const char *pszPassword) { - byte buf[128]; + byte buf[BlockSize]; byte dst[SHA1HashSize]; CodecInitKey(pszPassword); @@ -67,7 +76,7 @@ std::size_t codec_decode(byte *pbSrcDst, std::size_t size, const char *pszPasswo size -= sizeof(CodecSignature); if (size % BlockSize != 0) return 0; - for (int i = size; i != 0; pbSrcDst += BlockSize, i -= BlockSize) { + for (auto i = size; i != 0; pbSrcDst += BlockSize, i -= BlockSize) { memcpy(buf, pbSrcDst, BlockSize); SHA1Result(0, dst); for (int j = 0; j < BlockSize; j++) { @@ -107,7 +116,7 @@ std::size_t codec_get_encoded_len(std::size_t dwSrcBytes) void codec_encode(byte *pbSrcDst, std::size_t size, std::size_t size64, const char *pszPassword) { - byte buf[128]; + byte buf[BlockSize]; byte tmp[SHA1HashSize]; byte dst[SHA1HashSize]; @@ -115,9 +124,9 @@ void codec_encode(byte *pbSrcDst, std::size_t size, std::size_t size64, const ch app_fatal("Invalid encode parameters"); CodecInitKey(pszPassword); - uint16_t lastChunk = 0; + size_t lastChunk = 0; while (size != 0) { - uint16_t chunk = size < BlockSize ? size : BlockSize; + size_t chunk = size < BlockSize ? size : BlockSize; memcpy(buf, pbSrcDst, chunk); if (chunk < BlockSize) memset(buf + chunk, 0, BlockSize - chunk); @@ -138,7 +147,7 @@ void codec_encode(byte *pbSrcDst, std::size_t size, std::size_t size64, const ch sig->error = 0; sig->unused = 0; sig->checksum = *reinterpret_cast(tmp); - sig->lastChunkSize = lastChunk; + sig->lastChunkSize = static_cast(lastChunk); // lastChunk is at most 64 so will always fit in an 8 bit var SHA1Clear(); } diff --git a/Source/sha.cpp b/Source/sha.cpp index f42209845..53ec126ce 100644 --- a/Source/sha.cpp +++ b/Source/sha.cpp @@ -5,8 +5,8 @@ */ #include "sha.h" -#include #include +#include #include "appfat.h" @@ -17,28 +17,30 @@ namespace devilution { namespace { +struct SHA1Context { + uint32_t state[SHA1HashSize / sizeof(uint32_t)]; + uint32_t buffer[BlockSize / sizeof(uint32_t)]; +}; + SHA1Context sgSHA1[3]; /** * Diablo-"SHA1" circular left shift, portable version. */ -uint32_t SHA1CircularShift(uint32_t bits, uint32_t word) +uint32_t SHA1CircularShift(uint32_t word, size_t bits) { assert(bits < 32); assert(bits > 0); - if ((word & 0x80000000) != 0) { - //moving this part to a separate volatile variable fixes saves in x64-release build in visual studio 2017 - volatile uint32_t tmp = ((~word) >> (32 - bits)); - return (word << bits) | (~tmp); - } + // The SHA-like algorithm as originally implemented treated word as a signed value and used arithmetic right shifts + // (sign-extending). This results in the high 32-`bits` bits being set to 1. + if ((word & (1 << 31)) != 0) + return (0xFFFFFFFF << bits) | (word >> (32 - bits)); return (word << bits) | (word >> (32 - bits)); } void SHA1Init(SHA1Context *context) { - context->count[0] = 0; - context->count[1] = 0; context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; @@ -64,37 +66,37 @@ void SHA1ProcessMessageBlock(SHA1Context *context) std::uint32_t e = context->state[4]; for (int i = 0; i < 20; i++) { - std::uint32_t temp = SHA1CircularShift(5, a) + ((b & c) | ((~b) & d)) + e + w[i] + 0x5A827999; + std::uint32_t temp = SHA1CircularShift(a, 5) + ((b & c) | ((~b) & d)) + e + w[i] + 0x5A827999; e = d; d = c; - c = SHA1CircularShift(30, b); + c = SHA1CircularShift(b, 30); b = a; a = temp; } for (int i = 20; i < 40; i++) { - std::uint32_t temp = SHA1CircularShift(5, a) + (b ^ c ^ d) + e + w[i] + 0x6ED9EBA1; + std::uint32_t temp = SHA1CircularShift(a, 5) + (b ^ c ^ d) + e + w[i] + 0x6ED9EBA1; e = d; d = c; - c = SHA1CircularShift(30, b); + c = SHA1CircularShift(b, 30); b = a; a = temp; } for (int i = 40; i < 60; i++) { - std::uint32_t temp = SHA1CircularShift(5, a) + ((b & c) | (b & d) | (c & d)) + e + w[i] + 0x8F1BBCDC; + std::uint32_t temp = SHA1CircularShift(a, 5) + ((b & c) | (b & d) | (c & d)) + e + w[i] + 0x8F1BBCDC; e = d; d = c; - c = SHA1CircularShift(30, b); + c = SHA1CircularShift(b, 30); b = a; a = temp; } for (int i = 60; i < 80; i++) { - std::uint32_t temp = SHA1CircularShift(5, a) + (b ^ c ^ d) + e + w[i] + 0xCA62C1D6; + std::uint32_t temp = SHA1CircularShift(a, 5) + (b ^ c ^ d) + e + w[i] + 0xCA62C1D6; e = d; d = c; - c = SHA1CircularShift(30, b); + c = SHA1CircularShift(b, 30); b = a; a = temp; } @@ -106,19 +108,12 @@ void SHA1ProcessMessageBlock(SHA1Context *context) context->state[4] += e; } -void SHA1Input(SHA1Context *context, const byte *messageArray, std::uint32_t len) +void SHA1Input(SHA1Context *context, const byte *messageArray, std::size_t len) { - std::uint32_t count = context->count[0] + 8 * len; - if (count < context->count[0]) - context->count[1]++; - - context->count[0] = count; - context->count[1] += len >> 29; - - for (int i = len; i >= 64; i -= 64) { - memcpy(context->buffer, messageArray, sizeof(context->buffer)); + for (auto i = len / BlockSize; i != 0; i--) { + memcpy(context->buffer, messageArray, BlockSize); SHA1ProcessMessageBlock(context); - messageArray += 64; + messageArray += BlockSize; } } @@ -131,9 +126,7 @@ void SHA1Clear() void SHA1Result(int n, byte messageDigest[SHA1HashSize]) { - std::uint32_t *messageDigestBlock; - - messageDigestBlock = (std::uint32_t *)messageDigest; + std::uint32_t *messageDigestBlock = reinterpret_cast(messageDigest); if (messageDigest != nullptr) { for (auto &block : sgSHA1[n].state) { *messageDigestBlock = SDL_SwapLE32(block); @@ -142,9 +135,9 @@ void SHA1Result(int n, byte messageDigest[SHA1HashSize]) } } -void SHA1Calculate(int n, const byte *data, byte messageDigest[SHA1HashSize]) +void SHA1Calculate(int n, const byte data[BlockSize], byte messageDigest[SHA1HashSize]) { - SHA1Input(&sgSHA1[n], data, 64); + SHA1Input(&sgSHA1[n], data, BlockSize); if (messageDigest != nullptr) SHA1Result(n, messageDigest); } diff --git a/Source/sha.h b/Source/sha.h index d52ea95f3..50158e064 100644 --- a/Source/sha.h +++ b/Source/sha.h @@ -11,17 +11,12 @@ namespace devilution { -#define SHA1HashSize 20 - -struct SHA1Context { - uint32_t state[5]; - uint32_t count[2]; - uint32_t buffer[16]; -}; +constexpr size_t BlockSize = 64; +constexpr size_t SHA1HashSize = 20; void SHA1Clear(); void SHA1Result(int n, byte messageDigest[SHA1HashSize]); -void SHA1Calculate(int n, const byte *data, byte messageDigest[SHA1HashSize]); +void SHA1Calculate(int n, const byte data[BlockSize], byte messageDigest[SHA1HashSize]); void SHA1Reset(int n); } // namespace devilution