SecureStringはC#アプリケーションで実用的ですか?


224

ここで私の仮定が間違っている場合は、遠慮なく訂正してください。しかし、私が尋ねている理由を説明させてください。

MSDNから取得SecureString

秘密にしておくべきテキストを表します。テキストは使用時にプライバシー保護のために暗号化され、不要になるとコンピューターのメモリから削除されます。

私はそれがでパスワードやその他の個人情報保管するための完全な意味を成し、これを取得SecureStringオーバーをSystem.String、あなたはどのように制御することができますので、それは実際にメモリに格納されている場合、理由System.String

両方が不変であり、不要になった場合、ガベージコレクションをプログラムでスケジュールすることはできません。つまり、インスタンスは作成後は読み取り専用であり、インスタンスがコンピューターのメモリから削除される時期を予測することはできません。したがって、Stringオブジェクトにパスワード、クレジットカード番号、個人データなどの機密情報が含まれている場合、アプリケーションがコンピューターのメモリからデータを削除できないため、使用後に情報が漏洩する可能性があります。

ただし、GUIアプリケーション(たとえば、sshクライアント)の場合、SecureString はから構築する必要があり System.Stringます。すべてのテキストコントロールは、基礎となるデータ型として文字列を使用します

つまり、これは、ユーザーがキーを押すたびに、そこにあった古い文字列は破棄され、パスワードマスクを使用していても、テキストボックス内の値を表す新しい文字列が作成されることを意味します。また、これらの値のいずれがメモリから破棄されるか、または破棄されるかどうかを制御することはできません

次に、サーバーにログインします。何だと思う?認証のために接続を介して文字列を渡す必要があります。それではSecureStringSystem.String...に変換してみましょう。ガベージコレクションを強制的に実行する(またはバッファに0を書き込む)方法がないヒープ上に文字列があります。

私のポイントはある:あなたが何をすべきかに関係なく、線に沿ってどこか、SecureStringされに行くに変換するSystem.String(ガベージコレクションの任意の保証なし)いくつかの点で、ヒープ上の最低が存在でそれをしますつまり、。

私のポイントではありません:ssh接続への文字列の送信を回避する方法、またはコントロールに文字列を格納させる(カスタムコントロールを作成する)方法を回避する方法があるかどうか。この質問では、「ssh接続」を「ログインフォーム」、「登録フォーム」、「お支払いフォーム」、「食品-あなたがフィード-あなたの子犬-しかし、あなたの子供でないフォーム」に置き換えることができます。等

  • それでは、SecureString実際に使用することが実際に役立つのはどの時点ですか?
  • System.Stringオブジェクトの使用を完全に根絶するために、余分な開発時間を費やす価値はありますか?
  • 全体のポイントであるSecureString、単に時間の量削減するSystem.Stringヒープ上では、(物理的なスワップファイルに移動し、そのリスクを低減しますか)?
  • 攻撃者は、すでにヒープ検査のための手段を持っている場合、その後、彼は最も可能性のいずれか(A)は、既にキーストロークを読むための手段を有している、または(B)、すでに物理的にマシンを持っている ...だからが使用してしまうSecureStringになってから彼を防ぐためにとにかくデータ?
  • これは単に「あいまいさによるセキュリティ」ですか。

質問を厚くしすぎていると申し訳ありませんが、好奇心が私をより良くしました。私の質問の一部またはすべてに遠慮なく答えてください(または私の仮定が完全に間違っていることを教えてください)。:)


25
これSecureStringは実際には安全な文字列ではないことに注意してください。これは、誰かがメモリを検査して機密データを正常に取得できる時間枠を短縮する方法にすぎません。これは完全なものではなく、意図されたものではありません。しかし、あなたが提起しているポイントは非常に有効です。関連:stackoverflow.com/questions/14449579/...
テオドロスChatzigiannakis

1
@TheodorosChatzigiannakis、ええ、それは私が想像したことのほとんどです。今日は一日中過ごしただけで、アプリケーションの存続期間中、パスワードを安全に保存する方法を考え出そうとして頭を悩ませましたが、それだけの価値があるのでしょうか。
Steven Jeffries

