パスワードの文字列よりもchar []が推奨されるのはなぜですか?


3422

スイングでは、パスワードフィールドには持っているgetPassword()(リターンchar[]の代わりに通常の)方法getText()(リターンString)メソッドを。同様に、Stringパスワードの処理に使用しないという提案を見つけました。

Stringパスワードに関して、なぜセキュリティを脅かすのですか?使い勝手が悪いchar[]です。

回答:


4290

文字列は不変です。つまりString、を作成した後、別のプロセスがメモリをダンプできる場合、ガベージコレクションが開始される前に(リフレクションを除いて)データを削除することはできません。

配列を使用すると、データを使い終わった後で明示的にデータを消去できます。配列は好きなように上書きでき、ガベージコレクションの前でも、パスワードはシステムのどこにも存在しません。

つまり、これセキュリティ上の問題です。ただし、これを使用してもchar[]、攻撃者の機会を減らすだけであり、この特定の種類の攻撃のみを対象としています。

コメントで述べたように、ガベージコレクターによって移動されている配列がメモリにデータの浮遊コピーを残す可能性があります。これは実装固有であると考えています。ガベージコレクターは、この種のことを回避するために、すべてのメモリをクリアする場合があります。たとえそうであってもchar[]、実際のキャラクターが攻撃ウィンドウとして含まれている時間はまだあります。


3
プロセスがアプリケーションのメモリにアクセスできる場合、それはすでにセキュリティ違反です。
イエティ2015

5
@Yeti:ええ、でもそれは白黒のようなものではありません。彼らがメモリのスナップショットしか取得できない場合は、そのスナップショットが与えるダメージの量を減らすか、本当に深刻なスナップショットを取得できる時間を減らします。
Jon Skeet、2015

11
一般的な攻撃方法は、大量のメモリを割り当て、それをスキャンして、パスワードなどの残りの有用なデータを探すプロセスを実行することです。プロセスは別のプロセスのメモリ空間への魔法のようなアクセスを必要としません。新しいプロセスで使用できるようにする前に、機密データを消去せずに他のプロセスが停止し、OSがメモリ(またはページバッファ)を消去しないことに依存しています。char[]場所に保存されているパスワードをクリアすると、その攻撃ラインが遮断されますString
テッドホップ2016

OSが別のプロセスに渡す前にメモリをクリアしない場合、OSには重大なセキュリティ問題があります!ただし、技術的にはクリアはプロテクトモードのトリックで行われることが多く、CPUが故障した場合(Intel Meldownなど)、古いメモリの内容を読み取ることは可能です。
ミッコランタライネン

1222

ここでのその他の提案は有効であるように見えますが、もう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

40
@voo、しかし、ストリームと連結への直接書き込みを介してログを記録することはないと思います。ロギングフレームワークはchar []を適切な出力に変換します
bestsss

