SHA-1 を実装してみた

某所の例の問題を考えてるときにハッシュ関数アルゴリズムSHA-1 の中をいじりたくなって、英語版 WikipediaSHA-1 の頁を参考にして Ruby で実装してみた。
ほぼ擬似コードのとおりに実装しただけで、効率は考えていない。

class Integer
  def rotate(r)
    self << r & 0xffffffff | self >> 32 - r
  end
end

class SHA1
  def main_loop(h, w)
    a, b, c, d, e = h
    80.times do |i|
      f = case i
        when  0..19 then b & c | ~b & d
        when 40..59 then b & c | b & d | c & d
        else             b ^ c ^ d
      end
      a, b, c, d, e =
      a.rotate(5) + e + f + @k[i / 20] + w[i] & 0xffffffff, a, b.rotate(30), c, d
    end
    [a, b, c, d, e].each_with_index {|x, j| h[j] = h[j] + x & 0xffffffff}
  end

  def hash(m)
    h = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]
    until m == '' do
      w = []
      16.times do
        w << m[0, 8].hex
        m = m[8..-1]
      end
      16.upto(79) {|i| w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]).rotate(1)}
      main_loop(h, w)
    end
    h.map {|x| '%08x' % x} .join
  end

  def digest(message)
    @k = [2, 3, 5, 10].map {|x| (Math.sqrt(x) * 2 ** 30).to_i}
    length = message.size
    m = ''
    message.each_byte {|b| m += '%02x' % b}
    m += '80' + '00' * (-(length + 9) % 64) + '%016x' % (8 * length)
    hash(m)
  end
end

# 使用例
puts SHA1.new.digest('The quick brown fox jumps over the lazy dog')

# 同じことをライブラリにある Digest::SHA1 クラスでやるとこうなる
require 'digest/sha1'
puts Digest::SHA1.hexdigest('The quick brown fox jumps over the lazy dog')