« 文字列にUnicode固有文字が含まれるかのチェック方法 | トップページ | 記事改訂について »

2010年12月23日 (木)

ハッシュ値計算を途中経過取得可能な様に記述する

.NETで用意されているクラスを利用する事により、ハッシュ値の取得は簡単です。
例えばSHA-1なら、以下の記述で充分です。

/// <summary>
/// 指定ファイルのSHA1ハッシュ値を取得する。
/// </summary>
/// <param name="fileName">ハッシュ値を求めるファイルのパス</param>
/// <returns>SHA1ハッシュ値</returns>
public static byte[] SHA1(string file)
{
    using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
    {
        SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
        sha1.ComputeHash(fs);
        return sha1.Hash;
    }
}

ただ、これだと計算途中に処理を中断出来ません。また、計算途中の進捗状況を表示したいといった場合とかでも、これでは対処出来ません。
こんなときは、TransformBlock / TransformFinalBlock を使用します。これを使用して記述すると、以下の様な記述となります。

/// <summary>
/// 指定ファイルのSHA1ハッシュ値を取得する。
/// </summary>
/// <param name="fileName">ハッシュ値を求めるファイルのパス</param>
/// <returns>SHA1ハッシュ値</returns>
public static byte[] SHA1(string file)
{
    using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
    {
        SHA1Managed sha1 = new SHA1Managed();
        byte[] buffer = new Byte[4096];
        int len = fs.Read(buffer, 0, buffer.Length);
        while (fs.Position < fs.Length)
        {
            sha1.TransformBlock(buffer, 0, len, buffer, 0);
            len = fs.Read(buffer, 0, buffer.Length);
        }
        sha1.TransformFinalBlock(buffer, 0, len);
        return sha1.Hash;
    }
}

余談ですが、最初 TransformBlock の引数指定でなぜ outputBuffer, outputOffset の指定がいるのか理解に苦しみました。どうやら暗号化クラスで使用している ICryptoTransform インターフェイスを使いまわしているだけの様です。実際使用してみると、outputBuffer は使用してないらしく、MSDNのサンプルの様に inputBuffer と同じバイト配列を指定して問題ない様です(バイト配列は書き換えられない)。

実は、このあたりの事は、Compensator.net Memo 「C#でSHA-1ハッシュ値を計算する (その2)」で書かれています。そしてここで書かれている TransformBlock / TransformFinalBlock を使用すると随分処理が遅くなるという件が気になりました。

さっそく実験です。
SHA-1のクラスには、SHA1CryptoServiceProvider と SHA1Managed がありますが、SHA1Managed では、既に先程のブログで実験されていますので、SHA1CryptoServiceProvider を使用して、試した結果を示します。うーん、意外に差が出ません。それどころか、bufferサイズを大きくすると、ComputeHash メソッドより優秀な結果になっているではないですか。

SHA1CryptoServiceProvider による結果
buffer Size時間ComputeHashメソッドとの差
64B 226.9秒 3.15倍 72.0秒
1KB 90.5秒 1.26倍
4KB 74.1秒 1.03倍
10KB 69.2秒 0.96倍
512KB 67.2秒 0.93倍

先程のブログの計測結果と比較しても、あまりにも違いがあるのでひょっとして...と思い、SHA1Managed クラスを使用するように変えて計測し直してみました。

SHA1Managed による結果
buffer Size時間ComputeHashメソッドとの差
64B 218.0 3.02倍 72.0秒
1KB 108.5 1.51倍
4KB 101.4 1.41倍
10KB 96.6 1.34倍
512KB 92.8 1.29倍

明らかな差がでました(とは言っても、やはり先程のブログ程の差は出てない)。
ひょっとして、SHA1Managed クラスの TransformBlock / TransformFinalBlock の実装はタコ?(あるいはマネージドコードで実現する為に速度が犠牲になった)との疑惑が沸いて来ました。そこで、他のクラスも計測してみました。

MD5CryptoServiceProvider による結果
buffer Size時間ComputeHashメソッドとの差
64B 202.3 3.83倍 52.8秒
1KB 64.0 1.21倍
4KB 55.3 1.05倍
10KB 53.3 1.01倍
512KB 49.5 0.94倍

まあ、MD5CryptoServiceProvider は、CryptoAPIベースなので、SHA1CryptoServiceProvider と同様差が出ないだろうとの予測どおりの結果でした。
問題は、SHAXXXManaged クラスです。なお、SHAXXXManagedのクラスでは、5GBのファイルでは時間がかかり過ぎるので、500MBのスパースファイルを使用しました。

SHA256Managed による結果
buffer Size時間ComputeHashメソッドとの差
64B 51.2 1.39倍 36.8秒
1KB 37.5 1.02倍
4KB 36.7 1.00倍
10KB 36.2 0.98倍
512KB 35.0 0.95倍
SHA384Managed による結果
buffer Size時間ComputeHashメソッドとの差
64B 80.3 1.17倍 68.5秒
1KB 69.4 1.01倍
4KB 68.0 0.99倍
10KB 67.7 0.99倍
512KB 66.9 0.98倍
SHA512Managed による結果
buffer Size時間ComputeHashメソッドとの差
64B 121.3 1.25倍 97.3秒
1KB 102.9 1.06倍
4KB 106.9 1.10倍
10KB 105.7 1.09倍
40KB 104.6 1.08倍
128KB 100.2 1.03倍
256KB 96.1 0.99倍
512KB 94.1 0.97倍

意外にも、MD5CryptoServiceProvider / SHA1CryptoServiceProvider 同様 ComputeHash との差はほとんどない(むしろバッファサイズが大きいと逆に優秀)という結果を得ました。となると、SHA1Managed だけが TransformBlock / TransformFinalBlock の処理が遅い事になります。何故でしょうね。
原因ははっきりしませんが、SHA-1ハッシュで、TransformBlock / TransformFinalBlock を使用する場合は、SHA1Managed クラスを使用せずに、SHA1CryptoServiceProvider を使用すべきですね。

« 文字列にUnicode固有文字が含まれるかのチェック方法 | トップページ | 記事改訂について »

C#研究」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: ハッシュ値計算を途中経過取得可能な様に記述する:

« 文字列にUnicode固有文字が含まれるかのチェック方法 | トップページ | 記事改訂について »