Browse Source

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.
pull/2539/head
Andrew James 5 years ago committed by GitHub
parent
commit
0aea0782c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      Source/codec.cpp
  2. 59
      Source/sha.cpp
  3. 11
      Source/sha.h

45
Source/codec.cpp

@ -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();
}

59
Source/sha.cpp

@ -5,8 +5,8 @@
*/
#include "sha.h"
#include <SDL.h>
#include <cstdint>
#include <SDL.h>
#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<std::uint32_t *>(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);
}

11
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

Loading…
Cancel
Save