Windowsアプリの機密情報保存 - DPAPIで平文設定を避ける

· 更新日: · · Windows開発, セキュリティ, DPAPI, C# / .NET, Win32

更新履歴(5件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
暗号文とマスターキーがどちら側に残るのかを示す図と、保存方式を上から順に検討する選定フローの図を追加しました。選定フローには、10.3節のとおり資格情報ストア(Credential Locker / Credential Manager)へ向かう分岐も入れています。本文の説明は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
復号できなくなるケースの表で「ローミングプロファイル / 別PCへのコピー」を1行にまとめていたのを分けました。ローミングプロファイルなら鍵材料も一緒に移動するため別PCでも復号できます(Microsoft Learnの`CryptProtectData`に明記があります)。同じ扱いにすると、移行手順で不要に資格情報を作り直すことになります。
重複していた記述を統合しました(逃がし先の一覧と、選びたくなる動機を本来の章へ移し、元の箇所は参照に圧縮。内容は失っていません)。参照の追加が必要なことを明示し(.NET Core以降はNuGetパッケージが必須で、どの共有フレームワークにも含まれません)、Credential LockerとCredential Managerとの比較表、運用で復号できなくなる4ケースと対処を追加しました。
初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589650)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「Windowsアプリの機密情報保存 - DPAPIで平文設定を避ける」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589650 https://staging.comcomponent.com/blog/2026/03/16/000-windows-app-secret-storage-best-practices-dpapi/

DOI(最新版)
10.5281/zenodo.21589650
DOI(この版)
10.5281/zenodo.21732679

前回の「Windowsアプリ開発における最低限のセキュリティを守るためのチェックリスト」では、 「秘密情報をソースコードや平文設定に置かない」「Win32 / .NET なら DPAPI / ProtectedData を使う」という最低限の線を書きました。

今回はその中でも、「DPAPI を使って最低限平文よりはましにする」をもう少し掘り下げます。

対象は次のような Windows アプリです。

  • WPF / WinForms / WinUI のデスクトップアプリ
  • C# / .NET の Windows クライアント
  • ローカル設定ファイルに、接続先資格情報や API トークンを保存したくなるアプリ

ここで扱うのは、「ローカルに保存せざるを得ない秘密を、少なくとも appsettings.json の平文のまま放置しない」ための現実的な設計です。 「どんな攻撃者にも勝てる完全防御」の話ではありません。そこは話を盛りすぎると、セキュリティが急に怪談になります。

1. まず結論

実務では、この順番で考えるのが分かりやすいです。

  1. そもそもクライアントに長期秘密を持たせない
    • Windows 認証、統合認証、ユーザーの対話ログイン、サーバー側の秘密管理を優先する
  2. どうしてもローカル保存が必要なら、平文では置かない
    • Windows ならまず DPAPI / ProtectedData を第一候補にする
  3. 通常のデスクトップアプリは DataProtectionScope.CurrentUser を基本にする
    • LocalMachine は用途がかなり限られる
  4. DPAPI は「端末が完全に侵害された状況」まで守るものではない
    • 同じユーザー権限で動くコードは、基本的にそのユーザーが復号できるものを復号できる

そして、この記事で一番大事な論点はここです。

「どうせ秘密鍵はどこかに保存しないといけないのだから、平文でも DPAPI でもセキュリティ的には同じではないか?」

これは半分正しくて、結論は間違いです。

  • 自前 AES + 鍵を同じアプリや同じ設定に置くなら、かなり平文に近いです
  • でも DPAPI は鍵管理を OS に寄せ、復号できる主体を“その Windows ユーザー”または“そのコンピュータ”に結び付けます
  • その結果、設定ファイル単体の流出、別 PC への持ち出し、誤送付、バックアップ流出、リポジトリ混入のような事故に対する強さが大きく変わります

つまり、 「鍵がどこかにある」という抽象論だけを見ると同じに見えても、 「誰が・どの文脈で・どれだけ簡単に使えるか」が全然違う、ということです。

玄関マットの下に鍵を置くのと、管理室で本人確認してから鍵を出すのを、同じと言い切るのは少し乱暴です。

この記事の知識マップ

この記事はDPAPI(ProtectedData)を使ってWindowsデスクトップアプリの秘密情報(接続文字列やAPIトークンなど)を平文設定より安全に保存する方法を扱います。平文保存や鍵を暗号文と同じ場所に置く自前暗号は設定ファイル単体の流出でそのまま秘密が漏れますが、DPAPIは復号主体をWindowsユーザーまたはコンピューターに結び付けることでこれを軽減します。通常のデスクトップアプリはCurrentUserスコープを基本にし、LocalMachineは信頼された単一用途のWindowsサービスなど用途を絞るべきです。ただしパスワードリセットやプロファイル再作成、暗号文だけの別PCへのコピーは復号失敗の原因になり、逆にローミングプロファイルなら鍵材料も一緒に移動するため別PCでも復号できます。ユーザー名とパスワードの組を保存したい場合や複数マシンでの秘密共有が必要な場合は、DPAPIよりCredential LockerやCredential Managerの方が適しています。