1
おそらくそれだけの価値はありません。しかし、再び、あなたは極端なシナリオから防御しているかもしれません。誰かがこの種のコンピューターへのアクセスを取得する可能性が低いという意味では極端ではありませんが、誰かがこの種のアクセスを取得した場合、コンピューターは(すべての意図および目的で)侵害されたと見なされ、私はしませんこれを完全に防ぐために使用できる言語やテクニックはあると思います。
Theodoros Chatzigiannakis 2014年

8
問題が発生する可能性があると誰もが考えなくなってから何年もの間、パスワードが表示されないようにします。破棄されたハードドライブで、ページングファイルに保存されます。メモリのスクラブは、このためのオッズが低いことが重要であり、不変であるため、System.Stringでそれを行うことはできません。Marshal.SecureStringXxx()メソッドが有用であるため、最近ではほとんどのプログラムがアクセスできないインフラストラクチャがネイティブコードと相互運用できる必要があります。または、ユーザーにそれを要求する適切な方法:)
Hans Passant

1
SecureStringは、セキュリティの詳細な手法です。開発コストとセキュリティゲインの間のトレードオフは、ほとんどの状況で好ましくありません。そのための良いユースケースはほとんどありません。
usr

回答:


237

の実際の使用方法は非常に実用的ですSecureString

私がそのようなシナリオを見た回数を知っていますか?(答えは:多くです!):

  • パスワードが誤ってログファイルに表示されます。
  • パスワードがどこかで表示されている-GUIが実行中のアプリケーションのコマンドラインを表示し、コマンドラインがパスワードで構成されていた。おっと
  • メモリプロファイラーを使用して、同僚とソフトウェアのプロファイルを作成します。同僚があなたのパスワードをメモリに表示します。非現実的に聞こえますか?どういたしまして。
  • かつてRedGate、例外が発生した場合にローカル変数の「値」を取得できるソフトウェアを使用していました。これは驚くほど便利です。しかし、誤って「文字列パスワード」をログに記録することは想像できます。
  • 文字列のパスワードを含むクラッシュダンプ。

これらすべての問題を回避する方法を知っていますか?SecureString。一般的に、そのような愚かな間違いをしないようにします。それをどのように回避しますか?管理されていないメモリでパスワードが暗号化されていることを確認することで、実際の値にアクセスできるのは、何をしているのかが90%わかっている場合のみです。

ある意味で、SecureString非常に簡単に機能します。

1)すべてが暗号化されている

2)ユーザーの呼び出し AppendChar

3)管理されていないメモリのすべてを復号化し、文字を追加します

4)管理されていないメモリですべてを再度暗号化します。

ユーザーがコンピューターにアクセスできる場合はどうなりますか?ウイルスはすべてにアクセスできSecureStringsますか?はい。あなたがする必要があるのはRtlEncryptMemory、メモリが復号化されているときに自分自身をフックすることです、あなたは暗号化されていないメモリアドレスの場所を取得し、それを読みます。出来上がり!実際、使用状況を常にスキャンし、SecureStringすべてのアクティビティを記録するウイルスを作成することができます。簡単なことではありませんが、それは可能です。ご覧のとおりSecureString、システムにユーザー/ウイルスが存在すると、の「強力さ」は完全になくなります。

投稿にいくつかのポイントがあります。確かに、「文字列パスワード」を内部で保持するUIコントロールの一部を使用する場合、actualを使用してSecureStringもそれほど役に立ちません。ただし、それでも、上記の愚かさからは保護できます。

また、他の人が指摘したように、WPFはSecurePasswordプロパティSecureStringを通じて内部的に使用するPasswordBoxをサポートしています。

一番下の行です。機密データ(パスワード、クレジットカードなど)がある場合は、を使用しますSecureString。これはC#フレームワークが従うものです。たとえば、NetworkCredentialクラスはパスワードをとして保存しますSecureString。あなたが見ればこれ、あなたはの.NETフレームワークの中で〜80さまざまな用途を経由見ることができますSecureString

SecureString一部のAPIでは文字列を想定しているため、文字列に変換する必要がある場合が多くあります。

