wiz.code

動作テスト

以上でSHA256クラスのコーディングは完了です。 これを実際に使うには、バイト配列をSHA256.Hashへ放り込むだけです。 文字列からハッシュ値を得るには、System.Text.EncodingのGetBytesメソッドを使えば取得できます。 FIPSではすべてASCIIコードで検証が行われているので注意してください。

[ Module.vb ]
Sub Main()

' オリジナルSHA256クラス
Dim clsSHA256_Original As New SHA256

' .NET FrameworkのSHA256Managedクラス
Dim clsSHA256_DotNetClass As New System.Security.Cryptography.SHA256Managed

' テキストをバイト配列にするためのエンコーディング
Dim encText As System.Text.Encoding

' 入力データを収めるバイト配列
Dim bytData As Byte()

' Asciiエンコーディングを取得
encText = System.Text.Encoding.ASCII '.GetEncoding("shift_jis")

' 入力データを設定
bytData = encText.GetBytes("abc")

' 結果を出力
Call Console.WriteLine("SHA-256 Hashed by 'abc'")
Call Console.WriteLine("  SHA256 Original   = " & _
                       BytesToHex(clsSHA256_Original.Hash(bytData)))
Call Console.WriteLine("  SHA256 .Net Class = " & _
                       BytesToHex(clsSHA256_DotNetClass.ComputeHash(bytData)))
Call Console.WriteLine("  FIPS Sample       = " & _
                       "ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad")

' 入力データを設定(2つのメッセージブロック処理の検証)
bytData = encText.GetBytes("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")

' 結果を出力
Call Console.WriteLine("SHA-256 Hashed by 'abc'")
Call Console.WriteLine("  SHA256 Original   = " & _
                       BytesToHex(clsSHA256_Original.Hash(bytData)))
Call Console.WriteLine("  SHA256 .Net Class = " & _
                       BytesToHex(clsSHA256_DotNetClass.ComputeHash(bytData)))
Call Console.WriteLine("  FIPS Sample       = " & _
                       "248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1")

End Sub

' Byte配列を16進数文字列として取得
Private Function BytesToHex(ByVal Value As Byte()) As String
    Dim stbBuff As New System.Text.StringBuilder

        For intIndex As Integer = 0 To Value.GetUpperBound(0)
            Call stbBuff.Append(Value(intIndex).ToString("x2"))
            If (intIndex And &H3) = &H3 Then Call stbBuff.Append(" "c)
        Next

    Return stbBuff.ToString
End Function

上記コードの実行結果が次の通りです。

[ 出力結果 ]
SHA-256 Hashed by 'abc'
  SHA256 Original   = ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad
  SHA256 .Net Class = ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad
  FIPS Sample       = ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad
SHA-256 Hashed by 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'
  SHA256 Original   = 248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1
  SHA256 .Net Class = 248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1
  FIPS Sample       = 248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1

オリジナルクラス、.NET Framework(System.Security.Cryptography.SHA256Managed)、FIPSのサンプルデータともに一致していますね。 一応これで動作が正しいことが証明されました。

うまく動作しない方は…

今回紹介したコードに細かくコメントを入れたソースコードを記載します。 うまく動かない場合はソースを照らし合わせてみてください。

