空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

SHA-1 for Windows (Little Endian)

*追記:Windows Vista以降だと、CNGというライブラリがあるようです。こちらのほうが実務では役立つかも。 http://msdn.microsoft.com/en-us/library/windows/desktop/aa376210%28v=vs.85%29.aspx

*追記2:現時点では実務ではCrypto++を使うのがベストと思われる。 http://www.cryptopp.com/

仕事で必要になりそうだったけど、C++で書かれた便利な実装がなかったのでWikipediaを参考にして車輪を作ってみました。

  1. あとでWikipediaを見ながら何をやっているか推測できる程度にpseudo codeに忠実で、かつコンパイラによる最適化に引っかかるようにテンプレートを適度に使いました。条件の部分がテンプレートパラメータの定数になっているswitch-case文やif文、デバッグ目的のローカル変数は全て最適化で取り除かれ、関数自体もインライン化されます。
  2. Wikipediaのpseudo codeは、リテラルがbig endianを前提にしているので、little endianのシステムだとバイトの並びが逆になります。一方で、リテラル自体を変えてしまうとWikipediaの記述との対応がとりにくいので、ハッシュを計算するデータはbig endian、ビット演算のとこでエンディアンを入れ替えています。
  3. SHA-1は生データからハッシュ計算用のデータにプリプロセスするとき、生データ長を最後の64-bitに収めることになっています。little endianのシステムではここでもエンディアンを入れ替える必要があるところも注意です。
  4. Pseudo codeを実直に実装すると、データのプリプロセスとハッシュの計算で2回ループをするような実装になりがちですが、メインループ内で使われるのはチャンクの中の1要素だけなので、プリプロセスをハッシュの計算のループの中に押し込んでいます。

Enjoy!

//
// SHA-1 for Windows (Little Endian)
//
#include <cstdlib>
#include <cstdio>
#include <cstring>

using namespace std;

//-----------------------------------------------------------------------------

template <int N> inline unsigned __int32 LeftRotate(unsigned __int32 nVal)
{
  unsigned __int32 l = (nVal << N) | (nVal >> (32 - N));
  return l;
}

//-----------------------------------------------------------------------------
//
// In big-endian system, this function should return the given value itself
// since there is no need to swap.
//
template <typename Int> inline Int SwapEndian(Int n)
{
  unsigned __int8* pc = reinterpret_cast<unsigned __int8*>(&n);
  Int nRet = 0;

  for (int i = 0; i < sizeof(n); ++i)
    nRet |= Int(pc[i]) << ((sizeof(n)-1-i) * 8);

  return nRet;
}

//-----------------------------------------------------------------------------

template <int N, int Mod> inline void SHA1Sub(
  int i,
  unsigned __int32    w[80],
  unsigned __int32&   a,
  unsigned __int32&   b,
  unsigned __int32&   c,
  unsigned __int32&   d,
  unsigned __int32&   e
  )
{
  const unsigned __int32 k[] = { 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 };
  unsigned __int32 f = 0;

  switch (N)
  {
  case 0:
    f = (b & c) | (~b & d);
    break;
  case 1:
    f = b ^ c ^ d;
    break;
  case 2:
    f = (b & c) | (b & d) | (c & d);
    break;
  case 3:
    f = b ^ c ^ d;
    break;
  default:
    break;
  }

  if (Mod)
  {
    w[i] = LeftRotate<1>(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]);
  }
  else
  {
    w[i] = SwapEndian(w[i]);
  }

  unsigned __int32 n = LeftRotate<5>(a) + f + e + k[N] + w[i];
  e = d;
  d = c;
  c = LeftRotate<30>(b);
  b = a;
  a = n;
}

//-----------------------------------------------------------------------------

inline void SHA1Main(unsigned __int32 h[5], unsigned __int32 w[80])
{
  unsigned __int32 a = h[0];
  unsigned __int32 b = h[1];
  unsigned __int32 c = h[2];
  unsigned __int32 d = h[3];
  unsigned __int32 e = h[4];

  for (int i = 0; i < 16; ++i)
    SHA1Sub<0,0>(i, w, a, b, c, d, e);

  for (int i = 16; i < 20; ++i)
    SHA1Sub<0,1>(i, w, a, b, c, d, e);

  for (int i = 20; i < 40; ++i)
    SHA1Sub<1,1>(i, w, a, b, c, d, e);

  for (int i = 40; i < 60; ++i)
    SHA1Sub<2,1>(i, w, a, b, c, d, e);

  for (int i = 60; i < 80; ++i)
    SHA1Sub<3,1>(i, w, a, b, c, d, e);

  h[0] += a;
  h[1] += b;
  h[2] += c;
  h[3] += d;
  h[4] += e;
}