41
@ Thr4wnのデフォルトの実装はtoStringですclassname@hashcode[Cはを表しchar[]、残りは16進数のハッシュコードです。
Konrad Garus

15
面白いアイデア。これは、配列に対して意味のあるtoStringを持つScalaに転置されないことを指摘しておきます。
mauhiz 2015

37
Passwordこのためのクラス型を記述します。あいまいさが減り、誤ってどこかに移動することが難しくなります。
15

8
なぜ誰かがchar配列がオブジェクトとしてキャストされると想定するのですか?なぜ誰もがこの答えを気に入っている理由がわかりません。次のようにしたとします。System.out.println( "Password" .toCharArray());
GC_

680

公式ドキュメントを引用すると、Java暗号化アーキテクチャガイドでは、パスワードとパスワードのchar[]比較についてこれが述べられていStringます(パスワードベースの暗号化についてですが、これはもちろんより一般的にはパスワードについてです)。

タイプのオブジェクトにパスワードを収集して保存することは論理的に思えますjava.lang.String。ただし、注意点は次Objectのとおりです。タイプのStringは不変です。つまり、String 使用後の内容を変更(上書き)したりゼロにしたりできるメソッドは定義されていません。この機能により、Stringオブジェクトは、ユーザーのパスワードなどの機密情報を格納するのに適さなくなります。char代わりに、常に機密性の高い情報を収集してアレイに格納する必要があり ます。

Javaプログラミング言語、バージョン4.0のセキュアコーディングガイドラインのガイドライン2-2も同様のことを述べています(ただし、それは元々はロギングのコンテキストにあります)。

ガイドライン2-2:機密性の高い情報を記録しない

社会保障番号(SSN)やパスワードなどの一部の情報は非常に機密性が高くなります。この情報は、たとえ管理者であっても、必要以上に長く、見られる可能性のある場所に保管してはなりません。たとえば、ログファイルに送信したり、その存在を検索で検出したりしないでください。一部の一時データは、char配列などの変更可能なデータ構造に保持され、使用直後に消去される場合があります。データ構造をクリアすると、オブジェクトがプログラマーに対して透過的にメモリ内で移動されるため、一般的なJavaランタイムシステムでの効果が低下します。

このガイドラインは、処理するデータの意味論的知識を持たない下位レベルのライブラリの実装と使用にも影響を与えます。例として、低レベルの文字列解析ライブラリは、動作するテキストをログに記録する場合があります。アプリケーションは、ライブラリを使用してSSNを解析できます。これにより、ログファイルにアクセスできる管理者がSSNを利用できるようになります。


4
これはまさに私がジョンの答えの下で私が話す欠陥のある/偽の参照であり、それは多くの批判のある有名な情報源です。
bestss 2012年

37
@bestassも参考にしていただけますか?
user961954 2012

10
@bestass申し訳ありませんがString、JVMでの動作と動作はかなりよく理解されています... 安全な方法でパスワードを処理する場合char[]に代わりに使用するのには十分な理由がありStringます。
SnakeDoc 2014年

3
パスワードは文字列としてブラウザからリクエストに渡されますが、文字列ではなく「文字列」ですか?それであなたが何をしてもそれは文字列です、その時点でそれは作用されて破棄されるべきであり、決してメモリに保存されませんか?
Dawesi

3
@Dawesi-- At which pointこれはアプリケーション固有ですが、一般的なルールは、パスワード(プレーンテキストなど)と思われるものを手に入れたらすぐに行うことです。たとえば、ブラウザからHTTPリクエストの一部として取得します。配信を制御することはできませんが、独自のストレージを制御することができるので、それを取得したらすぐに、char []に入れ、それを使用して必要なことを実行してから、すべてを「0」に設定し、gcそれを取り戻す。
luis.espinal

354

文字配列(char[])は、使用後に各文字をゼロに設定し、文字列をゼロに設定しないことでクリアできます。誰かが何らかの形でメモリイメージを見ることができる場合、文字列が使用されていればプレーンテキストでパスワードを見ることができますが、使用されている場合char[]、0でデータを消去した後、パスワードは安全です。


13
デフォルトでは安全ではありません。Webアプリケーションについて話している場合、ほとんどのWebコンテナはパスワードをHttpServletRequestプレーンテキストでオブジェクトに渡します。JVMのバージョンが1.6以下の場合は、permgenスペースにあります。1.7の場合は、収集されるまで読み取り可能です。(いつでも)
avgvstvs

4
@avgvstvs:文字列はpermgenスペースに自動的に移動されず、インターンされた文字列にのみ適用されます。その上、permgenスペースもガベージコレクションの対象となりますが、その割合は低くなります。permgenスペースの本当の問題は、固定サイズであることです。これがintern()、任意の文字列を不注意に呼び出すべきではない理由です。しかし、Stringインスタンスは最初に(収集されるまで)存在し、char[]後でそれらを配列に変換しても変化しません。
Holger

3
参照@Holger docs.oracle.com/javase/specs/jvms/se6/html/... 「そうでなければ、クラス文字列の新しいインスタンスがCONSTANT_String_info構造によって与えられたUnicode文字の配列を含む作成され、そのクラスのインスタンスは、の結果であります文字列リテラルの派生。最後に、新しいStringインスタンスのインターンメソッドが呼び出されます。1.6では、JVMは同一のシーケンスを検出するとインターンを呼び出します。
avgvstvs 2017年

3
@ホルガー、あなたは正しいです私は定数プールと文字列プールを融合しましたが、permgenスペースがインターンされた文字列にのみ適用されることも誤りです。1.7より前は、constant_poolとstring_poolの両方がpermgenスペースにありました。つまり、ヒープに割り当てられた 文字列の唯一のクラスは、あなたが言ったとおりだったnew String()StringBuilder.toString()、多くの文字列定数を使用してアプリケーションを管理していたため、結果として多くのpermgenクリープが発生しました。1.7まで。
avgvstvs 2017年

5
@avgvstvs:まあ、JLSの義務として、文字列定数は常にインターンされるので、インターンされた文字列はpermgenスペースで終わり、暗黙的に文字列定数に適用されます。唯一の違いは、文字列定数が最初にpermgenスペースで作成されたのに対しintern()、任意の文字列を呼び出すと、permgenスペースに同等の文字列が割り当てられる可能性があることです。そのオブジェクトを共有する同じコンテンツのリテラル文字列がない場合、後者はGCされる可能性があります…
Holger

220

一部の人々は、パスワードが不要になったら、パスワードを保存するために使用されるメモリを上書きする必要があると信じています。これにより、攻撃者がシステムからパスワードを読み取る必要のある時間枠が短縮され、攻撃者がこれを実行するためにJVMメモリをハイジャックするために十分なアクセスがすでに必要であるという事実は完全に無視されます。これだけ多くのアクセス権を持つ攻撃者は、キーイベントをキャッチしてこれを完全に役に立たなくする可能性があります(私の知る限り、私が間違っている場合は修正してください)。

更新

コメントのおかげで、回答を更新する必要があります。明らかに、パスワードがハードドライブに到達する時間を短縮するため、(非常に)小さなセキュリティの改善をもたらす2つのケースがあります。それでも、ほとんどのユースケースでそれはやり過ぎだと思います。

  • ターゲットシステムが正しく構成されていないか、そうであると想定する必要があり、コアダンプに偏執的である必要があります(システムが管理者によって管理されていない場合は有効です)。
  • TrueCrypt(廃止)、VeraCryptCipherShedなどを使用して、攻撃者によるハードウェアへのアクセスによるデータ漏洩を防ぐために、ソフトウェアは過度に偏執的でなければなりません。

可能であれば、コアダンプとスワップファイルを無効にすると、両方の問題に対処できます。ただし、それらには管理者権限が必要であり、機能が減る可能性があり(使用するメモリが少ない)、実行中のシステムからRAMをプルすることは依然として有効な問題です。


33
「完全に役に立たない」を「ほんの少しのセキュリティ改善」に置き換えます。たとえば、tmpディレクトリへの読み取りアクセス、不適切に構成されたマシン、およびアプリケーションのクラッシュが発生した場合、メモリダンプにアクセスできます。その場合、キーロガーをインストールすることはできませんが、コアダンプを分析できます。
Joachim Sauer

44
暗号化されていないデータを使い終わったらすぐにメモリから消去することは、絶対確実ではない(そうではない)のではなく、ベストプラクティスと見なされています。しかし、それはあなたの脅威の露出レベルを下げるからです。これを行うことでリアルタイム攻撃を防ぐことはできません。ただし、メモリスナップショットに対する遡及的攻撃で公開されるデータの量を大幅に削減することにより、損傷軽減ツールとして機能します(たとえば、スワップファイルに書き込まれた、またはメモリから読み取られたアプリメモリのコピー)実行中のサーバーから、状態が失敗する前に別のサーバーに移動した場合)。
DanはFirelightによっていじっています