''' <summary>
''' SHA-256ハッシュアルゴリズムを用いてバイト配列のハッシュ(メッセージダイジェスト)を取得するクラスです。
''' </summary>
''' <remarks>
''' 参照文書 : FIPS 180-2
''' 文書URL  : http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
''' </remarks>
Public Class SHA256

#Region "定数"

    ''' <summary>
    ''' ブロックの回転処理に使用する定数
    ''' </summary>
    ''' <remarks>
    ''' 参照文書 : FIPS 180-2 4.2.2 SHA-256 Constants
    ''' 定数サイズ: 256byte (32bit(4byte) x 64)
    ''' </remarks>
    Private intK As UInt32() = { _
        &H428A2F98UI, &H71374491UI, &HB5C0FBCFUI, &HE9B5DBA5UI, &H3956C25BUI, &H59F111F1UI, &H923F82A4UI, &HAB1C5ED5UI, _
        &HD807AA98UI, &H12835B01UI, &H243185BEUI, &H550C7DC3UI, &H72BE5D74UI, &H80DEB1FEUI, &H9BDC06A7UI, &HC19BF174UI, _
        &HE49B69C1UI, &HEFBE4786UI, &HFC19DC6UI, &H240CA1CCUI, &H2DE92C6FUI, &H4A7484AAUI, &H5CB0A9DCUI, &H76F988DAUI, _
        &H983E5152UI, &HA831C66DUI, &HB00327C8UI, &HBF597FC7UI, &HC6E00BF3UI, &HD5A79147UI, &H6CA6351UI, &H14292967UI, _
        &H27B70A85UI, &H2E1B2138UI, &H4D2C6DFCUI, &H53380D13UI, &H650A7354UI, &H766A0ABBUI, &H81C2C92EUI, &H92722C85UI, _
        &HA2BFE8A1UI, &HA81A664BUI, &HC24B8B70UI, &HC76C51A3UI, &HD192E819UI, &HD6990624UI, &HF40E3585UI, &H106AA070UI, _
        &H19A4C116UI, &H1E376C08UI, &H2748774CUI, &H34B0BCB5UI, &H391C0CB3UI, &H4ED8AA4AUI, &H5B9CCA4FUI, &H682E6FF3UI, _
        &H748F82EEUI, &H78A5636FUI, &H84C87814UI, &H8CC70208UI, &H90BEFFFAUI, &HA4506CEBUI, &HBEF9A3F7UI, &HC67178F2UI}

    ''' <summary>
    ''' 1ワードあたりのビット数
    ''' </summary>
    ''' <remarks></remarks>
    Private Const w As Integer = 32

#End Region

#Region "演算関数"

    ''' <summary>
    ''' 右ビットシフト(SHift Right)
    ''' </summary>
    ''' <param name="v">ビットシフトさせる値を指定します。</param>
    ''' <param name="n">シフトするビット数を指定します。</param>
    ''' <returns>v ≫ n</returns>
    ''' <remarks></remarks>
    Private Function SHR(ByVal v As UInt32, ByVal n As Integer) As UInt32
        Return v >> n
    End Function

    ''' <summary>
    ''' 左ビットシフト(SHift Left)
    ''' </summary>
    ''' <param name="v">ビットシフトさせる値を指定します。</param>
    ''' <param name="n">シフトするビット数を指定します。</param>
    ''' <returns>v ≪ n</returns>
    ''' <remarks></remarks>
    Private Function SHL(ByVal v As UInt32, ByVal n As Integer) As UInt32
        Return v << n
    End Function

    ''' <summary>
    ''' 右ビット回転(ROTate Right)
    ''' </summary>
    ''' <param name="v">ビット回転させる値を指定します。</param>
    ''' <param name="n">回転するビット数を指定します。</param>
    ''' <returns>(v ≫ n) Or (v ≪ w - n)</returns>
    ''' <remarks></remarks>
    Private Function ROTR(ByVal v As UInt32, ByVal n As Integer) As UInt32
        Return (v >> n) Or (v << w - n)
    End Function

    ''' <summary>
    ''' 左ビット回転(ROTate Left)
    ''' </summary>
    ''' <param name="v">ビット回転させる値を指定します。</param>
    ''' <param name="n">回転するビット数を指定します。</param>
    ''' <returns>(v ≪ n) Or (v ≫ w - n)</returns>
    ''' <remarks></remarks>
    Private Function ROTL(ByVal v As UInt32, ByVal n As Integer) As UInt32
        Return (v << n) Or (v >> w - n)
    End Function

    ''' <summary>
    ''' オーバーフローを回避して複数の値を加算
    ''' </summary>
    ''' <param name="Values">加算する値を指定します。</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function SafeAdd(ByVal ParamArray Values As UInt32()) As UInt32
        Dim intReturn As UInt32 = Values(0)

            For i As Integer = 1 To Values.GetUpperBound(0)
                intReturn = ((intReturn And &H7FFFFFFFUI) + (Values(i) And &H7FFFFFFFUI)) Xor _
                            (intReturn And &H80000000UI) Xor (Values(i) And &H80000000UI)

            Next

        Return intReturn
    End Function

