ローテーション処理
さて、肝心要のハッシュ計算処理です。 ここは少し丁寧に解説していきます。
メソッド名はComputation。 現在のハッシュ値と64Byteのバイト配列を受け取り、新しいハッシュ値は引数の配列へ返すようにします。 ハッシュ値は符号なし32bit値の配列とします。 戻り値は使わないのでSubプロシージャで宣言します。
[ SHA256.vb ] Public Class SHA256 ' ハッシュ計算処理 Private Sub Computation(ByVal Hash As UInt32(), ByVal Block As Byte()) End Sub End Class
以降、特に断りがなければ掲載するコードはこのメソッド内と考えてください。 次に、ここで使う変数を宣言します。
[ Sub Computation ] ' ハッシュ計算用変数 Dim intTemp1 As UInt32 Dim intTemp2 As UInt32 Dim intW(63) As UInt32
まずはこの3つ。
intTemp1とintTemp2は、ローテーション処理中に値を変化させるための一時的な数値を格納するためのものです。 使うのが1回だけなら変数なしで直接計算してもよいのですが、複数回使うこととローテーション処理中に計算で使う値が変化してしまうので変数に記憶します。
intWは引数Blockから渡されたメッセージブロックから生成する値を格納するためのものです。 メッセージブロックの拡張処理で解説した通り、64個の符号なし32bit固定配列※として用意します。 このうち、インデックス0~15まではBlockに渡された配列がそのまま入ります。 そのままと言っても、intWは32bit、Blockは8bitなので、intWひとつにつきBlockから4個分データを引き抜きます。 これは、BitConverterを使えば簡単にできるのですが、ここで注意が必要です。 Windowsはリトルエンディアンを採用しているため、バイト配列の順番どおりに32bit値へ代入してしまうと順序が逆転してしまいます。 演算に使わなければ問題ありませんが、今回はおもいっきりビットシフトなどを行いますのでビッグエンディアンに置き換えないと正しい処理ができません。 そこで、BitConverterを呼び出す前に、Blockから読み込む範囲を逆転させます。 これを踏まえて、intW(0)~intW(15)までの代入式は次のようになります。
[ Sub Computation ] For i As Integer = 0 To 15 Call Array.Reverse(Block, i << 2, 4) intW(i) = BitConverter.ToUInt32(Block, i << 2) Next
<< 2 は * 4 に同じです。 乗算よりもビットシフトのほうが高速…だと思うのでこうしてるだけです。 あと、WIZ.はメソッドをそのまま名前で呼び出すのが嫌いなので「Call」を付けています。 気にしないでください。
ビッグエンディアンのプラットフォームで実行する可能性がある場合は、次のようにリトルエンディアンの環境下でのみ逆転させるようにしてください。
[ Sub Computation ] For i As Integer = 0 To 15 If BitConverter.IsLittleEndian Then Call Array.Reverse(Block, i << 2, 4) End If intW(i) = BitConverter.ToUInt32(Block, i << 2) Next
続いてintW(16)~intW(63)までを生成します。 この範囲は、すでに代入したintW(0)~intW(15)を元に値を生成します。 生成式は次の通り。
[ Sub Computation ] For i As Integer = 16 To 63 intW(i) = SafeAdd(SigmaB1(intW(i - 2)), intW(i - 7), SigmaB0(intW(i - 15)), intW(i - 16)) Next
ネスティングされてて分かりにくいですが、すでに代入されているintWのうちの4つ(-2/-7/-15/16)を、-2/-15についてはシグマ関数を使って変形してからすべてを合計しています。 ここでオーバーフローを回避するために先ほど作成したSafeAdd関数を使用しています。 これでintW配列には、元のデータとそれに続く元データを変形させた値の配列が完成します。
次に、引数Hashに渡された現在のハッシュ値(長さ8の符号なし32bit配列)をコピーします。 Hash自体はそのままの形で後ほど使うので手をつけません。 元が配列なので配列をコピーしたいところですが、FIPSではa~hの変数名で表記しているので、それに倣(なら)います。 いやなら配列にしちゃって構いません。
[ Sub Computation ] ' 現在のハッシュを複製 Dim intA As UInt32 = Hash(0) Dim intB As UInt32 = Hash(1) Dim intC As UInt32 = Hash(2) Dim intD As UInt32 = Hash(3) Dim intE As UInt32 = Hash(4) Dim intF As UInt32 = Hash(5) Dim intG As UInt32 = Hash(6) Dim intH As UInt32 = Hash(7)
これでローテーション処理を行う準備が整いました。
ローテーション処理はハッシュ計算処理でも述べたとおり、全部で64回行います。 これは普通にFor~Nextループで回します。 ここで、回転するごとに変化を与えるための値intTemp1/intTemp2を算出します。
[ Sub Computation ] ' ローテーション処理ループ For i As Integer = 0 To 63 intTemp1 = SafeAdd(intH, SigmaA1(intE), Ch(intE, intF, intG), intK(i), intW(i)) intTemp2 = SafeAdd(SigmaA0(intA), Maj(intA, intB, intC))
これはまだループの前半部分ですが、intTemp1/intTemp2の算出方法は上記の通りです。 ここでようやく定数配列※のintKが登場します。 また先ほど作ったintWも使われています。 用意したCh/Maj関数もここで使われていますね。 これで変化を与える値も準備できました。 次にハッシュ値を回転させます。
[ Sub Computation / For i = 0 To 63 ] intH = intG intG = intF intF = intE intE = SafeAdd(intD, intTemp1) intD = intC intC = intB intB = intA intA = SafeAdd(intTemp1, intTemp2) Next
ローテーション処理ループの後半です。 見て分かるように、intHにintGを、intGにintFをという具合にハッシュの列をひとつずつスライドしています。 そして、intEとintAのときにだけ算出した値を加算して変化を加えています。 ここでintHはどこにも行かず消えてしまいますが、intHはintTemp1の算出に使われています。 ローテーション処理はここまでです。
最後に、すでに得られているハッシュ値に新たに計算したハッシュ値を加算して戻します。 引数から受け取った配列を再利用すれば、呼び出し側の配列にそのまま戻ります。 ここは好き好きなので戻り値で戻したければそれでも構いません。
[ Sub Computation ] Hash(0) = SafeAdd(intA, Hash(0)) Hash(1) = SafeAdd(intB, Hash(1)) Hash(2) = SafeAdd(intC, Hash(2)) Hash(3) = SafeAdd(intD, Hash(3)) Hash(4) = SafeAdd(intE, Hash(4)) Hash(5) = SafeAdd(intF, Hash(5)) Hash(6) = SafeAdd(intG, Hash(6)) Hash(7) = SafeAdd(intH, Hash(7)) End Sub
ここでは分かりやすくするために、intWをあらかじめ算出しました。 ローテーション処理内においてintWは回転回数のインデックスになるまでその値が使われることがないので、ローテーション処理ループの中でintWを算出することも可能です。 この場合、For~Nextは0~63まで一気に回るのでIfで分岐してください。 この手法で作成したComputationメソッドが次のようになります。
[ SHA256.vb ] Private Sub Computation(ByVal Hash As UInt32(), ByVal Block As Byte()) ' ハッシュ計算用変数 Dim intTemp1 As UInt32 ' 回転処理中に変化を与える値1 Dim intTemp2 As UInt32 ' 回転処理中に変化を与える値2 Dim intW(63) As UInt32 ' Blockから生成される64Byteの配列 ' 現在のハッシュを複製 ' 配列の方が効率的だけど、FIPSに合わせてintA~intHで表現 Dim intA As UInt32 = Hash(0) Dim intB As UInt32 = Hash(1) Dim intC As UInt32 = Hash(2) Dim intD As UInt32 = Hash(3) Dim intE As UInt32 = Hash(4) Dim intF As UInt32 = Hash(5) Dim intG As UInt32 = Hash(6) Dim intH As UInt32 = Hash(7) ' ローテーション処理 ' intKやintW(Blockの配列)、現在のハッシュ値などを用いて64回、ハッシュ値をローテーションさせる ' intWの配列は回転させながら作る For i As Integer = 0 To 63 ' 0~15は、Blockの配列を4byteずつintXに代入していく If i < 16 Then ' リトルエンディアンの場合は、配列の読み取り位置を ' 逆転させてビッグエンディアンにする If BitConverter.IsLittleEndian Then Call Array.Reverse(Block, i << 2, 4) End If intW(i) = BitConverter.ToUInt32(Block, i << 2) ' 16~63は、すでに代入された値から生成する Else intW(i) = SafeAdd(SigmaB1(intW(i - 2)), intW(i - 7), _ SigmaB0(intW(i - 15)), intW(i - 16)) End If ' ローテーション時に変化を与える値を算出 ' ここでintKとintWが使われる intTemp1 = SafeAdd(intH, SigmaA1(intE), Ch(intE, intF, intG), intK(i), intW(i)) intTemp2 = SafeAdd(SigmaA0(intA), Maj(intA, intB, intC)) ' ハッシュ値ローテーション ' (intEとintAで変化を与える。ここで失われるintHはintTemp1の計算に使われている) intH = intG intG = intF intF = intE intE = SafeAdd(intD, intTemp1) intD = intC intC = intB intB = intA intA = SafeAdd(intTemp1, intTemp2) Next ' 算出されたハッシュを現在のハッシュに加算 Hash(0) = SafeAdd(Hash(0), intA) Hash(1) = SafeAdd(Hash(1), intB) Hash(2) = SafeAdd(Hash(2), intC) Hash(3) = SafeAdd(Hash(3), intD) Hash(4) = SafeAdd(Hash(4), intE) Hash(5) = SafeAdd(Hash(5), intF) Hash(6) = SafeAdd(Hash(6), intG) Hash(7) = SafeAdd(Hash(7), intH) End Sub