9
私はこの反応の態度に同意する傾向があります。結果のほとんどのセキュリティ違反は、メモリ内のビットよりもはるかに高い抽象化レベルで発生することを提案します。確かに、非常に安全な防御システムでは、これがかなりの懸念になる可能性のあるシナリオがありますが、.NETまたはJavaが活用されているアプリケーションの99%では、このレベルで真剣に考えるのはやりすぎです(ガベージコレクションに関連するため)。
kingdango 2012年

10
Heartbleedがサーバーメモリに侵入し、パスワードが明らかになった後、「ちょっとしたセキュリティの改善」という文字列を、「パスワードにStringを使用しないでください。代わりにchar []を使用してください」と置き換えます。
Peter vdL 2014

9
@PetervdLハートブリードは、特定の再利用されたバッファーのコレクションの読み取りのみを許可しました(パフォーマンス上の理由から、セキュリティ上の重要なデータとネットワークI / Oの両方で使用されます)。これらは、設計上再利用できないため、Java文字列と組み合わせることはできません。 。また、Javaを使用してランダムメモリに読み込み、文字列の内容を取得することもできません。ハートブリードにつながる言語と設計の問題は、Java文字列では不可能です。
josefx 14

86

これは妥当な提案ではないと思いますが、少なくともその理由は推測できます。