#End Region

#Region "ハッシュ計算用関数"

    ''' <summary>
    ''' Ch
    ''' </summary>
    ''' <returns>(x And y) Xor ((Not x) And z)</returns>
    ''' <remarks>
    ''' 参照: FIPS 180-2 4.1.2 SHA-256 Functions
    ''' </remarks>
    Private Function Ch(ByVal x As UInt32, ByVal y As UInt32, ByVal z As UInt32) As UInt32
        Return (x And y) Xor ((Not x) And z)
    End Function

    ''' <summary>
    ''' Maj
    ''' </summary>
    ''' <returns>(x And y) Xor (x And z) Xor (y And z)</returns>
    ''' <remarks>
    ''' 参照: FIPS 180-2 4.1.2 SHA-256 Functions
    ''' </remarks>
    Private Function Maj(ByVal x As UInt32, ByVal y As UInt32, ByVal z As UInt32) As UInt32
        Return (x And y) Xor (x And z) Xor (y And z)
    End Function

    ''' <summary>
    ''' シグマA0(Σ0)
    ''' </summary>
    ''' <returns>ROTR(x, 2) Xor ROTR(x, 13) Xor ROTR(x, 22)</returns>
    ''' <remarks>
    ''' 参照: FIPS 180-2 4.1.2 SHA-256 Functions
    ''' </remarks>
    Private Function SigmaA0(ByVal x As UInt32) As UInt32
        Return ROTR(x, 2) Xor ROTR(x, 13) Xor ROTR(x, 22)
    End Function

    ''' <summary>
    ''' シグマA1(Σ1)
    ''' </summary>
    ''' <returns>ROTR(x, 6) Xor ROTR(x, 11) Xor ROTR(x, 25)</returns>
    ''' <remarks>
    ''' 参照: FIPS 180-2 4.1.2 SHA-256 Functions
    ''' </remarks>
    Private Function SigmaA1(ByVal x As UInt32) As UInt32
        Return ROTR(x, 6) Xor ROTR(x, 11) Xor ROTR(x, 25)
    End Function

    ''' <summary>
    ''' シグマB0(σ0)
    ''' </summary>
    ''' <returns>ROTR(x, 7) Xor ROTR(x, 18) Xor SHR(x, 3)</returns>
    ''' <remarks>
    ''' 参照: FIPS 180-2 4.1.2 SHA-256 Functions
    ''' </remarks>
    Private Function SigmaB0(ByVal x As UInt32) As UInt32
        Return ROTR(x, 7) Xor ROTR(x, 18) Xor SHR(x, 3)
    End Function

    ''' <summary>
    ''' シグマB1(σ1)
    ''' </summary>
    ''' <returns>ROTR(x, 17) Xor ROTR(x, 19) Xor SHR(x, 10)</returns>
    ''' <remarks>
    ''' 参照: FIPS 180-2 4.1.2 SHA-256 Functions
    ''' </remarks>
    Private Function SigmaB1(ByVal x As UInt32) As UInt32
        Return ROTR(x, 17) Xor ROTR(x, 19) Xor SHR(x, 10)
    End Function

    ''' <summary>
    ''' ハッシュ計算処理
    ''' </summary>
    ''' <param name="Hash">現在までに生成されたハッシュ値を指定。</param>
    ''' <param name="Block">処理するメッセージブロックを指定。メッセージブロックは64byteのバイト配列。</param>
    ''' <remarks>
    ''' 参照: FIPS 180-2 6.2.2 SHA-256 Hash Computation
    ''' </remarks>
    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))


                ' ローテーションの途中結果を見るにはこの下のコメントをはずしてください。
                'Call Trace.Write(String.Format("ローテーション {0}回目", (i + 1).ToString("00")) & "  ")
                'Call Trace.Write(intA.ToString("x8") & " ")
                'Call Trace.Write(intB.ToString("x8") & " ")
                'Call Trace.Write(intC.ToString("x8") & " ")
                'Call Trace.Write(intD.ToString("x8") & " ")
                'Call Trace.Write(intE.ToString("x8") & " ")
                'Call Trace.Write(intF.ToString("x8") & " ")
                'Call Trace.Write(intG.ToString("x8") & " ")
                'Call Trace.Write(intH.ToString("x8") & " ")
                'Call Trace.WriteLine("")


                ' ハッシュ値ローテーション
                ' (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

