[VB.NET]文字列をチャンと暗号化する
.NETアプリの開発Tipsとして有名なDOBON.NETさんの「.NET Tips」に「ファイルを暗号化する」や「文字列を暗号化する」にサンプルのコードが挙がっていますが、初期化ベクタを生成せずデフォルト値をそのまま使っちゃっているため暗号化の処理としてちょっとマズイ事になっています。
そこで上記リンク先のコードを参考に改造を行いました。
ついでに暗号化アルゴリズムをRijndaelに変更してあります。
暗号化キーと初期化ベクタを生成は、RijndaelManagedのGenerateKeyメソッド・GenerateIVメソッドでランダムな暗号化キーと初期化ベクタを生成するようにしました。
そして、生成した暗号化キーと初期化ベクタはレジストリのHKEY_CURRENT_USER以下の任意の場所に保存し、復号化に備えています。
上記改造を施したコードは以下の通りとなります。
Public Class CrypticClass
Public RegistryKey As String 'レジストリのキー
Private Key As String '暗号化キー(16進文字列)
Private IV As String '初期化ベクタ(16進文字列)Private aes As System.Security.Cryptography.RijndaelManaged
'定数
Private Const SubkeyKey = "Key" '暗号キー保存用のレジストリのサブキー
Private Const SubkeyIV = "IV" '初期化ベクタ保存用のレジストリのサブキーPublic Sub New()
'初期化
RegistryKey = ""
Key = ""
IV = ""
'RijndaelManagedオブジェクトの作成
aes = New System.Security.Cryptography.RijndaelManaged
End Sub''' <summary>
''' 暗号化&(無ければ)暗号化キーの生成と保存もします
''' </summary>
''' <param name="strSrc">暗号化する文字列</param>
''' <returns>暗号化された文字列</returns>
Public Function Encrypt(ByVal strSrc As String) As StringEncrypt = ""
If strSrc = "" Then
Exit Function
End IfIf RegistryKey = "" Then
Throw New ApplicationException("CrypticClass.RegistryKeyプロパティが設定されていません。")
End If'暗号化キー・初期化ベクタを取得
GetKey()'暗号化キー・初期化ベクタが無ければランダムな暗号化キー・初期化ベクタを生成&保存
If (Key = "") Or (IV = "") Then
'ランダムな暗号化キーを生成
aes.GenerateKey()
'ランダムな初期化ベクタを生成
aes.GenerateIV()
'ハイフン区切りの16進数表記に変換
Key = BitConverter.ToString(aes.Key)
IV = BitConverter.ToString(aes.IV)
'生成された暗号化キー・初期化ベクタをレジストリに保存
SaveKey()
'念のため暗号化キー・初期化ベクタを再度読み込む
GetKey()
End If'キー・初期化ベクタの内容をチェック
If (Key = "") Or (IV = "") Then
Throw New ApplicationException("暗号化キーの生成に失敗しました。")
End If'暗号化
Encrypt = EncryptString(strSrc)End Function
''' <summary>
''' キーの取得&復号化
''' </summary>
''' <param name="strSrc">復号化する文字列</param>
''' <returns>復号化された文字列</returns>
Public Function Decrypt(ByVal strSrc As String) As StringDecrypt = ""
If strSrc = "" Then
Exit Function
End IfIf RegistryKey = "" Then
Throw New ApplicationException("CrypticClass.RegistryKeyプロパティが設定されていません。")
End If'暗号化キーを取得
GetKey()'暗号化キーが無い場合
If (Key = "") Or (IV = "") Then
Throw New ApplicationException("暗号化キーが存在しません。")
End If'復号化
Decrypt = DecryptString(strSrc)End Function
''' <summary>
''' キーの所得
''' </summary>
Private Sub GetKey()'レジストリのキーを開く
Using regkey As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(RegistryKey, False)
'レジストリのキーが存在しない場合
If (regkey Is Nothing) Then
Exit Sub
End If'サブキーに保存されている暗号化キーを読み込む
Dim strKeyValue As String = CType(regkey.GetValue(SubkeyKey), String)
'サブキーが存在しない場合
If (strKeyValue Is Nothing) Then
Exit Sub
End If'サブキーに保存されている初期化ベクタを読み込む
Dim strIVValue As String = CType(regkey.GetValue(SubkeyIV), String)
'サブキーが存在しない場合
If (strIVValue Is Nothing) Then
Exit Sub
End If'値のセット
Key = strKeyValue
IV = strIVValue
End UsingEnd Sub
''' <summary>
''' キーの保存
''' </summary>
Private Sub SaveKey()'レジストリのキーを開く(無い場合は作成)
Using regkey As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(RegistryKey)
'レジストリへの書き込み
'サブキーに暗号化キーと初期化ベクタを書き込む
regkey.SetValue(SubkeyKey, Key)
regkey.SetValue(SubkeyIV, IV)
End UsingEnd Sub
''' <summary>
''' 文字列を暗号化する
''' </summary>
''' <param name="str">暗号化する文字列</param>
''' <returns>暗号化された文字列</returns>
Private Function EncryptString(ByVal str As String) As StringEncryptString = ""
'文字列をバイト型配列にする
Dim bytesIn As Byte() = System.Text.Encoding.UTF8.GetBytes(str)'共有キーと初期化ベクタを設定
aes.Key = HexToByte(Key)
aes.IV = HexToByte(IV)'暗号化されたデータを書き出すためのMemoryStream
Using msOut As New System.IO.MemoryStream
'AES暗号化オブジェクトの作成
Dim aesdecrypt As System.Security.Cryptography.ICryptoTransform = aes.CreateEncryptor()
'書き込むためのCryptoStreamの作成
Using cryptStreem As New System.Security.Cryptography.CryptoStream(msOut, aesdecrypt, System.Security.Cryptography.CryptoStreamMode.Write)
'書き込む
cryptStreem.Write(bytesIn, 0, bytesIn.Length)
cryptStreem.FlushFinalBlock()
'暗号化されたデータを取得
Dim bytesOut As Byte() = msOut.ToArray()
'Base64で文字列に変更して結果を返す
EncryptString = System.Convert.ToBase64String(bytesOut)
End Using
End UsingEnd Function
''' <summary>
''' 暗号化された文字列を復号化する
''' </summary>
''' <param name="str">暗号化された文字列</param>
''' <returns>復号化された文字列</returns>
Private Function DecryptString(ByVal str As String) As StringDecryptString = ""
'共有キーと初期化ベクタを設定
aes.Key = HexToByte(Key)
aes.IV = HexToByte(IV)'Base64で文字列をバイト配列に戻す
Dim bytesIn As Byte() = System.Convert.FromBase64String(str)
'暗号化されたデータを読み込むためのMemoryStream
Using msIn As New System.IO.MemoryStream(bytesIn)
'AES復号化オブジェクトの作成
Dim aesdecrypt As System.Security.Cryptography.ICryptoTransform = aes.CreateDecryptor()
'読み込むためのCryptoStreamの作成
Using cryptStreem As New System.Security.Cryptography.CryptoStream(msIn, aesdecrypt, System.Security.Cryptography.CryptoStreamMode.Read)
'復号化されたデータを取得するためのStreamReader
Using srOut As New System.IO.StreamReader(cryptStreem, System.Text.Encoding.UTF8)
'復号化されたデータを取得する
Dim result As String = srOut.ReadToEnd()
DecryptString = result
End Using
End Using
End UsingEnd Function
''' <summary>
''' BitConverter.ToStringの16進数表記をバイト配列に復元する
''' </summary>
''' <param name="strHex">16進数表記の文字列</param>
''' <returns>復元されたバイト配列</returns>
Private Function HexToByte(ByVal strHex As String) As Byte()Dim i As Integer = 0
Dim strHexChr() As String = Split(strHex, "-")
Dim newBytes(UBound(strHexChr)) As Byte
For i = 0 To UBound(strHexChr)
newBytes(i) = Convert.ToByte(strHexChr(i), 16)
Next iHexToByte = newBytes
End Function
End Class
CrypticClassのインスタンスを生成後、RegistryKeyプロパティに暗号化キーと初期化ベクタを保存する(してある)レジストリの場所を指定します。
その後、暗号化する場合はEncryptメソッドを、復号化する場合はDecryptメソッドを呼び出します。
暗号化された文字列は扱いやすいよーにBase64でエンコードされています。
また、Encryptメソッドは指定されたレジストリの場所に暗号化キーと初期化ベクタの値が存在しているのかをチェックし、無ければRijndaelManagedのGenerateKeyメソッド・GenerateIVメソッドでランダムな暗号化キーと初期化ベクタを生成してレジストリに保存しています。
ちなみに生成された暗号化キーと初期化ベクタはBitConverter.ToStringメソッドでハイフン区切りの16進数表記の文字列でレジストリに登録されています。
また、暗号化キーと初期化ベクタはアクセスコントロールがチャンとしている場所に保存しないと第三者が覗けてしまって意味が無くなるので保存場所に気を使う必要があります。
あと、ブロック長やキー長はデフォルトのままになっています。(^^;A
【2009.08.21追記】
「DPAPI」を利用して暗号化キーの保存をWindows任せにすることで手軽に暗号化を実装することが可能です。
詳細についてはコチラをご覧ください。
| 固定リンク
コメント
はじめまして、暗号化で検索して訪問しました。
上記VBの内容をテキストボックスから読み込んで別のテキストボックスに表示させるプログラムを作成しています。
http://msdn.microsoft.com/ja-jp/events/dd283151.aspx
↑10行プログラムみたいな感じです。
初めてVBを作っているので苦戦中です。
strの変数をテキストボックスに指定しても動いてくれません。
VB2008で作成しているのですが、お時間有ればご教授頂けるとありがたいです。
投稿: tetu | 2009年3月25日 (水) 02:49
tetuさん、はじめまして
CrypticClassの使用法についてのご質問だと思いますが、↓こんな感じで使用します。
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'CrypticClassを生成
Dim MyCryptic As New CrypticClass
'暗号化に必要な設定を保存するレジストリの場所を設定
'この設定ではHKEY_CURRENT_USER\Software\hogehogeが指定しています。
MyCryptic.RegistryKey = "Software\hogehoge"
'暗号化
TextBox2.Text = MyCryptic.Encrypt(TextBox1.Text)
'復号化
TextBox3.Text = MyCryptic.Decrypt(TextBox2.Text)
End Sub
TextBox1の内容を暗号化してTextBox2に表示し、さらに復号化した結果をTextBox3に表示してます。
ちなみに動作確認はMicrosoft Visual Basic 2008 Express Editionで行いました。
参考になれば幸いです。
投稿: KAZU- | 2009年3月25日 (水) 19:35
KAZU-さん、早々の回答有り難う御座いました。
完璧です。動作確認しました。
先週からVBを始めてみました。色々なサイト回って勉強中です(´・ω・`)
後一点お聞きしたいのですが、上記プログラムでは「MemoryStream」を使用してますよね?
例えば、暗号化を実行すると。
暗号前「hogehoge」暗号後「AssV04S9/lcI972p/VdDlA==」となります。
しかし、暗号後から暗号前を出力する場合は「MemoryStream」に何も入っていない状態?なのでエラーが出ます。
暗号化の文字列からテキスト出力する際は別途処理が必要?でしょうか。
何度もお聞きして申し訳ないです、お手数でなければ教えて頂くと助かります。
投稿: tetu | 2009年3月25日 (水) 21:46
KAZU-さん、お世話になってます。
ちょっとソース弄くったら希望の物が出来ました。ヾ(`・ω・´)b
有り難う御座いました。
投稿: tetu | 2009年3月27日 (金) 01:08