動機は、メモリ内のパスワードのすべての痕跡を、使用後に迅速かつ確実に消去できるようにすることです。を使用するchar[]と、配列の各要素を確実に空白などで上書きできます。Stringそのように内部値を編集することはできません。

しかし、それだけでは良い答えではありません。なぜちょうどに必ず言及していないchar[]か、Stringエスケープしないのですか?その後、セキュリティの問題はありません。しかし、重要なのは、Stringオブジェクトはintern()理論的に編集され、定数プール内で存続できるということです。私はchar[]この可能性を禁じていると思います。


4
問題はあなたの参照が「エスケープ」するかしないかということではありません。文字列は変更さchar[]れる可能性がある間、メモリ内で変更されないまま残りますが、収集されたかどうかは関係ありません。また、文字列の解釈は非リテラルに対して明示的に行う必要があるためchar[]、静的フィールドでaを参照できることを伝えるのと同じです。
Groo

1
パスワードはフォームポストからの文字列としてメモリにありませんか?
Dawesi

66

答えはすでに出ていますが、私が最近発見した問題をJava標準ライブラリと共有したいと思います。現在、パスワード文字列をchar[]あらゆる場所に置き換えることに注意を払っていますが(もちろん、これは良いことです)、メモリからデータを消去するときに、他のセキュリティクリティカルなデータを見落としているようです。

私は例えばPrivateKeyクラスを考えています。RSA秘密鍵をPKCS#12ファイルからロードし、それを使用して何らかの操作を実行するシナリオを考えてみます。この場合、キーファイルへの物理的なアクセスが適切に制限されている限り、パスワードを盗聴するだけではあまり役に立ちません。攻撃者として、パスワードの代わりにキーを直接入手した方がずっとよいでしょう。必要な情報は、リークされたマニホールド、コアダンプ、デバッガセッション、スワップファイルなどです。

PrivateKey結局のところ、対応する情報を構成するバイトをワイプできるAPIがないため、メモリからプライベート情報を消去できるものはありません。

このホワイトペーパーでは、この状況がどのように悪用される可能性があるかについて説明しているため、これは悪い状況です。

たとえば、OpenSSLライブラリは、秘密鍵が解放される前に重要なメモリセクションを上書きします。Javaはガベージコレクションされるため、Javaキーのプライベート情報をワイプおよび無効化する明示的なメソッドが必要です。これらのキーは、キーを使用した直後に適用されます。


これを回避する1つの方法PrivateKeyは、実際にはプライベートコンテンツをメモリに読み込まない実装を使用することです。たとえば、PKCS#11ハードウェアトークンを使用します。おそらく、PKCS#11のソフトウェア実装により、メモリを手動でクリーンアップできます。おそらく、NSSストア(その実装の大部分をPKCS11Java のストアタイプと共有する)のようなものを使用する方が良いでしょう。KeychainStore(OSXのキーストアには)その内の秘密鍵の完全なコンテンツロードPrivateKeyのインスタンスを、それはする必要はありません。(WINDOWS-MYWindowsでのKeyStoreの動作がわかりません。)
Bruno

@ブルーノ確かに、ハードウェアベースのトークンはこれに悩まされることはありませんが、ソフトウェアキーの使用を余儀なくされる状況についてはどうですか?すべてのデプロイメントにHSMを提供する予算があるわけではありません。ソフトウェアキーストアは、ある時点でキーをメモリにロードする必要があるため、IMOには少なくともメモリをいつでも消去するオプションを提供する必要があります。
エンボス

絶対に、HSMと同等のソフトウェア実装のいくつかが、メモリのクリーンアップの点でより適切に動作するかどうか疑問に思っていました。たとえば、Safari / OSXでclient-authを使用する場合、Safariプロセスは実際には秘密キーを認識しません。OSによって提供される基本的なSSLライブラリは、ユーザーにキーチェーンのキーを使用するように求めるセキュリティデーモンと直接通信します。これはすべてソフトウェアで行われますが、このような同様の分離は、メモリをより適切にアンロードまたはクリアする別のエンティティ(ソフトウェアベースでも)に署名が委任されている場合に役立ちます。
Bruno

