ここでは、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クラスは、Windows 2000以降のOSで使用できます。ProtectedData.Protectメソッドで暗号化し、Unprotectメソッドで復号化します。
以下の例では、Button1ボタンを押すことによってTextBox1の値を暗号化し、暗号化された値をTextBox2に表示しています。また、Button2ボタンを押すことによってTextBox2の値を復号化し、復号化された値をTextBox1に表示しています。「system.security.dll」を参照設定に追加する必要があります。
'暗号化で使用する追加のバイト配列 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
//暗号化で使用する追加のバイト配列 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クラスはWindows XP以降のOSで使用できます。ProtectedDataクラスと同様に、ProtectedMemory.Protectメソッドで暗号化し、Unprotectメソッドで復号化します。
以下の例では、Button3ボタンを押すことによってTextBox1の値を暗号化し、暗号化された値をencryptedDataフィールドに格納しています。また、Button4ボタンを押すことによってencryptedDataフィールドのデータを復号化し、復号化された値をTextBox1に表示しています。「system.security.dll」を参照設定に追加する必要があります。
'暗号化されたデータ 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
//暗号化されたデータ 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クラスをどのように使い分ければよいかについては、「Managed DPAPI Part II: ProtectedMemory」で説明されています。これによると、ProtectedDataクラスとProtectedMemoryクラスの一番の違いは、ProtectedDataクラスで暗号化されたデータはコンピュータを再起動した後でも復号化できるのに対して、ProtectedMemoryクラスで暗号化されたデータはコンピュータを再起動した後は復号化できない点です。よって、暗号化されたデータをファイルやデータベースなどに保存して使用するのであればProtectedDataクラスを使い、暗号化されたデータをアプリケーションが起動している間だけ使うのであればProtectedMemoryクラスを使うというのが、基本的な使い方になります。