DPAPIによるWindowsアプリ秘密情報保護の知識マップDPAPIがCurrentUserとLocalMachineのスコープで復号主体を切り替えられること、平文保存や自前暗号がもたらす漏えいリスクをDPAPIが軽減すること、Credential LockerやCredential Managerとの使い分け、復号失敗を招く運用上の落とし穴の関係を示す図原因になり得る軽減する原因になり得る用いるのは非推奨で構成できるで構成できる推奨される対応推奨される対応用いるのは非推奨原因になり得る推奨される対応用いるのは非推奨推奨される対応推奨される対応前提とする両立しない推奨される対応原因になり得る原因になり得る原因になり得る防止するDPAPIアプリの秘密情報秘密情報の平文保存設定ファイル単体の流出による秘密漏えい鍵を暗号文と同じ場所に置く自前暗号全クライアント共通の長期秘密DataProtectionScope.CurrentUserDataProtectionScope.LocalMachine対話ユーザーのデスクトップアプリWindowsサービスDPAPI復号失敗ユーザー名とパスワードの組Credential Locker(PasswordVault)Credential Manager(CredWrite/CredRead)Microsoftアカウント複数マシン・複数ユーザーでの秘密共有管理者によるパスワードのリセットプロファイルの再作成暗号文だけの別PCへのコピーローミングプロファイル

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全21件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. なぜ平文設定が危ないのか

平文保存が危ない理由は、暗号理論よりずっと泥臭いです。実務では、だいたいこういう経路で漏れます。

  • 設定ファイルをそのまま Git に入れてしまう
  • 障害調査用 ZIP に設定ファイルが丸ごと入る
  • サポート問い合わせに設定ファイルを添付してもらう
  • バックアップやファイル共有で第三者が読める
  • ログに接続文字列やトークンがそのまま出る
  • 退職者や別ユーザーが同じ端末上のファイルを読める

平文の最大の問題は、「読めた瞬間に秘密として終わる」ことです。

  • ファイルを開けたら終わり
  • コピーできたら終わり
  • メール添付されたら終わり
  • リポジトリに残ったら半永久的に面倒を見る羽目になる

攻撃者が高度である必要すらありません。 テキストエディタで開ける、というのはそれだけでかなり弱いです。

3. 「秘密鍵はどこかに保存されるのだから同じでは?」への答え

この疑問はもっともです。 そして、ここを雑に答えると、セキュリティ記事が一気にふわっとします。

答えとしては、「どこかに鍵が必要」という意味では yes、だから同じという意味では no です。

3.1. 何が同じで、何が違うのか

確かに、暗号化には最終的に何らかの root of trust が必要です。 秘密は宇宙のどこかから無料では湧いてきません。そこは厳しい世界です。

ただし、セキュリティ上の差は次の 3 点で決まります。

  • 鍵をアプリが直接持つのか
  • 鍵がどの主体に結び付いているのか
  • ファイルだけ盗まれたときに復号できるのか

この違いをざっくり表にすると、こうなります。

方式 設定ファイルを読まれた ファイルだけ別 PC に持ち出された 同じ PC の別ユーザーに読まれた 同じユーザー権限で動くコード
平文 その場で漏れる そのまま漏れる そのまま漏れる 当然読める
自前暗号 + 鍵を同じ設定 / バイナリに置く かなり漏れる かなり漏れる かなり漏れる 当然復号できる
DPAPI + CurrentUser ファイル単体ではすぐ読めない 通常は復号しにくい 通常は復号しにくい 復号できる
DPAPI + LocalMachine ファイル単体ではすぐ読めない その PC 以外では通常は復号しにくい 同じ PC 上なら広く復号できる 復号できる

ここで重要なのは、DPAPI は「ファイルを読める」ことと「秘密を使える」ことを分離する点です。

復号に必要なもの ── OS の側に残り、暗号文には含まれないCurrentUser で保護LocalMachine で保護ユーザーのマスターキーCurrentUser で保護したときコンピューターのマスターキーLocalMachine で保護したとき暗号文設定ファイル / DBの列 / 調査用ZIP に入る= 持ち出されうるものそのユーザーとして動くコード→ 復号できる同じ PC の別ユーザー→ 復号できない同じ PC 上のコード→ 別ユーザーでも復号できる暗号文だけを別の PC へコピーする→ 復号できないローミングプロファイルごと移動する→ 鍵材料も一緒に動くので復号できる(6.5節)

図1: 暗号文だけが持ち出せる側にあり、復号に必要なマスターキーは OS の側に残る。ただし LocalMachine の届く範囲はその PC 全体で、同じ PC の別ユーザーは締め出せない

平文では、この 2 つが同じです。 ファイルが読めたら、秘密も読めます。