#End Region


#Region "公開メソッド"

    ''' <summary>
    ''' SHA-256アルゴリズムによりメッセージダイジェストを取得します。
    ''' </summary>
    ''' <param name="Bytes">元データが含まれたバイト配列を指定します。</param>
    ''' <returns>メッセージダイジェストを収めた64byteのバイト配列を返します。</returns>
    ''' <remarks></remarks>
    Public Function Hash(ByVal Bytes As Byte()) As Byte()

        '* パディング
        '  ブロック長(64byte)の倍数になるようにデータ長を調整
        '  本来なら1bit単位で処理すべきなんだろうけど、バイト配列で受け取ってるから8bit単位で処理
        '参照文書 : FIPS 180-2 5.1.1 SHA-1 and SHA-256

        ' 元データの長さ(64bit値)をビット数(8倍)で記憶
        ' << 3(左に3シフト)は * 8 と同じ
        Dim bytLength() As Byte = BitConverter.GetBytes(Bytes.LongLength << 3)

            ' リトルエンディアンの場合は配列を逆転してビッグエンディアンにする
            If BitConverter.IsLittleEndian Then
                Call Array.Reverse(bytLength)
            End If

        ' 配列を伸ばしてデータの末尾に&h80を追加する
        Call Array.Resize(Bytes, Bytes.Length + 1)
        Bytes(Bytes.GetUpperBound(0)) = &H80

        ' ブロックの総数を算出
        ' ブロックサイズ長は64(0100 0000)なので、右に6ビットシフトして+1すれば求められる。
        ' Bytes.Length \ 64 + 1 でも同じ
        Dim intBlock As Integer = (Bytes.Length >> 6) + 1

            ' 最後のブロックが56バイトを超える場合は、ブロックの総数を1つ増やす
            ' 64で割った余りは下位6bitに等しい
            If (Bytes.Length And &H3F) > 56 Then
                intBlock += 1
            End If

        ' データの配列をブロックの総数に合わせる
        ' 1ブロックあたり64Byteなので、64倍 = 左に6ビットシフト
        Call Array.Resize(Bytes, intBlock << 6)

        ' 配列の最後に元データの長さを記入する
        Call Array.Copy(bytLength, 0, Bytes, Bytes.Length - 8, 8)


        '* ブロックに分けてハッシュ値を計算

        ' ハッシュの初期値
        ' 参照  : FIPS 180-2 5.3.2 Setting the Initial Hash Value - SHA-256
        ' サイズ: 32bit(4Byte) x 8 = 256bit
        Dim intHash As UInt32() = {&H6A09E667UI, &HBB67AE85UI, &H3C6EF372UI, &HA54FF53AUI, _
                                   &H510E527FUI, &H9B05688CUI, &H1F83D9ABUI, &H5BE0CD19UI}

        ' ブロック用の配列(64Byte)を用意
        Dim bytBlock(63) As Byte

            ' 64byteごとに分けてループ
            For intPos As Integer = 0 To Bytes.Length - 1 Step 64

                ' ブロック用配列にデータをコピー
                Call Array.Copy(Bytes, intPos, bytBlock, 0, bytBlock.Length)

                ' ブロックのハッシュを計算
                Call Computation(intHash, bytBlock)

            Next


        '* 得られたハッシュ値をバイト配列に戻す

        ' ハッシュ値を格納するバイト配列
        Dim bytResult(31) As Byte

            For intIndex As Integer = 0 To 7
                ' バイト配列にハッシュ値のバイトをコピー
                Call Array.Copy(BitConverter.GetBytes(intHash(intIndex)), 0, bytResult, intIndex << 2, 4)

                    ' リトルエンディアンの場合は配列を回転させてビッグエンディアンにする
                    If BitConverter.IsLittleEndian Then
                        Call Array.Reverse(bytResult, intIndex << 2, 4)
                    End If
            Next

        Return bytResult
    End Function

#End Region

End Class