通常の問題は次のいずれかです。

  1. APIはGENERICです。機密データがあるかどうかはわかりません。
  2. APIは機密データを処理していることを認識し、「文字列」を使用します。これは単に悪い設計です。

あなたは良い点を上げました:がにSecureString変換されるとstringどうなりますか?これは、最初のポイントのためにのみ発生する可能性があります。たとえば、APIは機密データであることを認識していません。私は個人的にそのようなことが起こっているのを見ていません。SecureStringから文字列を取り出すのはそれほど簡単ではありません。

単純な理由で簡単ではありません。あなたが述べたように、ユーザーがSecureStringを文字列に変換できるようにすることは決して意図されていませんでした。これ、なぜ?

私が見た興味深いケースが1つあります。つまり、WinApi関数のLogonUserはLPTSTRをパスワードとして使用するため、を呼び出す必要がありますSecureStringToGlobalAllocUnicode。これは基本的に、管理されていないメモリに存在する暗号化されていないパスワードを提供します。完了したらすぐにそれを取り除く必要があります。

// Marshal the SecureString to unmanaged memory.
IntPtr rawPassword = Marshal.SecureStringToGlobalAllocUnicode(password);
try
{
   //...snip...
}
finally 
{
   // Zero-out and free the unmanaged string reference.
   Marshal.ZeroFreeGlobalAllocUnicode(rawPassword);
}

サーバーの公開鍵を使用して暗号化されるインスタンスを提供するSecureStringなどの拡張メソッドを使用して、いつでもクラスを拡張できます。その後、サーバーだけがそれを復号化できます。解決された問題:ガベージコレクションは、マネージメモリで決して公開しないため、「元の」文字列を表示することはありません。これはまさに()で行われていることです。ToEncryptedString(__SERVER__PUBLIC_KEY)stringSecureStringPSRemotingCryptoHelperEncryptSecureStringCore(SecureString secureString)

そして、ほぼ関連性のあるものとして、Mono SecureStringはまったく暗号化しません。実装がコメントアウトされているのは、それを待つ"それがどういうわけかnunitテストの破損を引き起こす"ためです。これは私の最後のポイントになります。

SecureStringどこでもサポートされていません。プラットフォーム/アーキテクチャがをサポートしていないSecureString場合は、例外が発生します。ドキュメントでサポートされているプラ​​ットフォームのリストがあります。


私が思うだろうSecureString多くなり、よりその個々の文字にアクセスするためのメカニズムがあった場合には実用的。このようなメカニズムの効率はSecureStringTempBuffer、パブリックメンバーのない構造体型を使用することで強化できrefSecureString「1文字の読み取り」メソッドに渡すことができます[暗号化/復号化が8文字のチャンクで処理される場合、そのような構造体はデータを保持できます。 8文字と、SecureString] 内の最初の文字の位置。
スーパーキャット2014年

2
私が実行するすべてのソースコード監査でこれにフラグを立てます。また、使用SecureStringする場合はスタック全体で使用する必要があることも繰り返します。
ケーシー

1
.ToEncryptedString()拡張を実装しましたが、証明書を使用しました。私がこれをすべて間違っているのかどうかを見て、私に知らせていただけませんか。その安全なengouhgの期待して-私gist.github.com/sturlath/99cfbfff26e0ebd12ea73316d0843330を
Sturla

@Sturla — EngineSettings拡張メソッドには何がありますか?
InteXX


15

あなたの仮定におけるいくつかの問題があります。

まず、SecureStringクラスにはStringコンストラクタがありません。オブジェクトを作成するには、オブジェクトを割り当ててから、文字を追加します。

GUIまたはコンソールの場合、押された各キーを安全な文字列に非常に簡単に渡すことができます。

このクラスは、格納されている値に誤ってアクセスできないように設計されています。つまり、stringからパスワードとしてを直接取得することはできません。

そのため、たとえばWebを介して認証するためにそれを使用するには、安全な適切なクラスを使用する必要があります。

.NETフレームワークには、SecureStringを使用できるいくつかのクラスがあります。

  • WPFのPasswordBoxコントロールは、パスワードをSecureStringとして内部的に保持します。
  • System.Diagnostics.ProcessInfoのPasswordプロパティはSecureStringです。
  • X509Certificate2のコンストラクタは、パスワードにSecureStringを取ります。