@Bruno:興味深いアイデアですが、メモリのクリアを処理する追加の間接層がこれを透過的に解決します。ソフトウェアキーストア用のPKCS#11ラッパーを作成すると、既にトリックを実行できますか?
秒にエンボス

51

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メモリに直接アクセスできる場合は、それを見ることができます。一般に、このようなアクセス権を持つユーザーは避けてください。


2
また、取得したパスワードの長さを出力しないようにするためにも何かをする方が良いでしょう'***********'
chux-モニカを2015年

@chuxでは、幅が0の文字を使用できますが、これは便利ではない場合があります。Unsafeを使用せずにchar配列の長さを変更することはできません。;)
Peter Lawrey、2015

5
Java 8の文字列重複排除のため、そのようなことを行うとかなり破壊的になる可能性があると思います。プログラム内で、偶然同じパスワード文字列の値を持っていた他の文字列をクリアしてしまう可能性があります。ありそうもないが、可能である...
jamp

1
@PeterLawrey JVM引数で有効にする必要がありますが、あります。ここではそれについて読むことができます:blog.codecentric.de/en/2014/08/...
JAMP

1
パスワードがまだScannerの内部バッファにあり、使用していないためSystem.console().readPassword()、コンソールウィンドウで読み取り可能な形式である可能性が高くなります。ただし、ほとんどの実際的な使用例でusePasswordは、の実行期間が実際の問題です。たとえば、別のマシンに接続する場合、かなりの時間がかかり、ヒープ内のパスワードを検索するのに適切なタイミングであることを攻撃者に知らせます。唯一の解決策は、攻撃者がヒープメモリを読み取らないようにすることです...
Holger

42

これらはすべて理由です。パスワードには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

このブログから参照。これがお役に立てば幸いです。


10
これは冗長です。この回答は、@ SrujanKumarGulla stackoverflow.com/a/14060804/1793718によって書かれた回答の正確なバージョンです。同じ回答を2回コピーして貼り付けたり複製したりしないでください。
ラッキー

1.)System.out.println( "Character password:" + charPassword);の違いは何ですか。および2.)System.out.println(charPassword); 出力と同じ「不明」を提供しているためです。
Vaibhav_Sharma 2017

3
@Lucky残念ながら、あなたがリンクした以前の回答は、この回答と同じブログから盗用されたため、現在削除されています。meta.stackoverflow.com/questions/389144/…を参照してください。この回答は、同じブログから切り取って貼り付けただけで何も追加していないので、元のソースにリンクするコメントにすぎません。
skomisa

41

編集: 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;
}

タイミング攻撃に関するその他のリソース:


しかし、それはchar []の比較にも存在する可能性があり、どこかでパスワード検証でも同じことを行います。char []は文字列よりどのように優れていますか?
Mohit Kanwar、2015

3
あなたは絶対に正しいです、間違いはどちらの方法でも起こりえます。文字列ベースのパスワードまたはchar []ベースのパスワードのいずれかについて、Javaに明示的なパスワード比較メソッドがないことを考えると、問題について知ることがここで最も重要なことです。文字列に対してcompare()を使用する誘惑は、char []を使用する良い理由だと思います。そうすれば、少なくとも比較がどのように行われるかを制御できます(文字列を拡張せずに、これは苦痛です)。
グラフ理論

平文パスワードを比較するだけでなく、とにかく正しいことではないが、使用する誘惑Arrays.equalsするためには、char[]用として高いようですString.equals。誰が世話をする場合は、専用のキークラス実際のパスワードをカプセル化し、問題-OH待つの世話をしてはいた、本当のセキュリティパッケージを持っている専用キークラスを、このQ&Aは約ある習慣のそれらの外、例えば、言ってJPasswordField使用することに、char[]代わりにString(実際のアルゴリズムはbyte[]とにかく使用します)。
Holger

sleep(secureRandom.nextInt())とにかく、セキュリティ関連のソフトウェアは、ログイン試行を拒否する前に、タイミング攻撃の可能性を取り除くだけでなく、ブルートフォース攻撃を打ち消すような処理を行う必要があります。
Holger

