.NET で外部に保存するデータ・レジストリの値やプロセス内・プロセス間にデータを保護・解除に使用するデータ保護API(DPAPI) を使用することがあったので覚書として記載します。データ保護APIを使用することで、パスワードやDBへの接続文字列を安全に保存できるようになります。
動作確認環境は次の環境で行っています。
- 開発環境:Visual Studio 2008 Professional
- 動作確認:Windows Server 2003
- .NET 3.5 (.NET 2.0でサポートされています)
ProtectedData と ProtectedMemoryですが、以下の表にしめすデータの用途にしたがって使い分けるとよいそうです。
(http://blogs.msdn.com/shawnfa/archive/2004/05/17/133650.aspxより抜粋)
| データの用途 | ProtectedData | ProtectedMemory | System.Security.Cryptography のアルゴリズムを実装するクラス |
| ファイルに書き出す | ○ | ||
| データベースに保存する | ○ | ||
| アプリケーションの内部でのみ使用する | ○ | ||
| 複数のアプリケーション間で使用されるが、データを保存しない | ○ | ||
| ネットワークを介して転送される | ○ |
1. サンプルプロジェクトの作成
Visual Studio 2008 を起動してConsole アプリケーションプロジェクトを作成します。プロジェクトの参照の追加で、System.Security.dll を追加します。
1.1 サンプルプログラム
既定で作成される、Program.cs に以下のソースを記載します。ProtectedData と ProtectedMemory を使用しています。ProtectedMemory でデータを暗号化する場合は、バイト配列が16の倍数である必要があるので注意してください。
Protect メソッドを呼び出すときに指定する列挙値 DataProtectionScope や MemoryProtectionScope の値にしたがって、データの保護・解除を行うスコープが決まります。
class Program
{
/// <summary>
/// データ(パスワード、接続文字列など)を安全に保護・解除する方法として
/// データ保護API(DPAPI)を使用してデータを暗号化・複合化するするサンプルです。
///
/// データ保護APIを使用するには、System.Security.dll
/// の参照を追加する必要があります。
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Console.WriteLine("データ保護・解除");
ProtectedDataTest();
Console.ReadLine();
Console.WriteLine("メモリデータ保護・解除");
ProtectedMemoryTest();
Console.ReadLine();
}
/// <summary>
/// ProtectedData を使用してデータの保護、解除を行う
/// </summary>
static void ProtectedDataTest()
{
Console.WriteLine("暗号化処理");
string originaldata = "hello world = こんにちは";
// バイト配列に変換
byte[] sensitiveDatas = Encoding.Unicode.GetBytes(originaldata);
// 暗号化エントロピーはサンプルでは固定。RNGCryptoServiceProvider を使用して、エントロピー用のバイト配列
// も乱数で生成したほうが安全。その場合は、エントロピーバイト配列も別途管理する
byte[] entropy = new byte[] { 1, 2, 3, 4 };
byte[] encrypted = System.Security.Cryptography.ProtectedData.Protect(sensitiveDatas, entropy, System.Security.Cryptography.DataProtectionScope.LocalMachine);
Console.WriteLine("暗号化されたデータ:{0}", Convert.ToBase64String(encrypted));
Console.Write("複合化処理");
byte[] unprotecteddata = System.Security.Cryptography.ProtectedData.Unprotect(encrypted, entropy, System.Security.Cryptography.DataProtectionScope.LocalMachine);
Console.WriteLine("複合化されたデータ:{0}", Encoding.Unicode.GetString(unprotecteddata));
}
/// <summary>
/// ProtectedMemory を使用して、メモリ上のデータの保護、解除を行う
/// </summary>
static void ProtectedMemoryTest()
{
string originaldata = "hello world = こんにちは";
// バイト配列に変換
byte[] sensitiveDatas = Encoding.Unicode.GetBytes(originaldata);
sensitiveDatas = MultipleOf16(sensitiveDatas);
Console.WriteLine("暗号化前:{0}", Convert.ToBase64String(sensitiveDatas));
System.Security.Cryptography.ProtectedMemory.Protect(sensitiveDatas, System.Security.Cryptography.MemoryProtectionScope.SameProcess);
Console.WriteLine("暗号化後:{0}", Convert.ToBase64String(sensitiveDatas));
System.Security.Cryptography.ProtectedMemory.Unprotect(sensitiveDatas, System.Security.Cryptography.MemoryProtectionScope.SameProcess);
Console.WriteLine("複合化後:{0}", Convert.ToBase64String(sensitiveDatas));
string unprotected = Encoding.Unicode.GetString(sensitiveDatas);
Console.WriteLine("データ:{0}", unprotected.TrimEnd('\0'));
}
static byte[] MultipleOf16(byte[] data)
{
using(System.IO.MemoryStream ms = new System.IO.MemoryStream())
using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(ms))
{
ms.Write(data, 0, data.Length);
int padsize = 16 - data.Length % 16;
for (int i = 0; i < padsize; ++i)
{
ms.WriteByte(0x0);
}
return ms.ToArray();
}
}
}
1.2 実行結果
作ったプログラムをデバッグ実行すると次のようにコマンドプロンプトに出力されます。

2. パスワードリセット時の注意
データ保護APIでの暗号化のマスターキーはパスワードを元に生成されるようです。ドメインに参加していないXPなどでDPAPIを使用したユーザに対してパスワードのリセットを行うとデータ保護の解除ができなくなる可能性があります。OS,環境によっては、パスワード リセット ディスクを作成してマスターキーの復元が行えるようにする必要があります。詳細は下記のリンクを参照してください。
DPAPI (データ保護 API) のトラブルシューティング
http://support.microsoft.com/kb/309408/ja
3. まとめ
DPAPIを使用することで機微なデータを容易に比較的安全に保護できます。
今回の説明は以上です。誤り、指摘点などがありましたらご連絡ください。