ストリームに対するインクリメンタルなハッシュ計算

ファイルのダウンローダなどで、ファイルのダウンロードが完了してからハッシュを計算しているソフトウェアを見るが、あれは、どう考えても、ダウンロード中にインクリメンタルにハッシュを計算した方が確実に効率がよい。ダウンロード中は、ほとんどの場合、CPUはネットワーク待ちとなってアイドル状態が続くことが多いと考えられるからだ。

ということで、Streamにラッパーを被せることでストリーム処理に透過的にハッシュの処理を追加できるようにするコードを書いてみた。

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;

namespace Test
{
  class HashTest
  {
    static void Main(string[] args)
    {
      using (Stream s = File.OpenRead(args[0]))
      {
        // HashAlgorithmを継承している任意のハッシュ方式を選択可能
        HashStream<SHA256Managed> ss = new HashStream<SHA256Managed>(s);

        // 
        byte[] tmp = new byte[1024 * 1024];
        while (ss.Read(tmp, 0, tmp.Length) != 0)
        {
          // なんか処理をする
        }
        Console.WriteLine(BytesToStr(ss.ComputeHash()));
      }
    }

    static string BytesToStr(byte[] bytes)
    {
      StringBuilder str = new StringBuilder();
      for (int i = 0; i < bytes.Length; i++)
        str.AppendFormat("{0:X2}", bytes[i]);
      return str.ToString();
    }
  }

  class HashStream<HashAlgo> : Stream where HashAlgo : HashAlgorithm, new()
  {
    private Stream _s;
    private HashAlgo _ha;
    private byte[] _result;

    public HashStream(Stream s)
    {
      _s = s;
      _ha = new HashAlgo();
      _result = null;
    }

    public override bool CanRead
    {
      get { return _s.CanRead; }
    }

    public override bool CanSeek
    {
      get { return false; }
    }

    public override bool CanWrite
    {
      get { return _s.CanWrite; }
    }

    public override void Flush()
    {
      _s.Flush();
    }

    public override long Length
    {
      get { return _s.Length; }
    }

    public override long Position
    {
      get
      {
        return _s.Position;
      }
      set
      {
        throw new NotSupportedException();
      }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
      int len = _s.Read(buffer, offset, count);
      if (_ha != null)
        _ha.TransformBlock(buffer, offset, len, null, 0);
      return len;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
      throw new NotSupportedException();
    }

    public override void SetLength(long value)
    {
      _s.SetLength(value);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
      _s.Write(buffer, offset, count);
      if (_ha != null)
        _ha.TransformBlock(buffer, offset, count, null, 0);
    }

    public byte[] ComputeHash()
    {
      if (_ha != null)
      {
        _ha.TransformFinalBlock(new byte[] { }, 0, 0);
        _result = _ha.Hash;
        _ha = null;
      }
      return _result;
    }
  }
}