でも DPAPI では、少なくとも CurrentUser なら、

  • その Windows ユーザーとして
  • その Windows の文脈で
  • OS の保護機構を通して

復号する必要があります。

この差は、事故の現場だとかなり大きいです。

3.2. 「それでも同じユーザーなら復号できるのでは?」はその通り

ここは誤魔化さずに書くべき点です。

同じユーザー権限で実行されるコードは、そのユーザーが復号できるものを基本的に復号できます。

つまり、DPAPI は次のような状況を主目的にはしていません。

  • すでにその端末がマルウェアに侵害されている
  • 攻撃者がそのユーザーとしてコード実行できる
  • 端末管理者レベルで完全に乗っ取られている

この状況では、アプリ自身も復号できるのだから、攻撃者コードも復号できてしまいます。 ここで「でも暗号化してあります」は、あまり頼もしくありません。

DPAPI が効くのは、主に “ファイル流出・誤配置・オフライン持ち出し・別ユーザーからの参照” の側です。

ここを取り違えると、

  • 守れるものを過小評価して使わない
  • 守れないものを過大評価して安心する

の両方が起きます。どちらも地味に危ないです。

3.3. だから何がうれしいのか

DPAPI のうれしさを一言で言うと、

「秘密そのものを設定ファイルの可読性から切り離せる」

ことです。

たとえばこういう事故では、平文と DPAPI で差が出ます。

  • 利用者が設定ファイルをサポートへ送ってしまった
  • 調査用 ZIP に設定ファイルが入った
  • バックアップから設定ファイルだけ流出した
  • 共有フォルダ上にコピーされた
  • 開発者が暗号文だけを見て中身を読めない状態にできた

これはかなり現実的なメリットです。 攻撃者を映画みたいな超人にしなくても、日常の事故半径を小さくできます。

4. DPAPI がちょうどよい理由

Windows でローカル保存の秘密を扱うとき、DPAPI が実務でちょうどよい理由は次のとおりです。

4.1. 鍵管理を OS に寄せられる

自前で AES 鍵を生成し、保存し、権限を付け、ローテーションし、漏えい時の影響を考え、改ざん検知まで入れる。 これは思ったより重いです。しかも雑にやると、だいたい鍵を同じ場所に置いて終わります。

DPAPI を使うと、「暗号鍵をどう作ってどこに置くか」問題を、アプリ実装から切り離せます。

その意味で、DPAPI は 「暗号化アルゴリズムを選ぶ API」ではなく、「鍵管理を OS に委譲する API」 として見るほうが本質に近いです。

4.2. 復号主体を Windows ユーザーまたはコンピュータに結び付けられる

通常のデスクトップアプリなら、CurrentUser を選べばよい場面が多いです。

  • そのユーザーがログオンしていること
  • そのユーザー文脈で処理が動くこと

を前提に復号できます。

そのため、暗号文だけを別 PC にコピーしても、そのままでは使いにくいという性質を得られます。

4.3. 改ざん検知まで含めやすい

自前暗号でありがちなのは、 「AES で暗号化したから終わり」として、改ざん検知を忘れることです。

DPAPI は暗号化データに対して整合性保護も持つので、 暗号文を勝手に書き換えられたときの検知まで OS 側の仕組みに乗せやすい、という実務上の利点があります。

4.4. C# / .NET から素直に使える

C# なら System.Security.Cryptography.ProtectedData をそのまま使えます。 余計なライブラリを足さずに済むのも、Windows 専用アプリではかなり助かります。

5. DPAPI で守れるものと、守れないもの

ここははっきり分けておいたほうが安全です。

5.1. 守りやすくなるもの

DPAPI は、少なくともこうした場面では有効です。

  • 設定ファイルの平文漏えい
  • 別 PC へのファイル持ち出し
  • 同じ PC の別ユーザーからの参照(CurrentUser 前提)
  • バックアップや添付ファイルとしての流出
  • 開発・保守現場での「うっかり読めてしまう」状態

5.2. 守れない、または守りが弱いもの

一方で、次の状況では過信しないほうがよいです。

  • 同じユーザー権限で実行される攻撃コード
  • 端末自体の完全な侵害
  • 管理者権限での乗っ取り
  • アプリが復号した後のメモリ上の平文
  • すべてのクライアントに共通で配られる長期秘密

最後の「全クライアント共通の長期秘密」は特に重要です。

たとえば、

  • 全顧客に同じ API キーを埋め込む
  • 全端末で同じ共有パスワードを持つ
  • クライアントだけで完結する固定の復号キーを配る

といった設計は、どこか 1 台から抜かれた時点で全体に波及しやすいです。 理屈は単純で、どこか 1 台でアプリが復号できるなら、その秘密は取り出せるからです。

DPAPI は「その保存場所を平文よりましにする」には有効ですが、 そもそもクライアントに置くべきでない秘密を正当化するものではありません。

