デスクトップアプリで APIキー・トークン・パスワード を扱うとき、設定ファイルに平文で書くのは避けたいものです。とはいえ「暗号化するなら鍵はどこに置く?」という鍵の保管問題が立ちはだかります。
Windows には、この問題を OS 側で肩代わりしてくれる DPAPI(Data Protection API) があります。暗号鍵をユーザー(またはマシン)の資格情報から OS が導出・管理してくれるため、自分で鍵を生成・保存する必要がありません。この記事では C# での使い方を、守れる範囲と守れない範囲まで正確に整理します。開発全体の抜け漏れ防止は デスクトップアプリ開発チェックリスト の「機密情報の管理」も参照してください。
Table of contents
Open Table of contents
DPAPI とは
DPAPI は Windows が提供する暗号化サービスです。ポイントは 「鍵を自分で持たない」 こと。
- 暗号鍵は ユーザーのログオン資格情報(またはマシン)から OS が導出
- アプリは「暗号化して」「復号して」と頼むだけ
- 復号できるのは 同じユーザー(または同じマシン)だけ
C# から使う方法は主に2つあります。
| 方法 | 名前空間 | 特徴 |
|---|---|---|
| ProtectedData | System.Security.Cryptography | 同期・byte[] ベース。WPF/コンソール/WinUI 何でも。手軽 |
| DataProtectionProvider | Windows.Security.Cryptography.DataProtection | 非同期・IBuffer ベース。WinUI/UWP など WinRT を使うアプリ向け |
方法A:ProtectedData(最も手軽)
ProtectedData.Protect / Unprotect で byte[] を暗号化・復号します。
⚠️ .NET 5 以降(.NET Core 含む)では NuGet パッケージ
System.Security.Cryptography.ProtectedDataの参照が必要です(.NET Framework では標準で利用可)。
using System.Security.Cryptography;
using System.Text;
static class SecretStore
{
// 追加の複雑さを与える固定エントロピー(アプリ固有の値にする)
static readonly byte[] Entropy = Encoding.UTF8.GetBytes("MyApp.v1");
public static byte[] Protect(string secret) =>
ProtectedData.Protect(
Encoding.UTF8.GetBytes(secret),
Entropy,
DataProtectionScope.CurrentUser);
public static string Unprotect(byte[] encrypted) =>
Encoding.UTF8.GetString(
ProtectedData.Unprotect(encrypted, Entropy, DataProtectionScope.CurrentUser));
}
使うときは、暗号化した byte[] をそのままファイルへ保存します(保存先は AppData などが定番)。
// 保存
byte[] encrypted = SecretStore.Protect("sk-xxxxxxxx"); // APIキーなど
File.WriteAllBytes(path, encrypted);
// 読み込み
string apiKey = SecretStore.Unprotect(File.ReadAllBytes(path));
暗号化に使った エントロピーは復号時にも同じ値が必要です。失敗時は CryptographicException が飛ぶので、try/catch でハンドリングします。
方法B:DataProtectionProvider(WinUI 向け)
WinUI/UWP など WinRT が使えるアプリでは、非同期の DataProtectionProvider が使えます。暗号化時は descriptor を指定し、復号時は引数なしのコンストラクターを使う点がポイントです。
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.DataProtection;
using Windows.Storage.Streams;
// 暗号化:現在のユーザーに紐付け("LOCAL=user")
static async Task<IBuffer> ProtectAsync(string secret)
{
var provider = new DataProtectionProvider("LOCAL=user");
IBuffer input = CryptographicBuffer.ConvertStringToBinary(
secret, BinaryStringEncoding.Utf8);
return await provider.ProtectAsync(input);
}
// 復号:descriptor を渡さないコンストラクターを使う
static async Task<string> UnprotectAsync(IBuffer protectedBuffer)
{
var provider = new DataProtectionProvider();
IBuffer output = await provider.UnprotectAsync(protectedBuffer);
return CryptographicBuffer.ConvertBinaryToString(
BinaryStringEncoding.Utf8, output);
}
"LOCAL=user" / "LOCAL=machine" は enterprise authentication ケーパビリティ不要で使えます(AD グループ向けの SID/SDDL 指定は別途ケーパビリティが必要)。
CurrentUser と LocalMachine の違い
どちらの方法でも、**保護の範囲(スコープ)**を選べます。
| スコープ | 復号できる範囲 | 用途 |
|---|---|---|
CurrentUser / "LOCAL=user" | 暗号化した**本人(同一ユーザー)**のみ | 通常はこちら |
LocalMachine / "LOCAL=machine" | その PC の全ユーザー・全プロセス | サービス等、信頼できる環境のみ |
LocalMachine は同じ PC の他アカウントでも復号できてしまうため、基本は CurrentUser を選びます。
重要:DPAPI が「守れない範囲」
ここを誤解すると危険です。DPAPI は万能ではありません。
- 同一ユーザーで動く別プロセスは復号できる:あなたの権限で動くコード(=そのユーザーを乗っ取ったマルウェア含む)は、同じように復号できます。DPAPI は 「他ユーザー」や「オフラインでのファイル盗難」に対する保護であって、同一ユーザー実行のコードからは守れません
- エントロピーは”鍵”ではない:コードに埋め込む固定値なので、バイナリを解析されれば同じ値を再現できます。パスワード代わりにはなりません
- 復号できなくなるリスク:ユーザープロファイルの削除・OS 再インストール・別 PC では復号できません。重要データは再入力できる導線やバックアップを用意しておきます
- Windows 専用:
ProtectedDataは他 OS でPlatformNotSupportedExceptionを投げます - サーバー/複数マシン共有には不向き:その用途では Azure Key Vault などの外部シークレット管理を検討します
どれを選ぶか
- WPF・コンソール・手軽に同期で使いたい → ProtectedData
- WinUI・パッケージアプリで非同期に使いたい → DataProtectionProvider
- ID+パスワードの資格情報そのものを保存したい →
Windows.Security.Credentials.PasswordVaultも選択肢(資格情報マネージャーに安全に保存)
まとめ
- DPAPI は 鍵の保管問題を OS に任せられる、デスクトップアプリの機密情報保護の第一歩
- C# では
ProtectedData(手軽) かDataProtectionProvider(WinUI 向け) を使う - スコープは基本
CurrentUser - ただし 同一ユーザー実行のコードからは守れない・環境が変わると復号不能という限界を理解して使う
平文でAPIキーを置いていないか——気になったら、まず 開発チェックリスト で全体を見直してみてください。