(もっと)

結論として、SecureStringクラスは便利ですが、開発者による注意が必要です。

これはすべて、例とともに、MSDNのSecureStringのドキュメントで詳しく説明されています


10
私はあなたの回答の最後から3番目の段落をよく理解していません。SecureStringシリアル化せずにネットワーク経由で転送するにはどうすればよいstringですか?質問のOPのポイントは、実際に安全に保持されている値を実際に使用したいときがSecureStringあるということだと思います。つまり、そこから取得する必要があり、もはや安全ではありません。すべてのフレームワーククラスライブラリには、SecureString入力として直接(私が見る限り)受け入れるメソッドはほとんどないのでSecureString、そもそもa 内に値を保持する意味は何ですか?
stakx-2014年

@DamianLeszczyński-Vash、SecureStringには文字列コンストラクタがないことを知っていますが、私のポイントは、(A)SecureStringを作成して文字列から各文字を追加するか、(B)ある時点で使用する必要があるということです何らかのAPIに渡すために、SecureStringに文字列として格納される値。そうは言っても、あなたはいくつかの良い点を持ち出します、そして私はいくつかの非常に特殊なケースでそれがどのように役立つことができるかを見ることができます。
スティーブンジェフリーズ2014年

1
@stakxを取得してソケット経由char[]で送信することにより、ネットワーク経由で送信charします-プロセスでガベージコレクション可能なオブジェクトを作成しません。
user253751 2014年

Char []は収集可能で変更可能であるため、上書きできます。
Peter Ritchie

もちろん、その配列を上書きするのを忘れるのも簡単です。どちらの方法でも、charsを渡すすべてのAPIもコピーを作成できます。
cHao

10

SecureStringは、次の場合に役立ちます。

  • 文字ごとに(たとえば、コンソール入力から)構築するか、アンマネージAPIから取得する

  • アンマネージAPI(SecureStringToBSTR)に渡して使用します。

これをマネージ文字列に変換する場合、その目的は無効になっています。

コメントに応じて更新

...またはあなたが言及したようなBSTR、これはもう安全ではないようです

BSTRに変換された後、BSTRを消費するアンマネージコンポーネントはメモリをゼロにすることができます。アンマネージメモリは、この方法でリセットできるという意味で、より安全です。

ただし、SecureStringをサポートする.NET FrameworkのAPIは非常に少ないため、現在のところ、その値は非常に限られていると言えます。

私が目にする主な使用例は、ユーザーが非常に機密性の高いコードまたはパスワードを入力する必要があるクライアントアプリケーションです。ユーザー入力を1文字ずつ使用してSecureStringを作成し、これをアンマネージAPIに渡して、使用後に受け取ったBSTRをゼロにすることができます。以降のメモリダンプには、機密性の高い文字列は含まれません。

サーバーアプリケーションでは、それがどこで役立つかを確認するのは困難です。

アップデート2

SecureStringを受け入れる.NET APIの一例は、X509Certificateクラスのこのコンストラクタです。ILSpyまたは同様のものでspelunkすると、SecureStringが内部的にアンマネージバッファー(Marshal.SecureStringToGlobalAllocUnicode)に変換され、()で終了するとゼロにされることがわかりますMarshal.ZeroFreeGlobalAllocUnicode


1
+1、しかしあなたはいつも「その目的を打ち負かす」ことになってしまうのではないでしょうか?フレームワーククラスライブラリのほとんどのメソッドがa SecureStringを入力として受け入れないことを考えると、それらはどのように使用されますか?あなたはいつもstring遅かれ早かれ(またはBSTRあなたが言及したように、これはもう安全ではないように)変換し直す必要があります。なぜそんなに気にSecureStringしないの?
stakx-2014年

5

マイクロソフトではSecureString、新しいコードの使用を推奨していません。

SecureStringクラスのドキュメントから:

重要

SecureString新規開発のためにクラスを使用することはお勧めしません。詳細については、「使用しないでください」を参照してSecureStringください