この手の秘密は、保存方法を工夫するのではなく、次の方向へ逃がすほうが本筋です。

  • サーバー側に置く
  • クライアントはトークンだけ持つ
  • ユーザーごとの資格情報にする
  • 期限付きトークンにする

6. CurrentUserLocalMachine の使い分け

ここはかなり重要です。雑に選ぶと意味が変わります。

6.1. 基本は CurrentUser

通常の Windows デスクトップアプリでは、まず CurrentUser を基本に考えます。

向いている例:

  • WPF / WinForms / WinUI のユーザー向けデスクトップアプリ
  • ユーザーごとに設定や資格情報を持つアプリ
  • %LocalAppData%%AppData% 配下に設定を持つアプリ

この場合、「その Windows ユーザーの秘密」として扱いやすくなります。

6.2. LocalMachine は用途がかなり限られる

LocalMachine は便利に見えますが、普通のデスクトップアプリでは広すぎます。

向いているのは、たとえばこんな場合です。

  • 信頼された単一用途マシン上の Windows サービス
  • そのマシン上の特定プロセスだけで使う機密
  • ログオンユーザーを跨いで同じ端末で使う必要があるケース

ただし注意点は重いです。

  • その PC 上で動くプロセスから広く復号できる
  • 共用端末、RDS、踏み台端末、複数ユーザーが入る環境では危険になりやすい
  • 「とりあえず全員が使えるから楽」で選ぶと、だいたい後で困る

そして、LocalMachine を選びたくなる動機は、たいていこの 3 つです。

  • ユーザー切り替えしても読める
  • サービスからも読める
  • 動けば便利

どれも「楽」であって、「守れている」ではありません。 普通のデスクトップアプリで LocalMachine を選ぶと、その PC 上の他のプロセスにも復号可能性を広げることになるので、意味がかなり変わります。

6.3. 迷ったらこう考える

  • 通常の UI アプリ -> CurrentUser
  • 本当にマシン単位で守りたい特殊ケース -> LocalMachine
  • どのユーザーでも復号できる必要があるが、端末上に他ユーザーもいる -> たいてい設計から見直したほうがよい

6.4. サービスや impersonation では少し注意が増える

Windows サービスや impersonation を絡めると、CurrentUser の意味は少し重くなります。

  • 実行アカウントは誰か
  • そのアカウントのプロファイルがロードされているか
  • 復号タイミングはどの文脈か

このあたりがずれると、「暗号化できたのに復号できない」になりやすいです。 サービス用途は「とりあえず CurrentUser」で済まないことがあります。

impersonation の場合、Microsoft Learn にも明記されている典型的な失敗が「Key not valid for use in specified state.」です。DPAPI は鍵データをユーザープロファイルに保持するため、プロファイルがロードされていないと復号できません。impersonate する前に、対象ユーザーのプロファイルをロードしておく必要があります。

6.5. 運用で「復号できなくなる」ケースを知っておく

暗号化そのものより、復号できなくなる事故のほうが実務では痛いです。DPAPI は復号できる主体を Windows ユーザー / コンピュータに結び付ける仕組みなので、その結び付きが切れると読めなくなります。

先に知っておきたいのは、次の 5 つです。

ケース 何が起きるか 備えかた
管理者によるパスワードのリセット ユーザーのパスワードに紐づく保護が外れ、DPAPI で保護したデータへアクセスできなくなることがあります。Microsoft のサポート情報にも、管理者がパスワードをリセットした後に DPAPI データへアクセスできなくなる事象として記載があります 秘密を「再取得できる」設計にしておく。復号失敗時に再入力へ誘導する
プロファイルの再作成 新しいプロファイルは別の鍵材料を持つため、以前の暗号文は復号できません 設定ファイルのバージョンを持ち、復号失敗を異常終了にしない
暗号文だけを別 PC へコピー 復号に要る鍵材料はユーザープロファイル側にあるので、CurrentUser の暗号文だけを持っていっても読めません(これは 3.1. で挙げた「強み」の裏面です) 端末ごとに保存し直す前提で設計する
ローミングプロファイル こちらは読めます。プロファイルと一緒に鍵材料も移動するため、Microsoft Learn も「ローミングプロファイルを持つユーザーはネットワーク上の別のコンピューターから復号できる」と明記しています。前の行と同じ扱いにすると、移行手順で不要に資格情報を作り直すことになります 「別 PC だから読めない」と決めつけない。ローミングの有無を確認してから移行手順を決める
サービスの実行アカウント変更 保護したときと復号するときで実行アカウントが変われば、CurrentUser では読めません アカウント変更時に再保護する手順を運用に入れる

要するに、ProtectedData.Unprotect は失敗し得るという前提でコードを書きます。復号に失敗すると CryptographicException が飛ぶので、そこを握って再入力へ寄せます。

using System;
using System.Security.Cryptography;
using System.Text;

