本文へスキップ
ysk-hack-log
戻る

C#でDPAPIを使い機密情報を安全に保存する|ProtectedDataでAPIキーを暗号化

「C#でDPAPIを使い機密情報を安全に保存する|ProtectedDataでAPIキーを暗号化」の記事アイキャッチ

デスクトップアプリで APIキー・トークン・パスワード を扱うとき、設定ファイルに平文で書くのは避けたいものです。とはいえ「暗号化するなら鍵はどこに置く?」という鍵の保管問題が立ちはだかります。

Windows には、この問題を OS 側で肩代わりしてくれる DPAPI(Data Protection API) があります。暗号鍵をユーザー(またはマシン)の資格情報から OS が導出・管理してくれるため、自分で鍵を生成・保存する必要がありません。この記事では C# での使い方を、守れる範囲と守れない範囲まで正確に整理します。開発全体の抜け漏れ防止は デスクトップアプリ開発チェックリスト の「機密情報の管理」も参照してください。

Table of contents

Open Table of contents

DPAPI とは

DPAPI は Windows が提供する暗号化サービスです。ポイントは 「鍵を自分で持たない」 こと。

C# から使う方法は主に2つあります。

方法名前空間特徴
ProtectedDataSystem.Security.Cryptography同期・byte[] ベース。WPF/コンソール/WinUI 何でも。手軽
DataProtectionProviderWindows.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 は万能ではありません。

どれを選ぶか

まとめ

平文でAPIキーを置いていないか——気になったら、まず 開発チェックリスト で全体を見直してみてください。

参考(公式ドキュメント)


この記事をシェア:

前の記事
Windowsデスクトップアプリ開発の忘れがちな作業チェックリスト|C#/WinUIで設計から配布まで
次の記事
Raspberry PiでBlazor(C#)を動かす|構成とBlazor Server/WASMの選び方【連載①】