どちらがお勧めですか:

SecureString新しいコードには使用しないでください。コードを.NET Coreに移植するときは、配列の内容がメモリ内で暗号化されていないことを考慮してください。

資格情報を処理する一般的なアプローチは、資格情報を回避し、代わりに、証明書やWindows認証などの他の認証手段に依存することです。GitHubで。


4

あなたが正しく識別したように、決定的な消去にSecureString勝る特定の利点がありますstring。この事実には2つの問題があります。

  1. 他の人が述べているように、あなたが自分で気づいたように、これだけでは十分ではありません。プロセスのすべてのステップ(入力の取得、文字列の構築、使用、削除、移動など)が、の使用目的を損なうことなく行われることを確認する必要がありますSecureString。これはstring、機密情報を格納するGC管理の不変変数やその他のバッファーを作成しないように注意する必要があることを意味します(または、それも追跡する必要があります)。多くのAPIはstring、ではなくで作業する方法を提供するだけなので、実際にこれを実現するのは必ずしも容易ではありませんSecureString。そして、あなたがすべてを正しく管理したとしても...
  2. SecureString非常に特定の種類の攻撃から保護します(一部の攻撃では、それほど信頼性が高くありません)。たとえばSecureString、攻撃者がプロセスのメモリをダンプし、機密情報を正常に抽出できる時間ウィンドウを縮小できます(ここでも、正しく指摘したとおり)が、ウィンドウが小さすぎて攻撃者が実行できないことを期待しています。あなたの記憶のスナップショットを取ることはセキュリティとはまったく考えられていません。

それで、あなたはいつそれを使うべきですか?SecureStringすべてのニーズに対応できるものを使用している場合にのみ、特定の状況でのみこれが安全であることに注意してください。


2
攻撃者が特定の時間にプロセスのメモリのスナップショットをとることができると想定しています。それは必ずしもそうではありません。クラッシュダンプを考えてみましょう。機密データを使用してアプリケーションを実行した後にクラッシュが発生した場合、クラッシュダンプに機密データが含まれていてはなりません。

4

以下のテキストはHP Fortify静的コードアナライザーからコピーされます

要約: PassGenerator.csのメソッドPassString()は、機密データを安全でない方法(つまり文字列)で格納し、ヒープの検査を介してデータを抽出できるようにします。

説明: メモリに格納されている機密データ(パスワード、社会保障番号、クレジットカード番号など)がマネージStringオブジェクトに格納されていると、漏洩する可能性があります。文字列オブジェクトは固定されていないため、ガベージコレクターはこれらのオブジェクトを自由に再配置して、メモリにいくつかのコピーを残すことができます。これらのオブジェクトはデフォルトでは暗号化されていないため、プロセスのメモリを読み取ることができる誰もが内容を見ることができます。さらに、プロセスのメモリがディスクにスワップアウトされると、暗号化されていない文字列の内容がスワップファイルに書き込まれます。最後に、Stringオブジェクトは不変であるため、メモリからのStringの値の削除は、CLRガベージコレクターによってのみ実行できます。CLRのメモリが不足していない限り、ガベージコレクターを実行する必要はないため、ガベージコレクションがいつ行われるかについての保証はありません。

推奨事項: 機密データを文字列などのオブジェクトに保存する代わりに、SecureStringオブジェクトに保存してください。各オブジェクトは、常にその内容を暗号化された形式でメモリに保存します。


3

この点に対処したいと思います。

攻撃者は、すでにヒープ検査のための手段を持っている場合、それらはほとんどのいずれかの(A)は、既にキーストロークを読むための手段、または(B)は、既にしている物理的なマシンを持っている ...だからが使用してしまうSecureStringになってからそれらを防ぐためにとにかくデータ?

攻撃者はコンピュータとアプリケーションへの完全なアクセス権を持っていない可能性がありますが、プロセスのメモリの一部にアクセスする手段を持っている可能性があります。これは通常、特別に構成された入力によってアプリケーションがメモリの一部を公開または上書きする可能性があるときに、バッファオーバーランなどのバグが原因で発生します。

HeartBleedリークメモリ

