「ほっ」と。キャンペーン
(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

''' <summary>
''' Byte型配列からBASE32への変換
''' </summary>
''' <param name="aryByte">Base32化するバイト型配列</param>
''' <returns>Base32化された文字列(25文字のみ。最後の1文字は含めない)</returns>
''' <remarks></remarks>
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

''' <summary>
''' Base32形式の文字列をバイト型配列にデコードする
''' </summary>
''' <param name="strBase32">Base32形式の文字列</param>
''' <returns>バイト型配列(最後の桁の値はエンコード時の桁落ちの関係上使えないので注意)</returns>
''' <remarks></remarks>
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

''' <summary>
''' 数値をBase32方式に変換する
''' </summary>
''' <param name="value">変換元の数値(0-31)</param>
''' <returns>変換後の文字(A-Z(I,O除く)と2-9)</returns>
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

''' <summary>
''' Base32方式を数値(Byte)に変換する
''' </summary>
''' <param name="value">変換対象のBase32文字</param>
''' <returns>変換後の数値</returns>
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でやるのではなく、あらかじめ配列に添え字と対応する形で文字を入れておいて、それを参照したほうがいいとの意見があってので、載せときます。たしかに、そっちのほうがプロ的ですな。
ソースは修正が面倒なので、そのままに。。
[PR]
by jehoshaphat | 2008-12-20 21:54 | .Net開発 | Trackback | Comments(0)
トラックバックURL : http://jehupc.exblog.jp/tb/9248500
トラックバックする(会員専用) [ヘルプ]
※このブログはトラックバック承認制を適用しています。 ブログの持ち主が承認するまでトラックバックは表示されません。


<< (CakePHP)命名規約の単... (VB.Net)時間用コンボボ... >>