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 ¬ch: 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:
parent
82d82fb7a3
commit
0aea0782c7
3 changed files with 56 additions and 59 deletions
|
|
@ -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 ¬ch : 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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
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…
Add table
Add a link
Reference in a new issue