36

使用後に手動でクリーンアップしない限り、char配列が文字列と比較して文字列を提供するものは何もありません。したがって、私にとっては、char []対Stringの設定は少し誇張されています。

ここで広く使用されている Spring Securityライブラリを見て、考えてみてください。SpringSecurityの無能なパスワードやchar []パスワードは、あまり意味がありません。悪意のあるハッカーがRAMのメモリダンプを取得する場合、高度な方法でパスワードを非表示にしても、すべてのパスワードを入手できることを確認してください。

ただし、Javaは常に変化し、Java 8の文字列重複排除機能などのいくつかの恐ろしい機能は、知らないうちにStringオブジェクトをインターンする可能性があります。しかし、それは別の会話です。


文字列重複除外が怖いのはなぜですか?同じ内容の文字列が少なくとも2つある場合にのみ適用されるため、これら2つのすでに同じ文字列で同じ配列を共有させると、どのような危険が生じますか?または、逆に質問してみましょう:文字列重複排除がない場合、両方の文字列が(同じ内容の)異なる配列を持つという事実からどのような利点が生じますか?どちらの場合でも、少なくともそのコンテンツの最長の文字列が生きている限り、そのコンテンツの配列が生きています…
Holger

@Holger制御できないものは潜在的なリスクです...たとえば、2人のユーザーが同じパスワードを持っている場合、この素晴らしい機能は両方を単一のchar []に格納し、それらが同じであることを明らかにします。巨大なリスクが、それでも
オレグMikheev

ヒープメモリと両方の文字列インスタンスにアクセスできる場合、文字列が同じ配列を指しているか、同じ内容の2つの配列を指しているかは関係ありません。それぞれを簡単に見つけることができます。特に、とにかく関係ないので。この時点で同じであるかどうかにかかわらず、両方のパスワードを取得します。実際のエラーは、ソルトハッシュの代わりにプレーンテキストのパスワードを使用することにあります。
ホルガー2017年

@Holgerは、パスワードを確認するために、メモリからしばらくの間(10ミリ秒)クリアテキストである必要があります。次に、2つの同一のパスワードが10ミリ秒でもメモリに保持されている場合、重複排除が機能する可能性があります。それが本当に文字列をインターンする場合、それらはずっと長い時間メモリに保持されます。数か月間再起動しないシステムは、これらの多くを収集します。理論化するだけです。
Oleg Mikheev

文字列重複排除について根本的な誤解があるようです。「インターン文字列」ではなく、同じ内容の文字列が同じ配列を指すようにしています。これにより、プレーンテキストパスワードを含む配列インスタンスの数が実際に減少します。他のオブジェクトによってすぐに。これらの文字列は、他の文字列と同様に収集されます。複数のGCサイクルのみに耐えた文字列に対して、重複排除が実際にガベージコレクターによって行われることを理解している場合は、それが役立つかもしれません。
Holger

30

文字列は不変であり、一度作成すると変更できません。文字列としてパスワードを作成すると、ヒープまたは文字列プールにパスワードへの参照が残ってしまいます。これで、誰かがJavaプロセスのヒープダンプを取り、注意深くスキャンすると、パスワードを推測できる可能性があります。もちろん、これらの未使用の文字列はガベージコレクションされますが、GCがいつ起動するかによって異なります。

一方、認証が完了するとすぐにchar []は変更可能です。すべてのMやバックスラッシュなどの任意の文字で上書きできます。これで、ヒープダンプを取得しても、現在使用されていないパスワードを取得できない可能性があります。これにより、オブジェクトのコンテンツを自分でクリアするのではなく、GCが実行するのを待つような感覚で、より詳細に制御できます。


問題のJVMが> 1.6の場合にのみGCされます。1.7より前は、すべての文字列がpermgenに格納されていました。
avgvstvs 2016

@avgvstvs:「すべての文字列はpermgenに格納されていました」はまったく間違っています。そこにはインターンされた文字列のみが格納され、それらがコードによって参照される文字列リテラルに由来しない場合でも、ガベージコレクションされました。考えてみてください。文字列が1.7より前のJVMで一般的にGCされなかった場合、どのJavaアプリケーションが数分以上存続できるでしょうか。
Holger

