読者です 読者をやめる 読者になる 読者になる

堕(惰)プログラマ開発記録

タイトル変えようかなとも思ってるけれど,思い浮かばない

SHA1ハッシュの計算

C++ Boost

こんなC++SHA1を計算できた!*1
ページをいくつか参考にしてやったけど、結構面倒なんだね。

// SHA-1ハッシュの計算
// 参考: http://www14.ocn.ne.jp/~bkclass/doc_sha1.html
class sha1{
  typedef oauth::string_type::size_type string_size;
  typedef oauth::string_type::iterator string_it;

  inline unsigned long sha1_shift(const unsigned long bits,unsigned long word)
  {
    return (((word) << (bits)) | ((word) >> (32-(bits))));
  }
    
  unsigned long H[5];
  unsigned long A,B,C,D,E;
  unsigned long W[80];

  void init()
  {
      H[0] = A = 0x67452301;
      H[1] = B = 0xefcdab89;
      H[2] = C = 0x98badcfe;
      H[3] = D = 0x10325476;
      H[4] = E = 0xc3d2e1f0;
      memset(&W,0,sizeof(long)*80);
  }
    
  inline void loop(const int t,const boost::function<unsigned long(unsigned long,unsigned long,unsigned long)> calculate,const unsigned long Key)
  {
    unsigned long TEMP = sha1_shift(5, A) + calculate(B, C, D) + E + W[t] + Key;
    E = D;
    D = C;
    C = sha1_shift(30, B);
    B = A;
    A = TEMP;
  }

  const boost::function<void(int)> from00_to19;
  const boost::function<void(int)> from20_to39;
  const boost::function<void(int)> from40_to59;
  const boost::function<void(int)> from60_to79;

public:
  sha1()
    : from00_to19(boost::bind(&sha1::loop,this, _1,[](unsigned long B,unsigned long C,unsigned long D){return (B & C) | ((~B) & D);},0x5A827999)),
      from20_to39(boost::bind(&sha1::loop,this, _1,[](unsigned long B,unsigned long C,unsigned long D){return B ^ C ^ D;},0x6ED9EBA1)),
      from40_to59(boost::bind(&sha1::loop,this, _1,[](unsigned long B,unsigned long C,unsigned long D){return (B & C) | (B & D) | (C & D);},0x8F1BBCDC)),
      from60_to79(boost::bind(&sha1::loop,this, _1,[](unsigned long B,unsigned long C,unsigned long D){return B ^ C ^ D;},0xCA62C1D6))
  {}
  std::string operator() (std::string data)
  {
    init();

    const string_size length = data.length(); //元データの大きさ
    if(length==0) return "";

    //メッセージの拡張
    {
      data += (char)0x80;  //メッセージ終端に 0x80 の文字を追加
        
      for(int len = length+1; (len%64) < 56; ++len)
        data += (char)0x00; //メッセージが (64*n)+56 になるまで 0x00 を追加
    }

    //ビット長の付加
    {
      std::string bit_len = util::convert::dec_to_binary(length*8);
      if(bit_len.length() > 64) return ""; //8バイト以内にまとまらない・・・だと・・・・・・

      for(int len = bit_len.length(); (len%8)!=0; ++len)
        bit_len = "0" + bit_len; //バイナリが8の倍数になるように補充
        
      //長さを指定しないビットを埋める
      const int zerofill = 8-(bit_len.length()/8);
      for(int i = 0; i < zerofill; ++i)
        data += (char)0x00;

      while(!bit_len.empty())
      {
        data += (char)util::convert::binary_to_dec(bit_len.substr(0,8));
        bit_len.erase(0,8);
      }
    }

    //メッセージダイジェストの計算
    //書いてあったとおりに実装
    std::string result=""; //結果を挿入
    {
      unsigned char *block = (unsigned char*)data.c_str();
        
      for (int t = 0; t < 16; t++)
      {
        W[t]  = block[t*4  ] << 24;
        W[t] |= block[t*4+1] << 16;
        W[t] |= block[t*4+2] <<  8;
        W[t] |= block[t*4+3];
      }

      for (int t = 16; t < 80; t++)
      {
        W[t] = sha1_shift(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
      }
        
      int t=0;
      unsigned long TEMP;
        
      for(; t<20; ++t)
        from00_to19(t);

      for(; t<40; ++t)
        from20_to39(t);

      for(; t<60; ++t)
        from40_to59(t);

      for(; t<80; ++t)
        from60_to79(t);
        
      H[0] += A; H[1] += B; H[2] += C; H[3] += D; H[4] += E;

      BOOST_FOREACH(unsigned long var,H)
      {
        std::string ss = util::convert::dec_to_hex(var);
        while(ss.length()!=8) ss = "0"+ss;
        boost::algorithm::to_lower(ss);
        result += ss;
      }
    }
    return result;
  }
};

頭の二文字ずつ、ビットに直してchar[20]に詰め込めば
20バイトのSHA-1計算値が作り出せる。
あとは、これをテンプレート引数として取る、HMACクラスをかけばCLX依存は無くなるかな。

*1:40文字表示だけど