// protectedBase64: 設定ファイルから読んだ暗号文 (Base64)
// entropy: 保護時と同じ値を渡す。null でも可
static bool TryUnprotect(string protectedBase64, byte[]? entropy, out string plaintext)
{
    plaintext = string.Empty;

    try
    {
        byte[] plainBytes = ProtectedData.Unprotect(
            Convert.FromBase64String(protectedBase64),
            optionalEntropy: entropy,
            scope: DataProtectionScope.CurrentUser);

        plaintext = Encoding.UTF8.GetString(plainBytes);
        return true;
    }
    catch (CryptographicException)
    {
        // 復号できない = 環境が変わった可能性が高い。
        // ここで落とさず、呼び出し側で再入力の導線へ寄せる
        return false;
    }
    catch (FormatException)
    {
        // Base64 として壊れている場合
        return false;
    }
}

「暗号化したのに復号できない」で問い合わせが来るのは、だいたいこの表のどれかです。

7. 実装の最低限の指針

Windows アプリで「設定ファイルの平文をやめる」だけなら、設計はそこまで複雑にしなくて構いません。 ただし、いくつか外したくないポイントがあります。

7.1. 秘密だけを保護する

設定全体を丸ごと暗号化するより、まずは秘密の項目だけを保護するほうが扱いやすいです。

たとえば、次のように分けます。

  • サーバー URL
  • ユーザー名
  • DB 名
  • 機能フラグ

は平文のままでもよいことが多いです。

一方で、

  • パスワード
  • API トークン
  • リフレッシュトークン
  • 共有フォルダ資格情報

は保護対象です。

この分け方にすると、

  • 設定編集がしやすい
  • 差分確認がしやすい
  • どこが秘密かが明確
  • 全体の運用が単純

になります。

7.2. 保存先は per-user を基本にする

通常のデスクトップアプリなら、保存先は per-user の場所を基本にします。

  • %LocalAppData%\Vendor\App\settings.json
  • %AppData%\Vendor\App\settings.json

少なくとも、インストールフォルダ配下や共有しやすい場所に雑に置かないほうがよいです。

DPAPI で保護していても、保存先の ACL が雑だと、 「暗号文は読まれる」「設定構造は見える」「運用ミスは起きる」 という話になります。防御は一段ではなく、重ねたほうが効きます。

7.3. optionalEntropy は万能の第二鍵ではない

ProtectedData には optionalEntropy を渡せます。 これは便利ですが、“これをバイナリに埋めれば安全になる魔法の第二鍵” ではありません。

  • 同じファイルに置けば秘密にはなりません
  • バイナリに固定値で埋めても、強い秘密とは言えません
  • それでも、用途識別や誤用防止には役立ちます

実務では、

  • アプリ名
  • 用途名
  • バージョン識別子

を固定のバイト列として渡し、「別用途の暗号文を誤って受け付けない」ために使う、くらいがちょうどよいです。

7.4. 暗号文を Git に入れてよい、ではない

ここも地味に大事です。

DPAPI の暗号文は平文よりずっとましですが、 だからといって設定ファイルごとリポジトリに入れてよい、ではありません。

理由は単純で、

  • 暗号文は長く残る
  • いつか同じ端末や同じ文脈が再現されるかもしれない
  • ファイルには秘密以外の情報も入る
  • 「保護されているから雑に扱ってよい」という文化ができる

からです。

「平文よりまし」「どこに置いても安全」 はまったく別です。

7.5. ログに出さない

意外とよくあるのが、復号した後にログへ出して全部台無しになるパターンです。

  • 接続失敗時に接続文字列を丸ごと出す
  • API 401 時に Authorization ヘッダーを残す
  • 例外メッセージに秘密を混ぜる

このへんをやると、設定ファイルの平文をやめても、結局ログが平文倉庫になります。 悲しいけれど、かなり実務です。

8. C# / .NET の最小実装例

8.1. 先に、参照の追加が必要です

ProtectedData は BCL に入っているように見えて、ターゲットフレームワークによって「どこから来るか」が違います。 ここでつまずくと、ProtectedData という型名が解決できません。

ターゲット 必要な作業 提供元
.NET Framework プロジェクトに System.Security アセンブリ参照を追加する System.Security.dll
.NET Core / .NET 5 以降(.NET 6 / 8 を含む) NuGet の System.Security.Cryptography.ProtectedData パッケージを追加する System.Security.Cryptography.ProtectedData.dll

このパッケージは、.NET Core / .NET 5 以降のどの共有フレームワークにも含まれていません。net8.0-windows のような Windows 向けターゲットでも、明示的に参照が必要です。

dotnet add package System.Security.Cryptography.ProtectedData

もう 1 点、実装より先に知っておきたいことがあります。 ProtectedData は Windows 専用です。 DPAPI に依存しているため、Windows 以外のプラットフォームの .NET 上で呼ぶと PlatformNotSupportedException が飛びます。クロスプラットフォーム前提のコードベースなら、10.1. のとおり最初から別設計にします。