たとえば、ハートブリードを見てみましょう。特別に作成されたリクエストにより、コードがプロセスのメモリのランダムな部分を攻撃者に公開する可能性があります。攻撃者はメモリからSSL証明書を抽出できますが、必要なのは不正なリクエストを使用することだけです。

マネージコードの世界では、バッファオーバーランが問題になることはほとんどありません。また、WinFormsの場合、データはすでに安全でない方法で格納されており、それについては何もできません。これにより、保護がSecureStringほとんど役に立たなくなります。

ただし、GUI を使用するようにプログラムできます。SecureStringそのような場合、メモリ内のパスワードの可用性のウィンドウを減らすことは価値があります。たとえば、WPFのPasswordBox.SecurePasswordのタイプはSecureStringです。


うーん、知って間違いなく興味深い。正直なところ、私は実際にはSystem.String値をメモリから取得するために必要な攻撃の種類についてはそれほど心配していません。純粋な好奇心から質問しているだけです。ただし、情報をありがとう!:)
スティーブン・ジェフリーズ2014年

2

少し前に、Javaクレジットカードの支払いゲートウェイに対してac#インターフェイスを作成する必要があり、互換性のある安全な通信キーの暗号化が必要でした。Javaの実装はかなり具体的であり、保護されたデータを特定の方法で操作する必要があったためです。

この設計は、SecureStringを使用するよりも非常に使いやすく、簡単であることがわかりました...使用したい人にとっては、自由に感じてください。法的な制限はありません:-)。これらのクラスは内部クラスであり、公開する必要がある場合があることに注意してください。

namespace Cardinity.Infrastructure
{
    using System.Security.Cryptography;
    using System;
    enum EncryptionMethods
    {
        None=0,
        HMACSHA1,
        HMACSHA256,
        HMACSHA384,
        HMACSHA512,
        HMACMD5
    }


internal class Protected
{
    private  Byte[] salt = Guid.NewGuid().ToByteArray();

    protected byte[] Protect(byte[] data)
    {
        try
        {
            return ProtectedData.Protect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }

    protected byte[] Unprotect(byte[] data)
    {
        try
        {
            return ProtectedData.Unprotect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }
}


    internal class SecretKeySpec:Protected,IDisposable
    {
        readonly EncryptionMethods _method;

        private byte[] _secretKey;
        public SecretKeySpec(byte[] secretKey, EncryptionMethods encryptionMethod)
        {
            _secretKey = Protect(secretKey);
            _method = encryptionMethod;
        }

        public EncryptionMethods Method => _method;
        public byte[] SecretKey => Unprotect( _secretKey);

        public void Dispose()
        {
            if (_secretKey == null)
                return;
            //overwrite array memory
            for (int i = 0; i < _secretKey.Length; i++)
            {
                _secretKey[i] = 0;
            }

            //set-null
            _secretKey = null;
        }
        ~SecretKeySpec()
        {
            Dispose();
        }
    }

    internal class Mac : Protected,IDisposable
    {
        byte[] rawHmac;
        HMAC mac;
        public Mac(SecretKeySpec key, string data)
        {

            switch (key.Method)
            {
                case EncryptionMethods.HMACMD5:
                    mac = new HMACMD5(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA512:
                    mac = new HMACSHA512(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA384:
                    mac = new HMACSHA384(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA256:
                    mac = new HMACSHA256(key.SecretKey);

                break;
                case EncryptionMethods.HMACSHA1:
                    mac = new HMACSHA1(key.SecretKey);
                    break;

                default:                    
                    throw new NotSupportedException("not supported HMAC");
            }
            rawHmac = Protect( mac.ComputeHash(Cardinity.ENCODING.GetBytes(data)));            

        }

        public string AsBase64()
        {
            return System.Convert.ToBase64String(Unprotect(rawHmac));
        }

        public void Dispose()
        {
            if (rawHmac != null)
            {
                //overwrite memory address
                for (int i = 0; i < rawHmac.Length; i++)
                {
                    rawHmac[i] = 0;
                }

                //release memory now
                rawHmac = null;

            }
            mac?.Dispose();
            mac = null;

        }
        ~Mac()
        {
            Dispose();
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.