« [VB.NET]暗号乱数ジェネレータを使った厳密にランダムな文字列の生成 | トップページ | 気になった2本のiPhoneアプリ »

2009年2月 7日 (土)

[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 String

        Encrypt = ""

        If strSrc = "" Then
            Exit Function
        End If

        If 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 String

        Decrypt = ""

        If strSrc = "" Then
            Exit Function
        End If

        If 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 Using

    End 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 Using

    End Sub

    ''' <summary>
    ''' 文字列を暗号化する
    ''' </summary>
    ''' <param name="str">暗号化する文字列</param>
    ''' <returns>暗号化された文字列</returns>
    Private Function EncryptString(ByVal str As String) As String

        EncryptString = ""

        '文字列をバイト型配列にする
        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 Using

    End Function

    ''' <summary>
    ''' 暗号化された文字列を復号化する
    ''' </summary>
    ''' <param name="str">暗号化された文字列</param>
    ''' <returns>復号化された文字列</returns>
    Private Function DecryptString(ByVal str As String) As String

        DecryptString = ""

        '共有キーと初期化ベクタを設定
        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 Using

    End 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 i

        HexToByte = newBytes

    End Function
End Class

CrypticClassのインスタンスを生成後、RegistryKeyプロパティに暗号化キーと初期化ベクタを保存する(してある)レジストリの場所を指定します。

その後、暗号化する場合はEncryptメソッドを、復号化する場合はDecryptメソッドを呼び出します。
暗号化された文字列は扱いやすいよーにBase64でエンコードされています。

また、Encryptメソッドは指定されたレジストリの場所に暗号化キーと初期化ベクタの値が存在しているのかをチェックし、無ければRijndaelManagedのGenerateKeyメソッド・GenerateIVメソッドでランダムな暗号化キーと初期化ベクタを生成してレジストリに保存しています。

ちなみに生成された暗号化キーと初期化ベクタはBitConverter.ToStringメソッドでハイフン区切りの16進数表記の文字列でレジストリに登録されています。

また、暗号化キーと初期化ベクタはアクセスコントロールがチャンとしている場所に保存しないと第三者が覗けてしまって意味が無くなるので保存場所に気を使う必要があります。

あと、ブロック長やキー長はデフォルトのままになっています。(^^;A


【2009.08.21追記】

「DPAPI」を利用して暗号化キーの保存をWindows任せにすることで手軽に暗号化を実装することが可能です。
詳細についてはコチラをご覧ください。

|

« [VB.NET]暗号乱数ジェネレータを使った厳密にランダムな文字列の生成 | トップページ | 気になった2本のiPhoneアプリ »

コメント

はじめまして、暗号化で検索して訪問しました。
上記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

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/26493/27849880

この記事へのトラックバック一覧です: [VB.NET]文字列をチャンと暗号化する:

« [VB.NET]暗号乱数ジェネレータを使った厳密にランダムな文字列の生成 | トップページ | 気になった2本のiPhoneアプリ »