8.2. 最小実装

以下は、設定ファイルへ保存する文字列を CurrentUser で保護する最小例です。 用途識別のために固定の optionalEntropy を入れていますが、これを秘密鍵だと思わないでください。

using System;
using System.Security.Cryptography;
using System.Text;

public static class DpapiSecretProtector
{
    // 用途識別用。第二の秘密鍵ではない。
    private static readonly byte[] Entropy =
        Encoding.UTF8.GetBytes("ComComponent:DesktopApp:SettingsSecret:v1");

    public static string ProtectToBase64(string plaintext)
    {
        ArgumentNullException.ThrowIfNull(plaintext);

        byte[] plainBytes = Encoding.UTF8.GetBytes(plaintext);
        byte[] protectedBytes = Array.Empty<byte>();

        try
        {
            protectedBytes = ProtectedData.Protect(
                plainBytes,
                optionalEntropy: Entropy,
                scope: DataProtectionScope.CurrentUser);

            return Convert.ToBase64String(protectedBytes);
        }
        finally
        {
            Array.Clear(plainBytes, 0, plainBytes.Length);

            if (protectedBytes.Length > 0)
            {
                Array.Clear(protectedBytes, 0, protectedBytes.Length);
            }
        }
    }

    public static string UnprotectFromBase64(string protectedBase64)
    {
        ArgumentNullException.ThrowIfNull(protectedBase64);

        byte[] protectedBytes = Convert.FromBase64String(protectedBase64);
        byte[] plainBytes = Array.Empty<byte>();

        try
        {
            plainBytes = ProtectedData.Unprotect(
                protectedBytes,
                optionalEntropy: Entropy,
                scope: DataProtectionScope.CurrentUser);

            return Encoding.UTF8.GetString(plainBytes);
        }
        finally
        {
            Array.Clear(protectedBytes, 0, protectedBytes.Length);

            if (plainBytes.Length > 0)
            {
                Array.Clear(plainBytes, 0, plainBytes.Length);
            }
        }
    }
}

使い方は単純です。

string protectedPassword = DpapiSecretProtector.ProtectToBase64(password);

// JSON などへ保存
// settings.DbPasswordProtected = protectedPassword;

string password = DpapiSecretProtector.UnprotectFromBase64(settings.DbPasswordProtected);

設定ファイルは、たとえばこんな形にできます。

{
  "ApiBaseUrl": "https://api.example.com/",
  "UserName": "app-user",
  "PasswordProtected": "AQAAANCMnd8BFdERjHoAwE..."
}

この形のよいところは、

  • URL やユーザー名は普通に編集できる
  • パスワードだけ保護できる
  • 設定構造が見やすい
  • 平文のまま置くより事故りにくい

ところです。

9. それでも危ない設計

DPAPI を使っていても、次の設計はまだ危ないです。

9.1. 復号後の値を長時間持ち回る

復号した値を、

  • ログに出す
  • 画面に出す
  • 例外に含める
  • 長寿命オブジェクトに乗せっぱなしにする

のは避けたいところです。

「保存時は暗号化」「使用中も安全」 は別問題です。

9.2. 全インストールに共通の秘密を持たせる

全ユーザーに同じ API キーを持たせる設計は、DPAPI で保存しても根本解決になりません。 理由と、代わりにどこへ逃がすかは 5.2. にまとめてあります。

9.3. LocalMachine を「楽だから」で選ぶ

これも本当にありがちです。ただし「楽」であって「守れている」ではありません。 選びたくなる動機と、そのときに何が広がるかは 6.2. のとおりです。 判断に迷ったら 6.3. の 3 行を見てください。

9.4. 自前暗号を足して安心する

DPAPI の代わりに、

  • AES 鍵をソースコードに埋める
  • AES 鍵を設定ファイルの別項目に置く
  • 「ちょっと難読化した文字列」を鍵扱いする

といった実装を入れるのは、たいてい効果が薄いです。

“平文ではない” と “安全である” の間には、かなり大きな溝があります。

10. DPAPI で十分でないケース

DPAPI は便利ですが、万能ではありません。次のケースでは別の選択肢を考えたほうがよいです。

10.1. Windows 以外でも動かしたい

DPAPI / ProtectedData は Windows 向けです。 クロスプラットフォームのアプリなら、その前提では組めません。

10.2. 複数マシン・複数ユーザーで同じ秘密を扱いたい

同じ暗号文を複数の PC で復号したい、複数ユーザーで共用したい、という要件は、 「その端末・そのユーザーに結び付ける」DPAPI の得意分野から外れます。

この場合は、

  • サーバー側の秘密管理
  • 資格情報基盤
  • Windows 認証 / 統合認証
  • アプリ用の資格情報ストア

など、要件に合う別設計を考えるべきです。

10.3. 保存対象がユーザー資格情報そのもの

保存したいものが明確に

  • ユーザー名
  • パスワード

