3流プログラマのメモ書き

元開発職→社内SE→派遣で営業支援の三流プログラマのIT技術メモ書き。 このメモが忘れっぽい自分とググってきた技術者の役に立ってくれれば幸いです。(jehupc.exblog.jpから移転中)

(VB.Net)MD5のハッシュからBase32エンコードを行う。

ソフトのプロダクトキー(シリアルキー)をMD5に基づくものにしようと思って考えたものです。

.NetでMD5の値を得ると16進数のByte型配列で帰ってくるのですが、これをBase32化するとちょうど桁数が25.6桁になります。

最後の0.6桁を切り捨てるとちょうど25桁というプロダクトキーにありがちな桁数になるわけです。

ということで、MD5で求めたハッシュ値からBase32化した25桁の文字列にエンコードするのと、その逆、つまりBase32化された文字列から16進数のByte型配列をデコードするソースを下記に書いてみました。

Byte型から5文字づつ使うBase32へのビット演算が苦労しました。ビット単位の演算苦手なんですよね。。

(文字数制限に引っ掛かるため、ハイライトはしてません)

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

Dim hashBase As String = "ハッシュにかける元の文字列"

'文字列をbyte型配列に変換する

Dim hashData As Byte() = System.Text.Encoding.ASCII.GetBytes(hashBase)

'MD5CryptoServiceProviderオブジェクトを作成

Dim md5 As New System.Security.Cryptography.MD5CryptoServiceProvider()

'ハッシュ値を計算する

Dim aryByte As Byte() = md5.ComputeHash(hashData)

'ハッシュのByte型配列をBase32形式に変換

Dim strBase32 As String = Base32Encode(aryByte)

Console.WriteLine("MD5 16進数文字列:" & BitConverter.ToString(aryByte))

Console.WriteLine("MD5 Base32化文字列:" & strBase32)

'Base32文字列をバイト型配列に戻す

Dim bytDecode As Byte() = Base32Decode(strBase32)

Console.WriteLine("Base32デコード16進数文字列:" & BitConverter.ToString(bytDecode))

End Sub

'''

''' Byte型配列からBASE32への変換

'''

''' Base32化するバイト型配列

''' Base32化された文字列(25文字のみ。最後の1文字は含めない)

'''

Public Function Base32Encode(ByVal aryByte As Byte()) As String

'aryByte配列の添え字

Dim j As Integer = 0

'次に何ビットシフトするか

Dim nextShift As UInteger = 0

'base32に変換後の配列(数値でもたす)

Dim Key As UInteger() = New UInteger(25) {}

For i As Integer = 0 To 24

If nextShift > 0 Then

'シフト

Dim shift_a As UInteger = CUInt(aryByte(j)) >> CInt(nextShift)

'5桁の論理積を取り値を求める

Dim res_before As UInteger = shift_a And CUInt(Math.Pow(2, 5)) - 1

'現在のresult[j]だけでは5桁足りないときはaryByteの添え字を+1する

If 8 - nextShift < 5 Then

j += 1

Dim shift_b As UInteger = CUInt(aryByte(j)) << CInt((8 - nextShift))

Dim res_next As UInteger = shift_b And CUInt(Math.Pow(2, 5)) - 1

'resultの前の要素で求めた値と今の要素で求めた値を加算して最終的な値求める

Dim res As UInteger = res_before + res_next

'変換

Key(i) = res

'次のシフトする桁数を求める

nextShift = 5 - (8 - nextShift)

Continue For

Else

'変換

Key(i) = res_before

If nextShift + 5 < 8 Then

'次の桁もシフトする必要があるとき

nextShift = nextShift + 5

Else

'次の桁はシフトする必要がない時

nextShift = 0

j += 1

End If

Continue For

End If

Else

Dim res As UInteger = CUInt(aryByte(j)) And CUInt(Math.Pow(2, 5)) - 1

'変換

Key(i) = res

End If

nextShift = 5

Continue For

Next

'数値型の配列を文字列に変換

Dim keyString As String = ""

For i As Integer = 0 To 24

keyString += CharBase32Encode(Key(i))

Next

Return keyString

End Function

'''

''' Base32形式の文字列をバイト型配列にデコードする

'''

''' Base32形式の文字列

''' バイト型配列(最後の桁の値はエンコード時の桁落ちの関係上使えないので注意)

'''

Private Function Base32Decode(ByVal strBase32 As String) As Byte()

'Base32形式の文字列を文字配列に変換する

Dim chrPrdID As Char() = strBase32.ToCharArray()

'Base32形式の文字列をバイト型配列に直した配列

Dim bytPrdID(25) As Byte

For i As Integer = 0 To UBound(chrPrdID)

bytPrdID(i) = CharBase32Decode(chrPrdID(i))

Next

'バイト型配列にデコードされたBase32形式の文字列

Dim key(15) As Byte

'key配列の何番目の要素を処理しているか

Dim j As Integer = 0

'次にシフトする桁数

Dim nextShift As Integer = 0

'処理中のkeyで何桁まで処理したか

Dim hasDigit As Integer = 0

For i As Integer = 0 To 24

'開始位置がそろった時

If (nextShift = 0) Then

key(j) = bytPrdID(i)

nextShift = 5

hasDigit = 5

Else

'左シフト

key(j) += CType(bytPrdID(i) << nextShift, Byte)

