Refactor Diablo-SHA to attempt to document behaviour through code (#2524)

* Hardcode the key and ensure buffers are appropriately sized.

If you do want to build the key at runtime the following initial state gets the right sequence:
```cpp
std::linear_congruential_engine<uint32_t, 214013, 2531011, 0> engine(3193970784);
for (auto &notch: key)
	notch = static_cast<byte>(engine() >> 16);
```

* Move public defines into sha.h, move private struct into .cpp

* Remove unused count member, clarify loop in SHA1Input

* Update circular shift to indicate desired behaviour, remove unnecessary negation.

The parameters are also reordered to match C++20 rotl/rotr and the usual order for shift operators.
This commit is contained in:
Andrew James
2021-08-08 13:22:22 +02:00
committed by GitHub
parent 82d82fb7a3
commit 0aea0782c7
3 changed files with 56 additions and 59 deletions
+27 -18
View File
@@ -4,6 +4,7 @@
* Implementation of save game encryption algorithm.
*/
#include <array>
#include <cstdint>
#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 <typename... Ts>
std::array<byte, sizeof...(Ts)> make_bytes(Ts &&...args) noexcept
{
return { byte(std::forward<Ts>(args))... };
}
void CodecInitKey(const char *pszPassword)
{
byte key[136]; // last 64 bytes are the SHA1
uint32_t randState = 0x7058;
for (auto &notch : key) {
randState = randState * 214013 + 2531011;
notch = static_cast<byte>(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<byte, BlockSize> 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<uint32_t *>(tmp);
sig->lastChunkSize = lastChunk;
sig->lastChunkSize = static_cast<uint8_t>(lastChunk); // lastChunk is at most 64 so will always fit in an 8 bit var
SHA1Clear();
}