なら、DPAPI で自分のファイルへ書くより、Windows が用意している資格情報ストアのほうが素直です。ここは実務でよく迷うところなので、比較を置きます。

観点 DPAPI (ProtectedData) Credential Locker (PasswordVault) Credential Manager (CredWrite / CredRead)
保存する場所 自分で決めたファイル(暗号文をどう置くかはアプリ次第) Windows が管理する資格情報ストア Windows が管理する資格情報ストア
保存できるもの 任意のバイト列(接続文字列、トークン、設定の一部でもよい) ユーザー名 + パスワードの組 資格情報(種別ごとの構造体)
API System.Security.Cryptography WinRT の Windows.Security.Credentials Win32 (wincred.h / Advapi32.dll)
デスクトップアプリから使えるか そのまま使える WinUI だけでなく WPF / WinForms からも使える(WinRT API 呼び出しの設定は必要) そのまま使える
同期 なし Microsoft アカウントで端末間ローミングされる なし(ローカルのユーザー資格情報セット)
制限 実質なし 1 アプリあたり 20 件まで。大きなデータ用ではない 現在のトークンのログオンセッションに紐づく
管理の主体 アプリ(保存先も ACL も自分で決める) OS(保存場所を設計しなくてよい) OS(コントロール パネルの資格情報マネージャーから管理できる)

使い分けの目安はこうです。

  • 保存対象がユーザー名 + パスワードの組で、件数も少ない -> Credential Locker / Credential Manager が第一候補です。保存場所の設計も ACL も自分で持たずに済みます
  • 保存対象が「ユーザー名 + パスワード」の形をしていない -> 接続文字列、API トークン、リフレッシュトークン、設定ファイルの一部といったものは DPAPI 側が素直です。この記事が扱っているのはこちらです
  • 端末間で引き継ぎたい -> Credential Locker のローミングが効きます。DPAPI の CurrentUser は逆に「引き継がれない」ことが利点なので、ここは目的が正反対です
  • 件数が多い / サイズが大きい -> Credential Locker の 20 件制限に当たります。DPAPI で自前ファイルに寄せます

なお、資格情報ストアも中身は OS の保護機構に乗っているので、「DPAPI より安全」「DPAPI が劣る」という並びではありません。保存したいものの形と、ローミングの要否で選ぶのが実務的です。

そしてどちらを選んでも、新規アプリなら Windows Hello / パスキーのようなパスワードレスを先に検討する価値はあります。そもそも長期パスワードを持たなくて済むなら、それがいちばん強いです。

この記事の中心は、あくまで 「Windows クライアントで設定ファイル平文をやめる」ための DPAPI の実務線です。

11. 実務でのおすすめ優先順位

最後に、実務で迷ったらこの順で考えると整理しやすいです。上から順に検討して、条件を満たさないときだけ下へ降ります。

済む済まない分けられない分けられるその組で、件数も少ないあるそうだドメイン / ローカルアカウントないトークンなど別の形あるない1つでよい(利用者本人、または専用のサービスアカウント)複数のアカウントから復号する必要がある言い切れる言い切れない長期の秘密を端末に持たせずに済むか優先1: 持たない(Windows認証・短命トークン)秘密を利用者ごとに分けられるか共通の鍵になっていないか設計から見直す保存したいものの形はユーザー名 + パスワードの組か(10.3節)端末間で引き継ぐ必要があるかMicrosoft アカウントで同期される端末か(10.3節)Credential Locker のローミングを使うDPAPI では引き継げない。サーバー側で管理する(10.2節)Credential Locker /Credential Manager端末間で引き継ぐ必要があるか同じ秘密を復号するアカウントはいくつか優先3: DPAPI + CurrentUser無人実行ならプロファイルのロードを確認する(6.4節)他の利用者は入らないと言い切れるか優先4: DPAPI + LocalMachine例外扱いとして根拠を残す他ユーザーも復号できてしまう。認証方式のほうを見直す

図2: 保存方式の選定順。秘密の形とローミングの要否を先に見てから DPAPI に入る。LocalMachine は「楽だから」ではなく、他の選択肢が成立しないときの例外として選ぶ

優先 1: そもそも持たない

  • Windows 認証
  • 統合認証
  • 対話ログイン
  • サーバー側で秘密保持
  • 短命トークン

優先 2: ユーザーごとの秘密に寄せる

  • 共通秘密より per-user
  • 長期固定資格情報より更新可能なトークン
  • 全クライアント共通鍵を避ける

優先 3: ローカル保存が必要なら DPAPI

  • 通常は CurrentUser
  • 保存先は per-user
  • 秘密項目だけ保護
  • ログに出さない

優先 4: LocalMachine は例外扱い

  • 本当にマシン単位である必要があるか
  • その端末に他ユーザーは入らないか
  • サービス設計として妥当か

12. まとめ

Windows アプリで設定ファイルに機密情報を保存する必要があるとき、 平文のまま置くのは避けたいです。

