DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

DPAPIを使用して暗号化する

ここでは、DPAPI(データ保護API、Data Protection API)を使用してデータを暗号化、復号化する方法を紹介します。DPAPIについて「セキュリティ保護された ASP.NET アプリケーションの構築 : 用語集」では、次のように説明されています。

「DPAPI は、Windows 2000 以降のオペレーティング システムに用意されているデータ暗号化および解読用の Win32 API です。DPAPI は、Windows アカウント パスワードを使って暗号化キーを生成し、暗号化技術に関するキー管理の問題をオペレーティング システムに渡します。」

DPAPIについてより詳しくは、「Windows Data Protection」や「DPAPI (データ保護 API) のトラブルシューティング」をご覧ください。

.NET Framework 2.0からは、DPAPIを簡単に利用するためのProtectedDataとProtectedMemoryクラスが追加されました。ここでは、ProtectedDataクラスとProtectedMemoryクラスの使い方を紹介します。

なお.NET Framework 1.1以前でも、PInvokeによりDPAPIを使うことができます。詳しくは、「DPAPI ライブラリを作成する方法」や「How To: Use DPAPI to Encrypt and Decrypt Data (C#/VB.NET)」などをご覧ください。

ProtectedDataクラス

まずはProtectedDataクラスの使い方から説明します。ProtectedDataクラスは、Windows 2000以降のOSで使用できます。ProtectedData.Protectメソッドで暗号化し、Unprotectメソッドで復号化します。

以下の例では、Button1ボタンを押すことによってTextBox1の値を暗号化し、暗号化された値をTextBox2に表示しています。また、Button2ボタンを押すことによってTextBox2の値を復号化し、復号化された値をTextBox1に表示しています。「system.security.dll」を参照設定に追加する必要があります。

VB.NET
コードを隠すコードを選択
'暗号化で使用する追加のバイト配列
Private entropy() As Byte = {&H72, &HA2, &H12, &H4}

'TextBox1の値を暗号化し、TextBox2に表示する
'Button1のClickイベントハンドラ
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles Button1.Click
    '文字列をバイト型配列に変換
    Dim userData As Byte() = System.Text.Encoding.UTF8.GetBytes(TextBox1.Text)

    '暗号化する
    Dim encryptedData As Byte() = _
        System.Security.Cryptography.ProtectedData.Protect( _
            userData, entropy, _
            System.Security.Cryptography.DataProtectionScope.CurrentUser)

    '暗号化されたデータを文字列に変換
    TextBox2.Text = System.Convert.ToBase64String(encryptedData)
End Sub

'TextBox2の値を復号化し、TextBox1に表示する
'Button2のClickイベントハンドラ
Private Sub Button2_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles Button2.Click
    '文字列を暗号化されたデータに戻す
    Dim encryptedData As Byte() = System.Convert.FromBase64String(TextBox2.Text)

    '復号化する
    Dim userData As Byte() = _
        System.Security.Cryptography.ProtectedData.Unprotect( _
            encryptedData, entropy, _
            System.Security.Cryptography.DataProtectionScope.CurrentUser)

    '復号化されたデータを文字列に変換
    TextBox1.Text = System.Text.Encoding.UTF8.GetString(userData)
End Sub
C#
コードを隠すコードを選択
//暗号化で使用する追加のバイト配列
private byte[] entropy = new byte[] { 0x72, 0xa2, 0x12, 0x04 };

//TextBox1の値を暗号化し、TextBox2に表示する
//Button1のClickイベントハンドラ
private void Button1_Click(object sender, EventArgs e)
{
    //文字列をバイト型配列に変換
    byte[] userData = System.Text.Encoding.UTF8.GetBytes(TextBox1.Text);

    //暗号化する
    byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect(
        userData, entropy,
        System.Security.Cryptography.DataProtectionScope.CurrentUser);

    //暗号化されたデータを文字列に変換
    TextBox2.Text = System.Convert.ToBase64String(encryptedData);
}

//TextBox2の値を復号化し、TextBox1に表示する
//Button2のClickイベントハンドラ
private void Button2_Click(object sender, EventArgs e)
{
    //文字列を暗号化されたデータに戻す
    byte[] encryptedData = System.Convert.FromBase64String(TextBox2.Text);

    //復号化する
    byte[] userData = System.Security.Cryptography.ProtectedData.Unprotect(
        encryptedData, entropy,
        System.Security.Cryptography.DataProtectionScope.CurrentUser);

    //復号化されたデータを文字列に変換
    TextBox1.Text = System.Text.Encoding.UTF8.GetString(userData);
}

ProtectedData.Protectメソッドではバイト型配列を暗号化するため、まず文字列をバイト型配列に変換しています。暗号化されたデータもバイト型配列のため、文字列にするために、Base64でエンコードしています。復号化では、その逆を行っています。

ProtectedData.Protectメソッドの2番目のパラメータ"optionalEntropy"には、追加のバイト型配列を指定することができます。"optionalEntropy"はUnprotectメソッドで復号化する際にも必要なため、同じユーザーが実行しているアプリケーションであっても、この値を知らなければ復号化できません。"optionalEntropy"が必要なければ、null(VB.NETでは、Nothing)でOKです。なお、上記のコードで指定している"optionalEntropy"の値には何の意味もなく、適当です。

ProtectedData.Protectメソッドの3番目のパラメータ"scope"には、DataProtectionScope列挙体のメンバを指定します。上記のようにDataProtectionScope.CurrentUserを指定すると、現在のユーザーだけが復号化できます。"scope"にDataProtectionScope.LocalMachineを指定すると、現在のコンピュータが実行している全てのプロセスが復号化できてしまいます。つまりLocalMachineを指定するケースは、信頼できるユーザーのみがログインすることのできるサーバーで実行されるアプリケーションということになるでしょう。

ProtectedMemoryクラス

次にProtectedMemoryクラスを使って暗号化と復号化を行う方法を紹介します。ProtectedMemoryクラスはWindows XP以降のOSで使用できます。ProtectedDataクラスと同様に、ProtectedMemory.Protectメソッドで暗号化し、Unprotectメソッドで復号化します。

以下の例では、Button3ボタンを押すことによってTextBox1の値を暗号化し、暗号化された値をencryptedDataフィールドに格納しています。また、Button4ボタンを押すことによってencryptedDataフィールドのデータを復号化し、復号化された値をTextBox1に表示しています。「system.security.dll」を参照設定に追加する必要があります。

VB.NET
コードを隠すコードを選択
'暗号化されたデータ
Private encryptedData() As Byte

'TextBox1の値を暗号化し、encryptedDataに格納する
'Button3のClickイベントハンドラ
Private Sub Button3_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles Button3.Click
    '文字列をバイト型配列に変換
    Me.encryptedData = System.Text.Encoding.UTF8.GetBytes(TextBox1.Text)
    TextBox1.Text = ""
    '16バイトの倍数にする
    Dim mod16 As Integer = Me.encryptedData.Length Mod 16
    If mod16 > 0 Then
        Array.Resize(Of Byte)( _
            Me.encryptedData, 16 - mod16 + Me.encryptedData.Length)
    End If

    '暗号化する
    'Me.encryptedDataに暗号化されたデータが入る
    System.Security.Cryptography.ProtectedMemory.Protect( _
        Me.encryptedData, _
        System.Security.Cryptography.MemoryProtectionScope.SameProcess)
End Sub

'encryptedDataの値を復号化し、TextBox1に表示する
'Button4のClickイベントハンドラ
Private Sub Button4_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles Button4.Click
    '復号化する
    System.Security.Cryptography.ProtectedMemory.Unprotect( _
        Me.encryptedData, _
        System.Security.Cryptography.MemoryProtectionScope.SameProcess)

    '復号化されたデータを文字列に変換
    TextBox1.Text = System.Text.Encoding.UTF8.GetString(Me.encryptedData)

    Me.encryptedData = Nothing
End Sub
C#
コードを隠すコードを選択
//暗号化されたデータ
private byte[] encryptedData;

//TextBox1の値を暗号化し、encryptedDataに格納する
//Button3のClickイベントハンドラ
private void Button3_Click(object sender, EventArgs e)
{
    //文字列をバイト型配列に変換
    this.encryptedData = System.Text.Encoding.UTF8.GetBytes(TextBox1.Text);
    TextBox1.Text = "";
    //16バイトの倍数にする
    int mod16 = this.encryptedData.Length % 16;
    if (mod16 > 0)
    {
        Array.Resize<byte>(ref this.encryptedData,
            16 - mod16 + this.encryptedData.Length);
    }

    //暗号化する
    //this.encryptedDataに暗号化されたデータが入る
    System.Security.Cryptography.ProtectedMemory.Protect(
        this.encryptedData,
        System.Security.Cryptography.MemoryProtectionScope.SameProcess);
}

//encryptedDataの値を復号化し、TextBox1に表示する
//Button4のClickイベントハンドラ
private void Button4_Click(object sender, EventArgs e)
{
    //復号化する
    System.Security.Cryptography.ProtectedMemory.Unprotect(
        this.encryptedData,
        System.Security.Cryptography.MemoryProtectionScope.SameProcess);

    //復号化されたデータを文字列に変換
    TextBox1.Text = System.Text.Encoding.UTF8.GetString(this.encryptedData);

    this.encryptedData = null;
}

ProtectedDataクラスと違い、ProtectedMemory.Protectメソッドで暗号化するデータは、16バイトの倍数にする必要があります。上記の例では、Array.Resizeメソッドを使って16バイトの倍数にしています。

また、ProtectedMemory.Protectメソッドでは、第1パラメータ"userData"で指定されたデータを暗号化されたデータが上書きします。つまり上記の例では、"encryptedData"に暗号化されたデータが入ります。

2番目のパラメータ"scope"には、MemoryProtectionScope構造体のメンバを指定します。上記のようにSameProcessを指定すると、同じプロセスだけが復号化できます。プロセスを再起動すると、復号化できなくなります。SameLogonを指定すると、同じユーザーだけが復号化できます。ログオフしてからログインすると、復号化できなくなります。CrossProcessを指定すると、すべてのプロセスで復号化できます。コンピュータを再起動すると、復号化できなくなります。

さらに、Protectメソッドで指定した"scope"と同じ値をUnprotectメソッドでも指定しないと、正しく復号化されません。

ProtectedDataクラスとProtectedMemoryクラスの違いと使い分け

ProtectedDataクラスとProtectedMemoryクラスをどのように使い分ければよいかについては、「Managed DPAPI Part II: ProtectedMemory」で説明されています。これによると、ProtectedDataクラスとProtectedMemoryクラスの一番の違いは、ProtectedDataクラスで暗号化されたデータはコンピュータを再起動した後でも復号化できるのに対して、ProtectedMemoryクラスで暗号化されたデータはコンピュータを再起動した後は復号化できない点です。よって、暗号化されたデータをファイルやデータベースなどに保存して使用するのであればProtectedDataクラスを使い、暗号化されたデータをアプリケーションが起動している間だけ使うのであればProtectedMemoryクラスを使うというのが、基本的な使い方になります。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。