Browse Source

Cleanup Diablo-SHA and codec.cpp

1. Switches to 32-bit based calculation throughout. This is faster and
   less error-prone as we only byte-swap once when reading and writing.
2. Removes global state from Diablo-SHA implementation.
pull/3916/head
Gleb Mazovetskiy 4 years ago
parent
commit
c43aead8d1
  1. 150
      Source/codec.cpp
  2. 68
      Source/sha.cpp
  3. 15
      Source/sha.h

150
Source/codec.cpp

@ -1,10 +1,3 @@
/**
* @file codec.cpp
*
* Implementation of save game encryption algorithm.
*/
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
@ -12,6 +5,7 @@
#include "appfat.h"
#include "sha.h"
#include "utils/endian.hpp"
#include "utils/log.hpp"
#include "utils/stdcompat/cstddef.hpp"
namespace devilution {
@ -23,48 +17,39 @@ struct CodecSignature {
uint8_t lastChunkSize;
};
constexpr size_t BlockSizeBytes = BlockSize * sizeof(uint32_t);
constexpr size_t SignatureSize = 8;
// 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
SHA1Context CodecInitKey(const char *pszPassword)
{
return { byte(std::forward<Ts>(args))... };
}
void CodecInitKey(const char *pszPassword)
{
byte pw[BlockSize]; // Repeat password until 64 char long
uint32_t pw[BlockSize]; // Repeat password until 64 char long
std::size_t j = 0;
for (std::size_t i = 0; i < sizeof(pw); i++, j++) {
for (uint32_t &value : pw) {
if (pszPassword[j] == '\0')
j = 0;
pw[i] = static_cast<byte>(pszPassword[j]);
value = LoadLE32(&pszPassword[j]);
j += sizeof(uint32_t);
}
alignas(alignof(uint32_t)) byte digest[SHA1HashSize];
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 + 12) % SHA1HashSize];
memset(pw, 0, sizeof(pw));
memset(digest, 0, sizeof(digest));
for (int n = 0; n < 3; ++n) {
SHA1Reset(n);
SHA1Calculate(n, key.data(), nullptr);
uint32_t digest[SHA1HashSize];
{
SHA1Context context;
SHA1Calculate(context, pw);
SHA1Result(context, digest);
}
memset(key.data(), 0, sizeof(key));
uint32_t key[BlockSize] {
2908958655, 4146550480, 658981742, 1113311088, 3927878744, 679301322, 1760465731, 3305370375,
2269115995, 3928541685, 580724401, 2607446661, 2233092279, 2416822349, 4106933702, 3046442503
};
for (unsigned i = 0; i < BlockSize; ++i) {
key[i] ^= digest[(i + 3) % SHA1HashSize];
}
SHA1Context context;
SHA1Calculate(context, key);
return context;
}
CodecSignature GetCodecSignature(byte *src)
@ -89,91 +74,96 @@ void SetCodecSignature(byte *dst, CodecSignature sig)
*dst++ = static_cast<byte>(0);
}
void ByteSwapBlock(uint32_t *data)
{
for (size_t i = 0; i < BlockSize; ++i)
data[i] = SDL_SwapLE32(data[i]);
}
void XorBlock(const uint32_t *shaResult, uint32_t *out)
{
for (unsigned i = 0; i < BlockSize; ++i)
out[i] ^= shaResult[i % SHA1HashSize];
}
} // namespace
std::size_t codec_decode(byte *pbSrcDst, std::size_t size, const char *pszPassword)
{
byte buf[BlockSize];
alignas(alignof(uint32_t)) byte dst[SHA1HashSize];
uint32_t buf[BlockSize];
uint32_t dst[SHA1HashSize];
CodecInitKey(pszPassword);
SHA1Context context = CodecInitKey(pszPassword);
if (size <= SignatureSize)
return 0;
size -= SignatureSize;
if (size % BlockSize != 0)
return 0;
for (auto i = size; i != 0; pbSrcDst += BlockSize, i -= BlockSize) {
memcpy(buf, pbSrcDst, BlockSize);
SHA1Result(0, dst);
for (unsigned j = 0; j < BlockSize; j++) {
buf[j] ^= dst[j % SHA1HashSize];
}
SHA1Calculate(0, buf, nullptr);
memset(dst, 0, sizeof(dst));
memcpy(pbSrcDst, buf, BlockSize);
for (size_t i = 0; i < size; pbSrcDst += BlockSizeBytes, i += BlockSizeBytes) {
memcpy(buf, pbSrcDst, BlockSizeBytes);
ByteSwapBlock(buf);
SHA1Result(context, dst);
XorBlock(dst, buf);
SHA1Calculate(context, buf);
ByteSwapBlock(buf);
memcpy(pbSrcDst, buf, BlockSizeBytes);
}
memset(buf, 0, sizeof(buf));
const CodecSignature sig = GetCodecSignature(pbSrcDst);
if (sig.error > 0) {
goto error;
return 0;
}
SHA1Result(0, dst);
if (sig.checksum != *reinterpret_cast<uint32_t *>(dst)) {
SHA1Result(context, dst);
if (sig.checksum != dst[0]) {
LogError("Checksum mismatch signature={} vs calculated={}", sig.checksum, dst[0]);
memset(dst, 0, sizeof(dst));
goto error;
return 0;
}
size += sig.lastChunkSize - BlockSize;
SHA1Clear();
size += sig.lastChunkSize - BlockSizeBytes;
return size;
error:
SHA1Clear();
return 0;
}
std::size_t codec_get_encoded_len(std::size_t dwSrcBytes)
{
if (dwSrcBytes % BlockSize != 0)
dwSrcBytes += BlockSize - (dwSrcBytes % BlockSize);
if (dwSrcBytes % BlockSizeBytes != 0)
dwSrcBytes += BlockSizeBytes - (dwSrcBytes % BlockSizeBytes);
return dwSrcBytes + SignatureSize;
}
void codec_encode(byte *pbSrcDst, std::size_t size, std::size_t size64, const char *pszPassword)
{
byte buf[BlockSize];
alignas(alignof(uint32_t)) byte tmp[SHA1HashSize];
byte dst[SHA1HashSize];
uint32_t buf[BlockSize];
uint32_t tmp[SHA1HashSize];
uint32_t dst[SHA1HashSize];
if (size64 != codec_get_encoded_len(size))
app_fatal("Invalid encode parameters");
CodecInitKey(pszPassword);
SHA1Context context = CodecInitKey(pszPassword);
size_t lastChunk = 0;
while (size != 0) {
size_t chunk = size < BlockSize ? size : BlockSize;
const size_t chunk = std::min(size, BlockSizeBytes);
memset(buf, 0, sizeof(buf));
memcpy(buf, pbSrcDst, chunk);
if (chunk < BlockSize)
memset(buf + chunk, 0, BlockSize - chunk);
SHA1Result(0, dst);
SHA1Calculate(0, buf, nullptr);
for (unsigned j = 0; j < BlockSize; j++) {
buf[j] ^= dst[j % SHA1HashSize];
}
memset(dst, 0, sizeof(dst));
memcpy(pbSrcDst, buf, BlockSize);
ByteSwapBlock(buf);
SHA1Result(context, dst);
SHA1Calculate(context, buf);
XorBlock(dst, buf);
ByteSwapBlock(buf);
memcpy(pbSrcDst, buf, BlockSizeBytes);
pbSrcDst += BlockSizeBytes;
lastChunk = chunk;
pbSrcDst += BlockSize;
size -= chunk;
}
memset(buf, 0, sizeof(buf));
SHA1Result(0, tmp);
SHA1Result(context, tmp);
SetCodecSignature(pbSrcDst, CodecSignature { /*.checksum=*/*reinterpret_cast<uint32_t *>(tmp),
/*.error=*/0,
// lastChunk is at most 64 so will always fit in an 8 bit var
/*.lastChunkSize=*/static_cast<uint8_t>(lastChunk) });
SHA1Clear();
}
} // namespace devilution

68
Source/sha.cpp

@ -1,17 +1,8 @@
/**
* @file sha.cpp
*
* Implementation of functionality for calculating X-SHA-1 (a flawed implementation of SHA-1).
*/
#include "sha.h"
#include <cstdint>
#include <cstring>
#include <SDL.h>
#include "appfat.h"
namespace devilution {
// NOTE: Diablo's "SHA1" is different from actual SHA1 in that it uses arithmetic
@ -19,21 +10,11 @@ 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 word, size_t bits)
{
assert(bits < 32);
assert(bits > 0);
// 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)
@ -41,22 +22,11 @@ uint32_t SHA1CircularShift(uint32_t word, size_t bits)
return (word << bits) | (word >> (32 - bits));
}
void SHA1Init(SHA1Context *context)
{
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
}
void SHA1ProcessMessageBlock(SHA1Context *context)
{
std::uint32_t w[80];
for (int i = 0; i < 16; i++)
w[i] = SDL_SwapLE32(context->buffer[i]);
memcpy(w, context->buffer, BlockSize * sizeof(uint32_t));
for (int i = 16; i < 80; i++) {
w[i] = w[i - 16] ^ w[i - 14] ^ w[i - 8] ^ w[i - 3];
}
@ -110,43 +80,17 @@ void SHA1ProcessMessageBlock(SHA1Context *context)
context->state[4] += e;
}
void SHA1Input(SHA1Context *context, const byte *messageArray, std::size_t len)
{
for (auto i = len / BlockSize; i != 0; i--) {
memcpy(context->buffer, messageArray, BlockSize);
SHA1ProcessMessageBlock(context);
messageArray += BlockSize;
}
}
} // namespace
void SHA1Clear()
{
memset(sgSHA1, 0, sizeof(sgSHA1));
}
void SHA1Result(int n, byte messageDigest[SHA1HashSize])
{
std::uint32_t *messageDigestBlock = reinterpret_cast<std::uint32_t *>(messageDigest);
if (messageDigest != nullptr) {
for (auto &block : sgSHA1[n].state) {
*messageDigestBlock = SDL_SwapLE32(block);
messageDigestBlock++;
}
}
}
void SHA1Calculate(int n, const byte data[BlockSize], byte messageDigest[SHA1HashSize])
void SHA1Result(SHA1Context &context, uint32_t messageDigest[SHA1HashSize])
{
SHA1Input(&sgSHA1[n], data, BlockSize);
if (messageDigest != nullptr)
SHA1Result(n, messageDigest);
memcpy(messageDigest, context.state, sizeof(context.state));
}
void SHA1Reset(int n)
void SHA1Calculate(SHA1Context &context, const uint32_t data[BlockSize])
{
SHA1Init(&sgSHA1[n]);
memcpy(&context.buffer[0], data, BlockSize * sizeof(uint32_t));
SHA1ProcessMessageBlock(&context);
}
} // namespace devilution

15
Source/sha.h

@ -11,12 +11,15 @@
namespace devilution {
constexpr size_t BlockSize = 64;
constexpr size_t SHA1HashSize = 20;
constexpr size_t BlockSize = 16;
constexpr size_t SHA1HashSize = 5;
void SHA1Clear();
void SHA1Result(int n, byte messageDigest[SHA1HashSize]);
void SHA1Calculate(int n, const byte data[BlockSize], byte messageDigest[SHA1HashSize]);
void SHA1Reset(int n);
struct SHA1Context {
uint32_t state[SHA1HashSize] = { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 };
uint32_t buffer[BlockSize];
};
void SHA1Result(SHA1Context &context, uint32_t messageDigest[SHA1HashSize]);
void SHA1Calculate(SHA1Context &context, const uint32_t data[BlockSize]);
} // namespace devilution

Loading…
Cancel
Save