//-----------------------------------------------------------------------------

template <typename Reader>
inline bool CalcSHA1_impl(Reader& zReader, char acHash[41])
{
  unsigned __int32 anHash[5] = {
    0x67452301,
    0xefcdab89,
    0x98badcfe,
    0x10325476,
    0xc3d2e1f0
  };

  union {
    unsigned __int8  m_acData[64]; // 512-bit
    unsigned __int64 m_anInt64[8]; // 512-bit
    unsigned __int32 m_anWork[80]; // 512-bit + working area.
  } zChunk;

  unsigned __int64 nTotal = 0;

  for (;;)
  {
    size_t nRead = zReader.Read64(zChunk.m_acData);
    nTotal += nRead * 8; // bits

    if (nRead < 56)
    {
      zChunk.m_acData[nRead] = 0x80;
      memset(&zChunk.m_acData[nRead+1], 0, 55-nRead);
      zChunk.m_anInt64[7] = SwapEndian(nTotal);
      SHA1Main(anHash, zChunk.m_anWork);
      break;
    }
    else if (nRead < 64)
    {
      zChunk.m_acData[nRead] = 0x80;
      memset(&zChunk.m_acData[nRead+1], 0, 63-nRead);
      SHA1Main(anHash, zChunk.m_anWork);
      memset(zChunk.m_acData, 0, 64);
      zChunk.m_anInt64[7] = SwapEndian(nTotal);
      SHA1Main(anHash, zChunk.m_anWork);
      break;
    }
    else
    {
      SHA1Main(anHash, zChunk.m_anWork);
    }
  }

  _snprintf_s(acHash, 41, _TRUNCATE, "%08x%08x%08x%08x%08x",
              anHash[0], anHash[1], anHash[2], anHash[3], anHash[4]);

  return true;
}

//-----------------------------------------------------------------------------

namespace
{
  class FileReader
  {
  private:
    FILE* m_fp;

  public:
    FileReader() : m_fp(NULL)
    {}

    ~FileReader()
    {
      if (m_fp)
      {
        fclose(m_fp);
        m_fp = NULL;
      }
    }

    bool Open(const wchar_t* pcFileName)
    {
      if (!pcFileName)
        return false;

      if (m_fp)
        return false;

      m_fp = _wfopen(pcFileName, L"rb");

      if (!m_fp)
        return false;

      return true;
    }

    size_t Read64(void* pcBuff)
    {
      return fread(pcBuff, 1, 64, m_fp);
    }
  };


  class MemReader
  {
  private:
    const unsigned __int8* m_pcBuff;
    size_t m_nSize;

  public:
    MemReader(): m_pcBuff(NULL), m_nSize(0)
    {}

    bool Initialize(const void* pBuff, size_t nSize)
    {
      if (!pBuff && nSize)
        return false;

      m_pcBuff = static_cast<const unsigned __int8*>(pBuff);
      m_nSize = nSize;
      return true;
    }

    size_t Read64(void* pBuff)
    {
      if (m_nSize >= 64)
      {
        m_nSize -= 64;
        memcpy(pBuff, m_pcBuff, 64);
        m_pcBuff += 64;
        return 64;
      }
      else if (m_nSize > 0)
      {
        size_t nSize = m_nSize;
        memcpy(pBuff, m_pcBuff, nSize);
        m_nSize = 0;
        return nSize;
      }
      else
      {
        return 0;
      }
    }
  };
}

//-----------------------------------------------------------------------------

bool CalcSHA1(const wchar_t* pcFileName, char acHash[41])
{
  FileReader zReader;

  if (!zReader.Open(pcFileName))
    return false;

  if (!CalcSHA1_impl(zReader, acHash))
    return false;

  return true;
}

//-----------------------------------------------------------------------------

bool CalcSHA1(const void* pBuff, size_t nSize, char acHash[41])
{
  MemReader zReader;

  if (!zReader.Initialize(pBuff, nSize))
    return false;

  if (!CalcSHA1_impl(zReader, acHash))
    return false;

  return true;
}