@Holgerこれは誤りです。インターンされたstringsAND 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"でいくつかのアプリケーションに取り組みました。それは本当の問題でした。
avgvstvs 2017年

つまり、1.7までconstant_poolは、文字列はヒープで開始されていたため、使用されたときに、permgenのINに配置されていました。その後、文字列が複数回使用された場合、インターンされます。
avgvstvs 2017年

@avgvstvs:「以前に使用された文字列のプール」はありません。あなたは完全に異なるものを一緒に投げています。文字列リテラルと明示的にインターンされた文字列を含むランタイム文字列プールが1つありますが、その他はありません。また、各クラスには、コンパイル時の定数を含む定数プールがあります。これらの文字列はランタイムプールに自動的に追加されますが、すべての文字列ではなく、これらのみが追加されます。
Holger

21

文字列は不変であり、文字列プールに移動します。一度書き込むと上書きできません。

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がクラッシュしてメモリダンプが生成されると、パスワードを見ることができます。

これは必ずしも悪意のある外部の攻撃者ではありません。これは、監視目的でサーバーにアクセスできるサポートユーザーである可能性があります。彼はクラッシュダンプをのぞいて、パスワードを見つけることができました。


2
ch = null; あなたはこれを行うことができません
Yugerten

しかし、request.getPassword()まだ文字列を作成してプールに追加していませんか?
Tvde1

1
ch = '0'ローカル変数を変更しますch。配列には影響しません。とにかくあなたの例は無意味です、あなたはあなたが呼び出す文字列インスタンスから始めて、toCharArray()新しい配列を作成します、そして新しい配列を正しく上書きしたとしても、それは文字列インスタンスを変更しないので、それは単に文字列を使用するよりも利点がありませんインスタンス。
Holger

@ホルガーありがとう。char配列のクリーンアップコードを修正しました。
ACV

3
単純に使用できますArrays.fill(pass, '0');
Holger

18

短くて簡単な答えは、char[]は変更可能であるのにStringオブジェクトは変更できないためです。

StringsJavaでは不変オブジェクトです。そのため、一度作成すると変更できないため、コンテンツをメモリから削除する唯一の方法は、ガベージコレクションを実行することです。オブジェクトによって解放されたメモリを上書きできるようになって初めて、データは失われます。

現在、Javaのガベージコレクションは保証された間隔で発生しません。このStringため、はメモリ内に長期間存続する可能性があり、この間にプロセスがクラッシュした場合、文字列の内容がメモリダンプまたはログに記録される可能性があります。

文字配列を使用すると、パスワードを読み取り、できるだけ早くパスワードでの作業を終了して、すぐに内容を変更できます。


@fallenidolまったくありません。よく読んでください。違いがわかります。
Pritam Banerjee 2017

12

Javaの文字列は不変です。そのため、文字列が作成されると、ガベージコレクションが行われるまでメモリに残ります。したがって、メモリにアクセスできる人なら誰でも文字列の値を読み取ることができます。
文字列の値が変更されると、新しい文字列が作成されます。したがって、元の値と変更された値の両方がガベージコレクションされるまでメモリに残ります。

文字配列を使用すると、パスワードの目的が果たされると、配列の内容を変更または消去できます。配列の元の内容は、変更された後、ガベージコレクションが実行される前でもメモリにありません。

セキュリティ上の理由から、パスワードを文字配列として保存することをお勧めします。


2

この目的にStringを使用するかChar []を使用するかについては、どちらにも長所と短所があるため、議論の余地があります。それはユーザーが何を必要としているのかによります。

Javaの文字列は不変なので、文字列を操作しようとするたびに新しいオブジェクトが作成され、既存の文字列は影響を受けません。これは、パスワードを文字列として保存する利点と見なすことができますが、オブジェクトは使用後もメモリに残ります。したがって、誰かが何らかの形でオブジェクトのメモリの場所を取得した場合、その人はその場所に保存されているパスワードを簡単に追跡できます。

Char []は変更可能ですが、使用後にプログラマーが明示的に配列を消去したり、値をオーバーライドしたりできるという利点があります。したがって、使用が完了するとそれはクリーンになり、保存した情報について誰も知ることができなくなります。

上記の状況に基づいて、要件に応じてStringを使用するのかChar []を使用するのかを知ることができます。


0

ケース文字列:

    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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.