If (8 - hasDigit >= 5) Then

'現在処理中のbytPrdIDの5桁まるまま処理対象とするときは +5

hasDigit += 5

Else

'現在処理中のbytPrdIDの一部の桁だけ処理対象

hasDigit += (8 - hasDigit)

End If

'keyの桁が8超えたら処理対象をkeyを次の要素に移す

If (hasDigit >= 8) Then

'各種リセット

nextShift = 8 - nextShift

hasDigit = 5 - nextShift

j += 1

'右シフト

key(j) += CType(bytPrdID(i) >> nextShift, Byte)

'次のbytPrdID要素でシフトする桁求める

nextShift = 5 - nextShift

Else

'次のシフト桁は今まで処理してるkeyの桁数

nextShift = hasDigit

End If

End If

Next

Return key

End Function

'''

''' 数値をBase32方式に変換する

'''

''' 変換元の数値(0-31)

''' 変換後の文字(A-Z(I,O除く)と2-9)

Private Function CharBase32Encode(ByVal value As UInteger) As Char

Dim chr As Char

Select Case value

Case 0

chr = "A"c

Exit Select

Case 1

chr = "B"c

Exit Select

Case 2

chr = "C"c

Exit Select

Case 3

chr = "D"c

Exit Select

Case 4

chr = "E"c

Exit Select

Case 5

chr = "F"c

Exit Select

Case 6

chr = "G"c

Exit Select

Case 7

chr = "H"c

Exit Select

Case 8

chr = "I"c

Exit Select

Case 9

chr = "J"c

Exit Select

Case 10

chr = "K"c

Exit Select

Case 11

chr = "L"c

Exit Select

Case 12

chr = "M"c

Exit Select

Case 13

chr = "N"c

Exit Select

Case 14

chr = "O"c

Exit Select

Case 15

chr = "P"c

Exit Select

Case 16

chr = "Q"c

Exit Select

Case 17

chr = "R"c

Exit Select

Case 18

chr = "S"c

Exit Select

Case 19

chr = "T"c

Exit Select

Case 20

chr = "U"c

Exit Select

Case 21

chr = "V"c

Exit Select

Case 22

chr = "W"c

Exit Select

Case 23

chr = "X"c

Exit Select

Case 24

chr = "Y"c

Exit Select

Case 25

chr = "Z"c

Exit Select

Case 26

chr = "2"c

Exit Select

Case 27

chr = "3"c

Exit Select

Case 28

chr = "4"c

Exit Select

Case 29

chr = "5"c

Exit Select

Case 30

chr = "6"c

Exit Select

Case 31

chr = "7"c

Exit Select

Case Else

chr = "="c

Exit Select

End Select

Return chr

End Function

'''

''' Base32方式を数値(Byte)に変換する

'''

''' 変換対象のBase32文字

''' 変換後の数値

Private Function CharBase32Decode(ByVal value As Char) As Byte

Dim by As Byte

Select Case value

Case "A"c

by = 0

Exit Select

Case "B"c

by = 1

Exit Select

Case "C"c

by = 2

Exit Select

Case "D"c

by = 3

Exit Select

Case "E"c

by = 4

Exit Select

Case "F"c

by = 5

Exit Select

Case "G"c

by = 6

Exit Select

Case "H"c

by = 7

Exit Select

Case "I"c

by = 8

Exit Select

Case "J"c

by = 9

Exit Select

Case "K"c

by = 10

Exit Select

Case "L"c

by = 11

Exit Select

Case "M"c

by = 12

Exit Select

Case "N"c

by = 13

Exit Select

Case "O"c

by = 14

Exit Select

Case "P"c

by = 15

Exit Select

Case "Q"c

by = 16

Exit Select

Case "R"c

by = 17

Exit Select

Case "S"c

by = 18

Exit Select

Case "T"c

by = 19

Exit Select

Case "U"c

by = 20

Exit Select

Case "V"c

by = 21

Exit Select

Case "W"c

by = 22

Exit Select

Case "X"c

by = 23

Exit Select

Case "Y"c

by = 24

Exit Select

Case "Z"c

by = 25

Exit Select

Case "2"c

by = 26

Exit Select

Case "3"c

by = 27

Exit Select

Case "4"c

by = 28

Exit Select

Case "5"c

by = 29

Exit Select

Case "6"c

by = 30

Exit Select

Case "7"c

by = 31

Exit Select

Case Else

by = 32

Exit Select

End Select

Return by

End Function

これで実行すると結果は下記のようになります。

MD5 16進数文字列:65-F6-75-B3-CF-48-B7-9D-1D-4F-E9-70-F1-68-8C-6B

MD5 Base32化文字列:FT5LXZ6ZI2N3ZO4JJH4CPURRL

Base32デコード16進数文字列:65-F6-75-B3-CF-48-B7-9D-1D-4F-E9-70-F1-68-8C-0B

最後の桁は桁落ちしているので値がずれています。

Base32の仕様はRFC3548にあります。

追記:

CharBase32Encode メソッドと CharBase32Decode メソッドについて、select caseでやるのではなく、あらかじめ配列に添え字と対応する形で文字を入れておいて、それを参照したほうがいいとの意見があってので、載せときます。たしかに、そっちのほうがプロ的ですな。

ソースは修正が面倒なので、そのままに。。