メイン関数とパディング処理
最後にメイン関数を作ります。 この関数の中で同時にパディング処理も行ってしまいます。 メイン関数はSHA256クラスにおける唯一の公開関数で、関数名をHashとし、引数は任意のバイト配列、戻り値はハッシュ値をバイト配列化したものとします。 ハッシュ計算には文字列を引数とするものが多く存在しますが、内部でキャラクターコードの配列に変換しています。 ハッシュ関数自体はあくまでバイト配列からバイト配列を生成する関数です。
[ SHA256.vb ] Public Class SHA256 ' SHA-256アルゴリズムによりメッセージダイジェストを取得します。 Public Function Hash(ByVal Bytes As Byte()) As Byte() End Function End Class
以降、特に断りがなければこの関数内のコードとして見て下さい。 ちなみに今回はバイト配列で受け取っていますが、IO.Streamで受け取るオーバーロードや文字列で受け取るオーバーロードがあったりすると便利でしょう。 この関数内の流れは「パディング処理」「メッセージブロックの形成」「ハッシュ計算処理の呼び出し」「ハッシュ値のバイト配列化」の3つになります。 まずはパディング処理から作っていきます。
パディング処理
パディング処理の目的は、引数に渡されたバイト配列を64Byteの「メッセージブロック」に分割できるように帳尻あわせを行うことです。 メッセージブロックに分割、といっても実際に分割するわけではなく、いくつのメッセージブロックが出来上がるかを算出するだけです。 メッセージブロック数が割り出せれば、最後のブロックを64Byteに切りそろえることができます。
っと、この説明は正確ではありません。 実際にはどんな長さのデータであれ、バイト配列の末尾に&h80※を追加し、64Byteに帳尻あわせをしたあとで、入力データのbit長を末尾に書き込まなければなりません。 仮にもともとが64Byteで割り切れるとしても、そのままハッシュ計算処理に引き渡すわけにはいきません。 このあたりが少しややこしいところです。
不足した分を単純に追加するだけなら簡単ですが、このパディング処理では&h80と64bitで表現したの入力データのbit長という、少なくとも9Byteのデータが追加されます。 そのため、最後のメッセージブロックが55Byteを超える場合、メッセージブロックにデータを追加した瞬間に64Byteを超えてしまいます。 その時は、&h80だけ追加し、データ長は次のブロックの末尾に記録するように決められています。 このあたりの処理も手法はいろいろとあると思いますが、今回は分かりやすくするのが目的なので&h80の追加とデータ長の記録を完全に分離して考えます。
※ FIPSの解説では、末尾に追加するのはあくまで1bitで、それ以降のデータ長までがパディングとしています。 これは8bit単位ではなく1bit単位でデータ長を記録するようにしているためなのですが、通常はバイト配列を使ってハッシュを求めるので、 ここでは入力データの最小単位を8bitとし、8bit整数型(Byte型)の最上位ビットである&h80を代入しました。
まずは、Bytes配列の長さが変わっても大丈夫なように、引数に渡された配列の長さを記憶しておきましょう。 このデータ長は後ほどメッセージブロックに記憶しますが、やはりリトルエンディアンのままでは都合が悪いので逆転させます。 処理しやすいようにいきなり配列に放り込み、あとで配列ごとコピーします。
[ Function Hash ] ' 元データの長さ(64bit値)をビット数(8倍)で記憶 Dim bytLength() As Byte = BitConverter.GetBytes(Bytes.LongLength << 3) ' リトルエンディアンの場合は配列を逆転してビッグエンディアンにする If BitConverter.IsLittleEndian Then Call Array.Reverse(bytLength) End If
ここでも * 8 を << 3 に置き換えています。 自分でも面倒臭くなってきましたが統一しないと気持ち悪いので…。 コメントにあるように8倍にしているのは「バイト数」ではなく「ビット数」を書き込まなければならないからです。 忘れないようにしましょう。 また、32bitではなく64bitで記録するため、配列の長さをLongLengthで取得しています。 入力データ量のオーバーフローは考慮していませんが、オーバーフローすることもないでしょう…。 ちなみに、オーバーフローさせるために必要な入力データ量は約461亥1686兆0184億Byte(約461万TByte)です。
次に配列を1Byte伸ばして&h80を書き込んでしまいます。
[ Function Hash ] Call Array.Resize(Bytes, Bytes.Length + 1) Bytes(Bytes.GetUpperBound(0)) = &H80
ここからメッセージブロックの総数を算出します。算術演算子で \ 64 すれば求められますが、これは >> 6 に同じなのでここでもビットシフトを使っちゃいます。+1を忘れずに。
[ Function Hash ] Dim intBlock As Integer = (Bytes.Length >> 6) + 1
ブロックの総数が分かればメッセージブロック全体のバイト数さが決まります。 ですが、最後のメッセージブロックに64bit分の空きがないとデータ長を書き込むことができません。 最後のメッセージブロックが56バイトを超えていたらメッセージブロック総数をひとつ増やすようにします。
[ Function Hash ] If (Bytes.Length And &H3F) > 56 Then intBlock += 1 End If
ここでも性懲りもなく Mod を使わずに And で済ませています。 もう放っておいてやってください。
さて、これで正しいメッセージブロックの総数が出ましたので、入力データを一気に拡張します。 .NETでは拡張された部分が自動的に0で初期化されるので、先ほど記憶しておいたデータ長の配列を末尾にコピーすれば完成です。
[ Function Hash ] Call Array.Resize(Bytes, intBlock << 6) Call Array.Copy(bytLength, 0, Bytes, Bytes.Length - 8, 8)
64倍を << 6 に書き換えてるなんてことはありまs(ry