セキュリティ上の理由から、C#文字列のメモリの内容をクリアしようとしています。私はSecureString
クラスを認識していますが、アプリケーションでSecureString
の代わりにString
を使用できません。消去する必要のある文字列は、実行時に動的に作成されます(たとえば、文字列リテラルを消去しようとしていません)。
私が見つけたほとんどの検索結果は、基本的にはString
のコンテンツをクリアすることは不可能であり(文字列は不変であるため)、SecureString
を使用する必要があると述べています。
したがって、私は以下の(安全でないコードを使用した)独自のソリューションを考え出しました。テストはソリューションが機能することを示していますが、ソリューションに何か問題があるかどうかまだわかりませんか?もっと良いものはありますか?
static unsafe bool clearString(string s, bool clearInternedString=false)
{
if (clearInternedString || string.IsInterned(s) == null)
{
fixed (char* c = s)
{
for (int i = 0; i < s.Length; i++)
c[i] = '\0';
}
return true;
}
return false;
}
編集:clearString
が呼び出される前に文字列を移動するGCのコメントのため、次のスニペットはどうですか?
string s = new string('\0', len);
fixed (char* c = s)
{
// copy data from secure location to s
c[0] = ...;
c[1] = ...;
...
// do stuff with the string
// clear the string
for (int i = 0; i < s.Length; i++)
c[i] = '\0';
}
これのあなたの問題はひもが動くことができるということです。 GCが実行されると、コンテンツを新しい場所に移動できますが、古い場所はゼロになりません。問題の文字列をゼロにした場合、そのコピーがメモリ内の他の場所に存在しないという保証はありません。
これが.NETガベージコレクターへの link であり、圧縮について説明しています。
編集:更新に関するあなたの問題はここにあります:
// do stuff with the string
問題は、それがあなたのコントロールを離れると、それが安全であることを確認する能力を失うことです。それが完全にあなたの管理下にあった場合、文字列型のみを使用するという制限はありません。簡単に言えば、この問題は長い間存在しており、誰もがこれを安全に処理する方法を考え出していません。安全に保管したい場合は、他の方法で処理するのが最適です。文字列をクリアすることは、メモリダンプを通じて誰かが文字列を見つけられないようにすることを目的としています。安全な文字列を使用できない場合にこれを停止する最善の方法は、コードが実行されているマシンへのアクセスを制限することです。
標準の「危険な領域に足を踏み入れています」という回答は別として、私自身が説明できると思いますが、次の点を考慮してください。
CLRは、任意の時点で文字列のインスタンスが1つだけであることを保証していません。また、文字列がガベージコレクションされることも保証していません。私が次のことをした場合:
var input = "somestring";
input += "sensitive info";
//do something with input
clearString(input, false);
これの結果は何ですか? (文字列リテラルを使用していないと仮定しましょう。これらは、ある種の環境からの入力です)
「somestring」の内容で文字列が作成されます。別の文字列は「機密情報」のコンテンツで作成され、さらに別の文字列は「somestringsensitive info」のコンテンツで作成されます。後者の文字列のみがクリアされます。「機密情報」はクリアされません。すぐにガベージコレクションされる場合とされない場合があります。
機密情報を含む文字列を常に消去するように注意しても、CLRは文字列のインスタンスが1つだけ存在することを保証しません。
edit:編集に関しては、単に文字列を固定するだけで望ましい効果が得られる場合があります-文字列をコピーする必要はありません別の場所か何かに。上記の文字列を受け取った直後にそれを行う必要がありますが、他にも心配すべきセキュリティ上の問題があります。たとえば、文字列のソースがITSメモリにコピーされていないことを保証することはできません。ソースとそれがどのように機能するかを明確に理解していない場合です。
また、明らかな理由でこの文字列を変更することはできません(変更された文字列が文字列と正確に同じサイズである場合を除きます)。その文字列の一部。
また、自分で記述していない他の関数に渡すと、その関数によってコピーされる場合とされない場合があります。
文字列がクリアしようとしている関数に到達する前に、文字列が通過するCLR関数と非CLR関数の数を知ることは不可能です。これらの関数(マネージおよびアンマネージ)は、さまざまな理由(おそらく複数のコピー)で文字列のコピーを作成する場合があります。
これらの場所をすべて知り、現実的にそれらをすべてクリアすることはできません。パスワードがメモリからクリアされることは保証できません。あなたshould代わりに SecureString
を使用する必要がありますが、上記が引き続き適用されることを理解する必要があります。あなたのプログラムはあなたがパスワードを受け取り、あなたはそれをメモリに保持する必要があります(あなたがそれを安全な文字列に移動している間、たとえ短時間であっても)。つまり、文字列は、制御できない関数呼び出しのチェーンを通過します。
SecureStringのユーザーとして、私はときどき通常の文字列から入力を取得し、SecureStringに入れたら、入力した文字列メモリを固定してゼロにするために使用しました。次に、奇妙なバグに遭遇し、サードパーティライブラリ(Redis)のメモリがゼロになりました。サードパーティのライブラリには文字列の2つのインスタンスがあり、その内容はテスト入力の通常の文字列(「パスワード」)とまったく同じであることがわかりました。どうやら.NETは3つの文字列すべてを同じメモリバッファを指すように最適化しました。そのため、文字列の「自分の」メモリを固定してゼロにすると、サードパーティのライブラリメモリもゼロになっていたことがわかりました。そして、Redisクライアントライブラリは、「パスワード」が認識されたキーではないというエラーで接続文字列を解析できません。したがって、私が苦労して学んだ教訓は、文字列からメモリをゼロにしないことです。これは、同じ内容の別の文字列からのメモリでもある可能性があるためです。
SecureString
を実際に使用できず、安全でないコードを記述したい場合は、アンマネージメモリを使用する独自の単純な文字列クラスを記述し、割り当て解除の前にすべてのメモリを確実にゼロにすることができます。
ただし、データを完全に制御することはできないため、データの安全性を確実に保証することはできません。たとえば、十分に深く埋め込まれたウイルスは、プログラムの実行中にそのメモリを読み取る可能性があります。また、これらはプロセスが終了する可能性もあります。その場合、デストラクタコードが実行されず、割り当てられていないメモリにデータが残ります。別のプロセスに割り当てられても、最初は機密データが含まれます。誰かが簡単にVisual Studioなどのツールを使用して、デバッグされたプロセスのメモリを監視したり、メモリを割り当てて機密データを検索するプログラムを作成したりできます。