回答:
文字列は不変です。つまりString
、を作成した後、別のプロセスがメモリをダンプできる場合、ガベージコレクションが開始される前に(リフレクションを除いて)データを削除することはできません。
配列を使用すると、データを使い終わった後で明示的にデータを消去できます。配列は好きなように上書きでき、ガベージコレクションの前でも、パスワードはシステムのどこにも存在しません。
つまり、これはセキュリティ上の問題です。ただし、これを使用してもchar[]
、攻撃者の機会を減らすだけであり、この特定の種類の攻撃のみを対象としています。
コメントで述べたように、ガベージコレクターによって移動されている配列がメモリにデータの浮遊コピーを残す可能性があります。これは実装固有であると考えています。ガベージコレクターは、この種のことを回避するために、すべてのメモリをクリアする場合があります。たとえそうであってもchar[]
、実際のキャラクターが攻撃ウィンドウとして含まれている時間はまだあります。
char[]
場所に保存されているパスワードをクリアすると、その攻撃ラインが遮断されますString
。
ここでのその他の提案は有効であるように見えますが、もう1つの理由があります。プレーンString
を使用すると、誤ってパスワードをログ、モニター、またはその他の安全でない場所に出力する可能性がはるかに高くなります。char[]
脆弱性は低いです。
このことを考慮:
public static void main(String[] args) {
Object pw = "Password";
System.out.println("String: " + pw);
pw = "Password".toCharArray();
System.out.println("Array: " + pw);
}
プリント:
String: Password
Array: [C@5829428e
toString
ですclassname@hashcode
。[C
はを表しchar[]
、残りは16進数のハッシュコードです。
Password
このためのクラス型を記述します。あいまいさが減り、誤ってどこかに移動することが難しくなります。
公式ドキュメントを引用すると、Java暗号化アーキテクチャガイドでは、パスワードとパスワードのchar[]
比較についてこれが述べられていString
ます(パスワードベースの暗号化についてですが、これはもちろんより一般的にはパスワードについてです)。
タイプのオブジェクトにパスワードを収集して保存することは論理的に思えます
java.lang.String
。ただし、注意点は次Object
のとおりです。タイプのString
は不変です。つまり、String
使用後の内容を変更(上書き)したりゼロにしたりできるメソッドは定義されていません。この機能により、String
オブジェクトは、ユーザーのパスワードなどの機密情報を格納するのに適さなくなります。char
代わりに、常に機密性の高い情報を収集してアレイに格納する必要があり ます。
Javaプログラミング言語、バージョン4.0のセキュアコーディングガイドラインのガイドライン2-2も同様のことを述べています(ただし、それは元々はロギングのコンテキストにあります)。
ガイドライン2-2:機密性の高い情報を記録しない
社会保障番号(SSN)やパスワードなどの一部の情報は非常に機密性が高くなります。この情報は、たとえ管理者であっても、必要以上に長く、見られる可能性のある場所に保管してはなりません。たとえば、ログファイルに送信したり、その存在を検索で検出したりしないでください。一部の一時データは、char配列などの変更可能なデータ構造に保持され、使用直後に消去される場合があります。データ構造をクリアすると、オブジェクトがプログラマーに対して透過的にメモリ内で移動されるため、一般的なJavaランタイムシステムでの効果が低下します。
このガイドラインは、処理するデータの意味論的知識を持たない下位レベルのライブラリの実装と使用にも影響を与えます。例として、低レベルの文字列解析ライブラリは、動作するテキストをログに記録する場合があります。アプリケーションは、ライブラリを使用してSSNを解析できます。これにより、ログファイルにアクセスできる管理者がSSNを利用できるようになります。
String
、JVMでの動作と動作はかなりよく理解されています... 安全な方法でパスワードを処理する場合char[]
に代わりに使用するのには十分な理由がありString
ます。
At which point
これはアプリケーション固有ですが、一般的なルールは、パスワード(プレーンテキストなど)と思われるものを手に入れたらすぐに行うことです。たとえば、ブラウザからHTTPリクエストの一部として取得します。配信を制御することはできませんが、独自のストレージを制御することができるので、それを取得したらすぐに、char []に入れ、それを使用して必要なことを実行してから、すべてを「0」に設定し、gcそれを取り戻す。
文字配列(char[]
)は、使用後に各文字をゼロに設定し、文字列をゼロに設定しないことでクリアできます。誰かが何らかの形でメモリイメージを見ることができる場合、文字列が使用されていればプレーンテキストでパスワードを見ることができますが、使用されている場合char[]
、0でデータを消去した後、パスワードは安全です。
HttpServletRequest
プレーンテキストでオブジェクトに渡します。JVMのバージョンが1.6以下の場合は、permgenスペースにあります。1.7の場合は、収集されるまで読み取り可能です。(いつでも)
intern()
、任意の文字列を不注意に呼び出すべきではない理由です。しかし、String
インスタンスは最初に(収集されるまで)存在し、char[]
後でそれらを配列に変換しても変化しません。
new String()
かStringBuilder.toString()
、多くの文字列定数を使用してアプリケーションを管理していたため、結果として多くのpermgenクリープが発生しました。1.7まで。
intern()
、任意の文字列を呼び出すと、permgenスペースに同等の文字列が割り当てられる可能性があることです。そのオブジェクトを共有する同じコンテンツのリテラル文字列がない場合、後者はGCされる可能性があります…
一部の人々は、パスワードが不要になったら、パスワードを保存するために使用されるメモリを上書きする必要があると信じています。これにより、攻撃者がシステムからパスワードを読み取る必要のある時間枠が短縮され、攻撃者がこれを実行するためにJVMメモリをハイジャックするために十分なアクセスがすでに必要であるという事実は完全に無視されます。これだけ多くのアクセス権を持つ攻撃者は、キーイベントをキャッチしてこれを完全に役に立たなくする可能性があります(私の知る限り、私が間違っている場合は修正してください)。
更新
コメントのおかげで、回答を更新する必要があります。明らかに、パスワードがハードドライブに到達する時間を短縮するため、(非常に)小さなセキュリティの改善をもたらす2つのケースがあります。それでも、ほとんどのユースケースでそれはやり過ぎだと思います。
可能であれば、コアダンプとスワップファイルを無効にすると、両方の問題に対処できます。ただし、それらには管理者権限が必要であり、機能が減る可能性があり(使用するメモリが少ない)、実行中のシステムからRAMをプルすることは依然として有効な問題です。
これは妥当な提案ではないと思いますが、少なくともその理由は推測できます。
動機は、メモリ内のパスワードのすべての痕跡を、使用後に迅速かつ確実に消去できるようにすることです。を使用するchar[]
と、配列の各要素を確実に空白などで上書きできます。String
そのように内部値を編集することはできません。
しかし、それだけでは良い答えではありません。なぜちょうどに必ず言及していないchar[]
か、String
エスケープしないのですか?その後、セキュリティの問題はありません。しかし、重要なのは、String
オブジェクトはintern()
理論的に編集され、定数プール内で存続できるということです。私はchar[]
この可能性を禁じていると思います。
char[]
れる可能性がある間、メモリ内で変更されないまま残りますが、収集されたかどうかは関係ありません。また、文字列の解釈は非リテラルに対して明示的に行う必要があるためchar[]
、静的フィールドでaを参照できることを伝えるのと同じです。
答えはすでに出ていますが、私が最近発見した問題をJava標準ライブラリと共有したいと思います。現在、パスワード文字列をchar[]
あらゆる場所に置き換えることに注意を払っていますが(もちろん、これは良いことです)、メモリからデータを消去するときに、他のセキュリティクリティカルなデータを見落としているようです。
私は例えばPrivateKeyクラスを考えています。RSA秘密鍵をPKCS#12ファイルからロードし、それを使用して何らかの操作を実行するシナリオを考えてみます。この場合、キーファイルへの物理的なアクセスが適切に制限されている限り、パスワードを盗聴するだけではあまり役に立ちません。攻撃者として、パスワードの代わりにキーを直接入手した方がずっとよいでしょう。必要な情報は、リークされたマニホールド、コアダンプ、デバッガセッション、スワップファイルなどです。
PrivateKey
結局のところ、対応する情報を構成するバイトをワイプできるAPIがないため、メモリからプライベート情報を消去できるものはありません。
このホワイトペーパーでは、この状況がどのように悪用される可能性があるかについて説明しているため、これは悪い状況です。
たとえば、OpenSSLライブラリは、秘密鍵が解放される前に重要なメモリセクションを上書きします。Javaはガベージコレクションされるため、Javaキーのプライベート情報をワイプおよび無効化する明示的なメソッドが必要です。これらのキーは、キーを使用した直後に適用されます。
PrivateKey
は、実際にはプライベートコンテンツをメモリに読み込まない実装を使用することです。たとえば、PKCS#11ハードウェアトークンを使用します。おそらく、PKCS#11のソフトウェア実装により、メモリを手動でクリーンアップできます。おそらく、NSSストア(その実装の大部分をPKCS11
Java のストアタイプと共有する)のようなものを使用する方が良いでしょう。KeychainStore
(OSXのキーストアには)その内の秘密鍵の完全なコンテンツロードPrivateKey
のインスタンスを、それはする必要はありません。(WINDOWS-MY
WindowsでのKeyStoreの動作がわかりません。)
Jon Skeetが述べているように、リフレクションを使用する以外に方法はありません。
ただし、反射がオプションの場合は、これを行うことができます。
public static void main(String[] args) {
System.out.println("please enter a password");
// don't actually do this, this is an example only.
Scanner in = new Scanner(System.in);
String password = in.nextLine();
usePassword(password);
clearString(password);
System.out.println("password: '" + password + "'");
}
private static void usePassword(String password) {
}
private static void clearString(String password) {
try {
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
char[] chars = (char[]) value.get(password);
Arrays.fill(chars, '*');
} catch (Exception e) {
throw new AssertionError(e);
}
}
実行すると
please enter a password
hello world
password: '***********'
注:文字列のchar []がGCサイクルの一部としてコピーされている場合、以前のコピーがメモリ内のどこかにある可能性があります。
この古いコピーはヒープダンプには表示されませんが、プロセスのrawメモリに直接アクセスできる場合は、それを見ることができます。一般に、このようなアクセス権を持つユーザーは避けてください。
'***********'
。
Scanner
の内部バッファにあり、使用していないためSystem.console().readPassword()
、コンソールウィンドウで読み取り可能な形式である可能性が高くなります。ただし、ほとんどの実際的な使用例でusePassword
は、の実行期間が実際の問題です。たとえば、別のマシンに接続する場合、かなりの時間がかかり、ヒープ内のパスワードを検索するのに適切なタイミングであることを攻撃者に知らせます。唯一の解決策は、攻撃者がヒープメモリを読み取らないようにすることです...
これらはすべて理由です。パスワードにはStringではなくchar []配列を選択する必要があります。
1. 文字列はJavaでは不変なので、パスワードをプレーンテキストとして保存すると、ガベージコレクターがそれをクリアするまでメモリで使用できます。また、文字列は再利用性のために文字列プールで使用されるため、かなり高い可能性があります。セキュリティの脅威となる長期間メモリに残ります。
メモリダンプにアクセスできる人は誰でもパスワードをクリアテキストで見つけることができるため、プレーンテキストではなく常に暗号化されたパスワードを使用する必要があるもう1つの理由です。文字列は不変なので、変更によって新しい文字列が生成されるため、文字列の内容を変更する方法はありません。char[]を使用する場合は、すべての要素を空白またはゼロに設定できます。したがって、パスワードを文字配列に格納すると、パスワードを盗むセキュリティリスクが明らかに軽減されます。
2. Java自体は、セキュリティ上の理由からパスワードをクリアテキストで返す非推奨のgetText()メソッドの代わりに、char []を返すJPasswordFieldのgetPassword()メソッドを使用することを推奨しています。Javaチームからのアドバイスに従い、標準に反するのではなく、標準に準拠することをお勧めします。
3. 文字列を使用すると、常にログファイルまたはコンソールにプレーンテキストが印刷されるリスクがありますが、配列を使用すると、配列の内容は印刷されず、代わりにメモリの場所が印刷されます。本当の理由ではありませんが、それでも意味があります。
String strPassword="Unknown";
char[] charPassword= new char[]{'U','n','k','w','o','n'};
System.out.println("String password: " + strPassword);
System.out.println("Character password: " + charPassword);
String password: Unknown
Character password: [C@110b053
このブログから参照。これがお役に立てば幸いです。
編集: 1年のセキュリティ調査の後でこの回答に戻ると、プレーンテキストのパスワードを実際に比較することはかなり不幸な意味を持っていることがわかりました。しないでください。ソルトと適切な回数の反復を伴う安全な一方向ハッシュを使用します。ライブラリの使用を検討してください。これを正しく行うのは困難です。
元の回答: String.equals()が短絡評価を使用しているため、タイミング攻撃に対して脆弱であるという事実はどうですか?それはありそうもないかもしれませんが、正しい文字シーケンスを決定するために理論的にはパスワード比較の時間を計ることができます。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// Quits here if Strings are different lengths.
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// Quits here at first different character.
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
タイミング攻撃に関するその他のリソース:
Arrays.equals
するためには、char[]
用として高いようですString.equals
。誰が世話をする場合は、専用のキークラス実際のパスワードをカプセル化し、問題-OH待つの世話をしてはいた、本当のセキュリティパッケージを持っている専用キークラスを、このQ&Aは約ある習慣のそれらの外、例えば、言ってJPasswordField
使用することに、char[]
代わりにString
(実際のアルゴリズムはbyte[]
とにかく使用します)。
sleep(secureRandom.nextInt())
とにかく、セキュリティ関連のソフトウェアは、ログイン試行を拒否する前に、タイミング攻撃の可能性を取り除くだけでなく、ブルートフォース攻撃を打ち消すような処理を行う必要があります。
使用後に手動でクリーンアップしない限り、char配列が文字列と比較して文字列を提供するものは何もありません。したがって、私にとっては、char []対Stringの設定は少し誇張されています。
ここで広く使用されている Spring Securityライブラリを見て、考えてみてください。SpringSecurityの無能なパスワードやchar []パスワードは、あまり意味がありません。悪意のあるハッカーがRAMのメモリダンプを取得する場合、高度な方法でパスワードを非表示にしても、すべてのパスワードを入手できることを確認してください。
ただし、Javaは常に変化し、Java 8の文字列重複排除機能などのいくつかの恐ろしい機能は、知らないうちにStringオブジェクトをインターンする可能性があります。しかし、それは別の会話です。
文字列は不変であり、一度作成すると変更できません。文字列としてパスワードを作成すると、ヒープまたは文字列プールにパスワードへの参照が残ってしまいます。これで、誰かがJavaプロセスのヒープダンプを取り、注意深くスキャンすると、パスワードを推測できる可能性があります。もちろん、これらの未使用の文字列はガベージコレクションされますが、GCがいつ起動するかによって異なります。
一方、認証が完了するとすぐにchar []は変更可能です。すべてのMやバックスラッシュなどの任意の文字で上書きできます。これで、ヒープダンプを取得しても、現在使用されていないパスワードを取得できない可能性があります。これにより、オブジェクトのコンテンツを自分でクリアするのではなく、GCが実行するのを待つような感覚で、より詳細に制御できます。
strings
AND String
プール(以前に使用された文字列のプール)は両方とも、1.7より前のPermgenに格納されていました。また、セクション5.1:docs.oracle.com/javase/specs/jvms/se6/html/…も参照してください 。JVMは常にStrings
同じ参照値であるかどうかを確認し、String.intern()
FOR YOU を呼び出します。その結果、JVMは、constant_pool
またはヒープ内で同じ文字列を検出するたびに、それらをpermgenに移動しました。そして、私は1.7まで "crepering permgen"でいくつかのアプリケーションに取り組みました。それは本当の問題でした。
constant_pool
は、文字列はヒープで開始されていたため、使用されたときに、permgenのINに配置されていました。その後、文字列が複数回使用された場合、インターンされます。
文字列は不変であり、文字列プールに移動します。一度書き込むと上書きできません。
char[]
パスワードを使用したら上書きする必要がある配列で、次のようにしてください。
char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
allowUser = true;
cleanPassword(passw);
cleanPassword(dbPassword);
passw=null;
}
private static void cleanPassword (char[] pass) {
Arrays.fill(pass, '0');
}
攻撃者がそれを使用する可能性があるシナリオの1つはクラッシュダンプです。JVMがクラッシュしてメモリダンプが生成されると、パスワードを見ることができます。
これは必ずしも悪意のある外部の攻撃者ではありません。これは、監視目的でサーバーにアクセスできるサポートユーザーである可能性があります。彼はクラッシュダンプをのぞいて、パスワードを見つけることができました。
request.getPassword()
まだ文字列を作成してプールに追加していませんか?
ch = '0'
ローカル変数を変更しますch
。配列には影響しません。とにかくあなたの例は無意味です、あなたはあなたが呼び出す文字列インスタンスから始めて、toCharArray()
新しい配列を作成します、そして新しい配列を正しく上書きしたとしても、それは文字列インスタンスを変更しないので、それは単に文字列を使用するよりも利点がありませんインスタンス。
Arrays.fill(pass, '0');
短くて簡単な答えは、char[]
は変更可能であるのにString
オブジェクトは変更できないためです。
Strings
Javaでは不変オブジェクトです。そのため、一度作成すると変更できないため、コンテンツをメモリから削除する唯一の方法は、ガベージコレクションを実行することです。オブジェクトによって解放されたメモリを上書きできるようになって初めて、データは失われます。
現在、Javaのガベージコレクションは保証された間隔で発生しません。このString
ため、はメモリ内に長期間存続する可能性があり、この間にプロセスがクラッシュした場合、文字列の内容がメモリダンプまたはログに記録される可能性があります。
文字配列を使用すると、パスワードを読み取り、できるだけ早くパスワードでの作業を終了して、すぐに内容を変更できます。
Javaの文字列は不変です。そのため、文字列が作成されると、ガベージコレクションが行われるまでメモリに残ります。したがって、メモリにアクセスできる人なら誰でも文字列の値を読み取ることができます。
文字列の値が変更されると、新しい文字列が作成されます。したがって、元の値と変更された値の両方がガベージコレクションされるまでメモリに残ります。
文字配列を使用すると、パスワードの目的が果たされると、配列の内容を変更または消去できます。配列の元の内容は、変更された後、ガベージコレクションが実行される前でもメモリにありません。
セキュリティ上の理由から、パスワードを文字配列として保存することをお勧めします。
この目的にStringを使用するかChar []を使用するかについては、どちらにも長所と短所があるため、議論の余地があります。それはユーザーが何を必要としているのかによります。
Javaの文字列は不変なので、文字列を操作しようとするたびに新しいオブジェクトが作成され、既存の文字列は影響を受けません。これは、パスワードを文字列として保存する利点と見なすことができますが、オブジェクトは使用後もメモリに残ります。したがって、誰かが何らかの形でオブジェクトのメモリの場所を取得した場合、その人はその場所に保存されているパスワードを簡単に追跡できます。
Char []は変更可能ですが、使用後にプログラマーが明示的に配列を消去したり、値をオーバーライドしたりできるという利点があります。したがって、使用が完了するとそれはクリーンになり、保存した情報について誰も知ることができなくなります。
上記の状況に基づいて、要件に応じてStringを使用するのかChar []を使用するのかを知ることができます。
ケース文字列:
String password = "ill stay in StringPool after Death !!!";
// some long code goes
// ...Now I want to remove traces of password
password = null;
password = "";
// above attempts wil change value of password
// but the actual password can be traced from String pool through memory dump, if not garbage collected
ケースCHAR ARRAY:
char[] passArray = {'p','a','s','s','w','o','r','d'};
// some long code goes
// ...Now I want to remove traces of password
for (int i=0; i<passArray.length;i++){
passArray[i] = 'x';
}
// Now you ACTUALLY DESTROYED traces of password form memory