そして、

「どうせ鍵はどこかに保存されるのだから同じでは?」

という疑問に対しては、こう答えるのが実務的です。

  • 自前暗号で鍵を同じ場所に置くなら、かなり同じ
  • DPAPI は同じではない
    • 鍵管理を OS に寄せられる
    • 復号主体を Windows ユーザー / コンピュータに結び付けられる
    • ファイル単体の流出を、そのまま秘密流出にしなくて済む
  • ただし
    • 同じユーザー権限で動くコード
    • 完全に侵害された端末
    • クライアントに置くべきでない長期共通秘密

までは解決しない

要するに、DPAPI は万能の城壁ではありません。 でも、設定ファイル平文という素通しの窓ガラスを、最低限まともな窓に替えるくらいの効果はあります。

Windows クライアントの実務では、この差がかなり大きいです。 まずはここを外さないところから始めるのが、いちばん現実的です。

13. 参考資料

  • 前回記事: https://comcomponent.com/blog/2026/03/14/001-windows-app-security-minimum-checklist/
  • Microsoft Learn: CryptProtectData https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
  • Microsoft Learn: ProtectedData https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata?view=windowsdesktop-10.0
  • Microsoft Learn: DataProtectionScope https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotectionscope?view=windowsdesktop-10.0
  • Microsoft Learn: How to: Use Data Protection https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection
  • Microsoft Learn: Credential locker for Windows apps https://learn.microsoft.com/en-us/windows/apps/develop/security/credential-locker
  • Microsoft Learn: CredWrite(Windows Credential Manager の Win32 API) https://learn.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credwritew
  • NuGet: System.Security.Cryptography.ProtectedData https://www.nuget.org/packages/System.Security.Cryptography.ProtectedData
  • Microsoft サポート: 管理者がパスワードをリセットした後に DPAPI データへアクセスできなくなる事象 https://support.microsoft.com/en-us/topic/you-cannot-access-dpapi-data-after-an-administrator-resets-your-password-on-a-windows-server-2012-based-domain-controller-4aa890cd-12b5-fe5c-9e68-06244e70673d

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

Windows I/Oの深層(第6回・最終回) ── フィルタードライバーとミニフィルター:ProcmonとウイルススキャンがI/Oに割り込める理由

Windowsのフィルタードライバーとミニフィルターを図解で解説する連載の最終回です。フィルターマネージャーとアルティチュード、pre/postコールバック、Procmonやウイルス対策が全I/Oを検査できる仕組み、「あの環境だけ遅い」の調査手順まで整理します。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

DPAPIとは何ですか?
DPAPI(Data Protection API)は、Windows が提供するデータ保護の仕組みで、暗号鍵の管理を OS に委譲し、復号できる主体をその Windows ユーザーまたはそのコンピュータに結び付けます。C# / .NET からは System.Security.Cryptography.ProtectedData クラスを通して余計なライブラリを足さずに使えます。暗号化アルゴリズムを選ぶ API というより、鍵管理を OS に委譲する API として見るのが本質に近いです。設定ファイルに保存するパスワードや API トークンを平文のまま置かないための現実的な選択肢です。
鍵はどこかに保存されるのだから、平文でもDPAPIでも同じではないですか?
同じではありません。自前の AES 暗号で鍵を同じアプリや設定ファイルに置くならかなり平文に近いですが、DPAPI は鍵管理を OS に寄せ、復号できる主体を Windows ユーザーまたはコンピュータに結び付けます。その結果、設定ファイル単体の流出、別 PC への持ち出し、誤送付、バックアップ流出、リポジトリ混入のような事故に対する強さが大きく変わります。DPAPI は「ファイルを読める」ことと「秘密を使える」ことを分離できる点が平文との決定的な違いです。
DPAPIで守れないものは何ですか?
同じユーザー権限で実行されるコードは、そのユーザーが復号できるものを基本的に復号できます。そのため、端末がマルウェアに侵害されている状況や、管理者権限で乗っ取られた状況、復号後のメモリ上の平文までは守れません。また、全クライアントに共通で配る長期秘密は、どこか 1 台から抜かれた時点で全体に波及しやすいため、DPAPI で保存しても根本解決になりません。DPAPI が効くのは主にファイル流出・誤配置・オフライン持ち出し・別ユーザーからの参照の側です。
DataProtectionScopeのCurrentUserとLocalMachineはどちらを使うべきですか?
通常の Windows デスクトップアプリでは CurrentUser が基本です。そのユーザーの秘密として扱え、暗号文だけを別 PC にコピーしてもそのままでは使いにくい性質が得られます。LocalMachine はその PC 上で動くプロセスから広く復号できるため、共用端末や複数ユーザーが入る環境では危険になりやすく、信頼された単一用途マシン上の Windows サービスなど用途がかなり限られます。「全員が使えて楽だから」で LocalMachine を選ぶと、だいたい後で困ります。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る