Warum wird char [] für Passwörter String vorgezogen?


3422

In Swing verfügt das Kennwortfeld über eine getPassword()(Rückgabe- char[]) Methode anstelle der üblichen getText()(Rückgabe- String) Methode. Ebenso bin ich auf einen Vorschlag gestoßen, nicht Stringmit Passwörtern umzugehen.

Warum ist die StringSicherheit bei Passwörtern eine Bedrohung? Es fühlt sich unpraktisch an, es zu benutzen char[].

Antworten:


4290

Saiten sind unveränderlich . Das bedeutet , dass , sobald Sie die erstellten String, wenn ein anderer Prozess Speicherabbild kann, gibt es keine Möglichkeit (abgesehen von Reflexion ) Sie loszuwerden, die Daten , bevor bekommen können Garbage Collection in Kicks.

Mit einem Array können Sie die Daten explizit löschen, nachdem Sie damit fertig sind. Sie können das Array mit einem beliebigen Element überschreiben, und das Kennwort ist auch vor der Speicherbereinigung nirgendwo im System vorhanden.

Ja, dies ist ein Sicherheitsrisiko - aber selbst die Verwendung char[]verringert nur das Zeitfenster für einen Angreifer und nur für diese bestimmte Art von Angriff.

Wie in den Kommentaren erwähnt, können Arrays, die vom Garbage Collector verschoben werden, Streukopien der Daten im Speicher belassen. Ich glaube, dies ist implementierungsspezifisch - der Garbage Collector löscht möglicherweise den gesamten Speicher, um dies zu vermeiden. Selbst wenn dies der Fall ist, gibt es immer noch die Zeit, in der das char[]die tatsächlichen Charaktere als Angriffsfenster enthält.


3
Wenn ein Prozess Zugriff auf den Speicher Ihrer Anwendung hat, handelt es sich bereits um eine Sicherheitsverletzung, oder?
Yeti

5
@ Yeti: Ja, aber es ist nicht so, als wäre es schwarz und weiß. Wenn sie nur einen Schnappschuss des Speichers erhalten können, möchten Sie reduzieren, wie viel Schaden dieser Schnappschuss anrichten kann, oder das Fenster verkleinern, in dem ein wirklich schwerwiegender Schnappschuss erstellt werden kann.
Jon Skeet

11
Eine übliche Angriffsmethode besteht darin, einen Prozess auszuführen, der viel Speicher zuweist und ihn dann nach übrig gebliebenen, nützlichen Daten wie Kennwörtern durchsucht. Der Prozess benötigt keinen magischen Zugriff auf den Speicherplatz eines anderen Prozesses. Es hängt nur davon ab, dass andere Prozesse sterben, ohne zuerst vertrauliche Daten zu löschen, und das Betriebssystem löscht auch nicht den Speicher (oder die Seitenpuffer), bevor es für einen neuen Prozess verfügbar gemacht wird. Durch das Löschen von an char[]Orten gespeicherten Passwörtern wird diese Angriffslinie abgeschnitten, was bei Verwendung nicht möglich ist String.
Ted Hopp

Wenn das Betriebssystem den Speicher nicht löscht, bevor es einem anderen Prozess übergeben wird, hat das Betriebssystem große Sicherheitsprobleme! Technisch gesehen erfolgt das Löschen jedoch häufig mit Tricks im geschützten Modus. Wenn die CPU defekt ist (z. B. Intel Meldown), können alte Speicherinhalte weiterhin gelesen werden.
Mikko Rantalainen

1222

Während andere Vorschläge hier gültig erscheinen, gibt es einen anderen guten Grund. Mit plain haben StringSie eine viel höhere Wahrscheinlichkeit, das Kennwort versehentlich auf Protokolle , Monitore oder einen anderen unsicheren Ort zu drucken . char[]ist weniger anfällig.

Bedenken Sie:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}

Drucke:

String: Password
Array: [C@5829428e

40
@voo, aber ich bezweifle, dass Sie sich per direktem Schreiben zum Streamen und Verketten anmelden würden. Protokollierungs-Framework würde das char [] in eine gute Ausgabe
verwandeln

41
@ Thr4wn Standardimplementierung von toStringist classname@hashcode. [Cstellt dar char[], der Rest ist hexadezimaler Hash-Code.
Konrad Garus

15
Interessante Idee. Ich möchte darauf hinweisen, dass dies nicht auf Scala übertragen wird, was für Arrays einen sinnvollen toString hat.
Mauhiz

37
Ich würde einen PasswordKlassentyp dafür schreiben . Es ist weniger dunkel und schwieriger, versehentlich irgendwohin zu gelangen.
Rechtsfalte

8
Warum sollte jemand annehmen, dass das char-Array als Objekt umgewandelt werden würde? Ich bin mir nicht sicher, warum jeder diese Antwort mag. Angenommen, Sie haben dies getan: System.out.println ("Password" .toCharArray ());
GC_

680

Um ein offizielles Dokument zu zitieren, der Java Cryptography Architecture Guide sagt über char[]vs. StringPasswörter (über Passwort-basierte Verschlüsselung, aber dies ist im Allgemeinen über Passwörter natürlich):

Es erscheint logisch, das Passwort in einem Objekt vom Typ zu sammeln und zu speichern java.lang.String. Hier ist jedoch die Einschränkung: Objects vom Typ Stringsind unveränderlich, dh es sind keine Methoden definiert, mit denen Sie den Inhalt einer String Nachverwendung ändern (überschreiben) oder auf Null setzen können . Diese Funktion macht StringObjekte ungeeignet zum Speichern sicherheitsrelevanter Informationen wie Benutzerkennwörter. Sie sollten charstattdessen immer sicherheitsrelevante Informationen in einem Array sammeln und speichern .

Die Richtlinie 2-2 der Secure Coding Guidelines für die Java-Programmiersprache, Version 4.0, sagt ebenfalls etwas Ähnliches aus (obwohl dies ursprünglich im Zusammenhang mit der Protokollierung steht):

Richtlinie 2-2: Protokollieren Sie keine hochsensiblen Informationen

Einige Informationen, wie z. B. Sozialversicherungsnummern (SSNs) und Kennwörter, sind sehr sensibel. Diese Informationen sollten nicht länger als nötig aufbewahrt werden und auch nicht von Administratoren angezeigt werden. Beispielsweise sollte es nicht an Protokolldateien gesendet werden und sein Vorhandensein sollte nicht durch Suchen erkennbar sein. Einige vorübergehende Daten können in veränderlichen Datenstrukturen wie Char-Arrays gespeichert und unmittelbar nach der Verwendung gelöscht werden. Das Löschen von Datenstrukturen hat die Effektivität auf typischen Java-Laufzeitsystemen verringert, da Objekte transparent in den Speicher für den Programmierer verschoben werden.

Diese Richtlinie hat auch Auswirkungen auf die Implementierung und Verwendung von Bibliotheken auf niedrigerer Ebene, die keine semantischen Kenntnisse über die Daten haben, mit denen sie sich befassen. Beispielsweise kann eine Low-Level-String-Parsing-Bibliothek den Text protokollieren, mit dem sie arbeitet. Eine Anwendung kann eine SSN mit der Bibliothek analysieren. Dies führt zu einer Situation, in der die SSNs Administratoren mit Zugriff auf die Protokolldateien zur Verfügung stehen.


4
Dies ist genau die fehlerhafte / falsche Referenz, über die ich unter Jons Antwort spreche. Es ist eine bekannte Quelle mit viel Kritik.
Bests

37
@bestass kannst du bitte auch eine referenz zitieren?
user961954

10
@bestass sorry, aber es Stringist ziemlich gut verstanden und wie es sich in der JVM verhält ... es gibt gute Gründe, char[]anstelle von StringPasswörtern auf sichere Weise zu verwenden.
SnakeDoc

3
noch einmal, obwohl das Passwort als Zeichenfolge vom Browser an die Anforderung als 'Zeichenfolge' nicht char übergeben wird? Egal was Sie tun, es ist eine Zeichenfolge. An welchem ​​Punkt sollte sie bearbeitet und verworfen werden und niemals im Speicher gespeichert werden.
Dawesi

3
@Dawesi - At which point- das ist anwendungsspezifisch, aber die allgemeine Regel lautet, dies zu tun, sobald Sie etwas erhalten, das ein Passwort sein soll (Klartext oder auf andere Weise). Sie erhalten es beispielsweise im Rahmen einer HTTP-Anfrage vom Browser. Sie können die Lieferung nicht steuern, aber Sie können Ihren eigenen Speicher steuern. Sobald Sie ihn erhalten, geben Sie ihn in ein Zeichen [] ein, tun Sie, was Sie damit tun müssen, setzen Sie dann alles auf '0' und lassen Sie den gc fordere es zurück.
Luis.espinal

354

Zeichenarrays ( char[]) können nach der Verwendung gelöscht werden, indem jedes Zeichen auf Null und Zeichenfolgen nicht gesetzt wird. Wenn jemand das Speicherbild irgendwie sehen kann, kann er ein Kennwort im Klartext sehen, wenn Zeichenfolgen verwendet werden. Wenn char[]es jedoch verwendet wird, ist das Kennwort nach dem Löschen von Daten mit Nullen sicher.


13
Standardmäßig nicht sicher. Wenn es sich um eine Webanwendung handelt, geben die meisten Webcontainer das Kennwort HttpServletRequestim Klartext an das Objekt weiter. Wenn die JVM-Version 1.6 oder niedriger ist, befindet sie sich im Permgen-Raum. Wenn es in 1.7 ist, ist es noch lesbar, bis es gesammelt wird. (Wann immer das ist.)
avgvstvs

4
@avgvstvs: Strings werden nicht automatisch in den Permgen-Bereich verschoben, dies gilt nur für internierte Strings. Außerdem wird der Permgenraum nur mit einer geringeren Rate einer Müllabfuhr unterzogen. Das eigentliche Problem mit dem Permgen-Raum ist seine feste Größe, was genau der Grund ist, warum niemand gedankenlos intern()auf beliebige Zeichenfolgen zurückgreifen sollte . Aber Sie haben insofern Recht, als die StringInstanzen an erster Stelle existieren (bis sie gesammelt werden), und wenn Sie sie anschließend in char[]Arrays umwandeln, ändert sich dies nicht.
Holger

3
@Holger siehe docs.oracle.com/javase/specs/jvms/se6/html/… "Andernfalls wird eine neue Instanz der Klassenzeichenfolge erstellt, die die von der Struktur CONSTANT_String_info angegebene Folge von Unicode-Zeichen enthält. Diese Klasseninstanz ist das Ergebnis von String-Literal-Ableitung. Schließlich wird die interne Methode der neuen String-Instanz aufgerufen. " In 1.6 rief die JVM intern für Sie an, wenn sie identische Sequenzen erkannte.
avgvstvs

3
@Holger, Sie haben Recht, ich habe den konstanten Pool und den String-Pool zusammengeführt, aber es ist auch falsch, dass der Permgen-Speicher nur auf internierte Strings angewendet wird. Vor 1.7 befanden sich sowohl der Konstante_Pool als auch der String_Pool im Permgen-Raum. Das bedeutet, dass die einzige Klasse von Strings, die dem Heap zugewiesen wurden, wie Sie sagten, new String()oder StringBuilder.toString() ich verwaltete Anwendungen mit vielen String-Konstanten, und wir hatten als Ergebnis viel Permgen-Creep. Bis 1.7.
avgvstvs

5
@avgvstvs: Nun, String-Konstanten werden, wie die JLS-Mandate, immer interniert, daher die Aussage, dass internierte Strings im Permgen-Raum gelandet sind und implizit auf String-Konstanten angewendet werden. Der einzige Unterschied besteht darin, dass in erster Linie Zeichenfolgenkonstanten im Permgenraum erstellt wurden, während das Aufrufen intern()einer beliebigen Zeichenfolge die Zuweisung einer äquivalenten Zeichenfolge im Permgenraum bewirken kann. Letzteres könnte GC'ed bekommen, wenn es keine wörtliche Zeichenfolge mit demselben Inhalt gäbe, die dieses Objekt teilt…
Holger

220

Einige Leute glauben, dass Sie den Speicher, der zum Speichern des Passworts verwendet wird, überschreiben müssen, sobald Sie es nicht mehr benötigen. Dies verkürzt das Zeitfenster, in dem ein Angreifer das Kennwort von Ihrem System lesen muss, und ignoriert vollständig die Tatsache, dass der Angreifer bereits genügend Zugriff benötigt, um den JVM-Speicher zu entführen, um dies zu tun. Ein Angreifer mit so viel Zugriff kann Ihre Schlüsselereignisse abfangen, was dies völlig unbrauchbar macht (AFAIK, bitte korrigieren Sie mich, wenn ich falsch liege).

Aktualisieren

Dank der Kommentare muss ich meine Antwort aktualisieren. Anscheinend gibt es zwei Fälle, in denen dies zu einer (sehr) geringfügigen Sicherheitsverbesserung führen kann, da dadurch die Zeit verkürzt wird, die ein Kennwort auf der Festplatte landen könnte. Trotzdem denke ich, dass es für die meisten Anwendungsfälle übertrieben ist.

  • Ihr Zielsystem ist möglicherweise schlecht konfiguriert oder Sie müssen davon ausgehen, dass dies der Fall ist, und Sie müssen paranoid gegenüber Core Dumps sein (kann gültig sein, wenn die Systeme nicht von einem Administrator verwaltet werden).
  • Ihre Software muss übermäßig paranoid sein, um zu verhindern, dass Datenlecks auftreten, wenn der Angreifer Zugriff auf die Hardware erhält - beispielsweise mit TrueCrypt (nicht mehr verfügbar ), VeraCrypt oder CipherShed .

Wenn möglich, würden beide Probleme durch Deaktivieren der Core-Dumps und der Auslagerungsdatei behoben. Sie würden jedoch Administratorrechte erfordern und könnten die Funktionalität reduzieren (weniger Arbeitsspeicher), und das Abrufen von RAM von einem laufenden System wäre weiterhin ein berechtigtes Anliegen.


33
Ich würde "völlig nutzlos" durch "nur eine kleine Sicherheitsverbesserung" ersetzen. Beispielsweise könnten Sie Zugriff auf einen Speicherauszug erhalten, wenn Sie Lesezugriff auf ein tmp-Verzeichnis, einen schlecht konfigurierten Computer und einen Absturz in Ihrer Anwendung haben. In diesem Fall könnten Sie keinen Keylogger installieren, aber Sie könnten den Core Dump analysieren.
Joachim Sauer

44
Das Löschen unverschlüsselter Daten aus dem Speicher, sobald Sie damit fertig sind, wird als bewährte Methode angesehen, nicht weil sie narrensicher sind (nicht). sondern weil es Ihre Gefährdung verringert. Echtzeitangriffe werden dadurch nicht verhindert. Aber weil es als Schadensbegrenzungstool dient, indem es die Datenmenge, die bei einem rückwirkenden Angriff auf einen Speicher-Snapshot verfügbar wird, erheblich reduziert (z. B. eine Kopie des Speichers Ihrer Apps, die in eine Auslagerungsdatei geschrieben oder aus dem Speicher ausgelesen wurde) von einem laufenden Server und auf einen anderen verschoben, bevor sein Status fehlschlug).
Dan spielt am Feuer

9
Ich stimme der Haltung dieser Antwort eher zu. Ich würde es wagen vorzuschlagen, dass die meisten Sicherheitsverletzungen mit Konsequenzen auf einer viel höheren Abstraktionsebene auftreten als die Bits im Speicher. Sicher, es gibt wahrscheinlich Szenarien in hyper-sicheren Verteidigungssystemen, in denen dies von erheblicher Bedeutung sein kann, aber ernsthaftes Denken auf dieser Ebene ist für 99% der Anwendungen, in denen .NET oder Java eingesetzt werden (im Zusammenhang mit der Speicherbereinigung), übertrieben.
Kingdango

10
Nach dem Eindringen von Heartbleed in den Serverspeicher, bei dem Kennwörter enthüllt wurden, würde ich die Zeichenfolge "nur eine geringfügige Sicherheitsverbesserung" durch "unbedingt erforderlich, um die Zeichenfolge nicht für Kennwörter zu verwenden, sondern stattdessen ein Zeichen [] zu verwenden" ersetzen.
Peter vdL

9
@PetervdL heartbleed erlaubte nur das Lesen einer bestimmten wiederverwendeten Sammlung von Puffern (die sowohl für sicherheitskritische Daten als auch für Netzwerk-E / A verwendet werden, ohne dazwischen zu löschen - aus Leistungsgründen). Sie können dies nicht mit einem Java-String kombinieren, da diese vom Design her nicht wiederverwendbar sind . Sie können mit Java auch nicht in einen zufälligen Speicher lesen, um an den Inhalt eines Strings zu gelangen. Die Sprach- und Designprobleme, die zu Herzblutungen führen, sind mit Java Strings einfach nicht möglich.
Josefx

86

Ich denke nicht, dass dies ein gültiger Vorschlag ist, aber ich kann zumindest den Grund erraten.

Ich denke, die Motivation besteht darin, sicherzustellen, dass Sie alle Spuren des Passworts im Speicher sofort und mit Sicherheit löschen können, nachdem es verwendet wurde. Mit a können char[]Sie jedes Element des Arrays mit einem Leerzeichen oder etwas sicherem überschreiben. Sie können den internen Wert von a auf Stringdiese Weise nicht bearbeiten .

Aber das allein ist keine gute Antwort. warum nicht einfach sicherstellen, dass ein Verweis auf die char[]oder Stringnicht entkommt? Dann gibt es kein Sicherheitsproblem. Aber die Sache ist, dass StringObjekte intern()theoretisch bearbeitet und im konstanten Pool am Leben erhalten werden können. Ich nehme an, die Verwendung char[]verbietet diese Möglichkeit.


4
Ich würde nicht sagen, dass das Problem darin besteht, dass Ihre Referenzen "entkommen" oder nicht. Es ist nur so, dass Zeichenfolgen für einige zusätzliche Zeit im Speicher unverändert bleiben, während char[]sie geändert werden können, und dann ist es irrelevant, ob sie gesammelt werden oder nicht. Und da das Internieren von Zeichenfolgen explizit für Nicht-Literale durchgeführt werden muss, ist es dasselbe wie zu sagen, dass a char[]durch ein statisches Feld referenziert werden kann.
Groo

1
Ist das Passwort nicht als Zeichenfolge aus dem Formularbeitrag gespeichert?
Dawesi

66

Die Antwort wurde bereits gegeben, aber ich möchte ein Problem, das ich kürzlich entdeckt habe, mit Java-Standardbibliotheken teilen. Während sie jetzt sehr darauf achten, Kennwortzeichenfolgen char[]überall zu ersetzen (was natürlich eine gute Sache ist), scheinen andere sicherheitskritische Daten beim Löschen aus dem Speicher übersehen zu werden.

Ich denke zB an die PrivateKey- Klasse. Stellen Sie sich ein Szenario vor, in dem Sie einen privaten RSA-Schlüssel aus einer PKCS # 12-Datei laden und damit einen Vorgang ausführen. In diesem Fall würde es Ihnen nicht viel helfen, nur das Kennwort zu schnüffeln, solange der physische Zugriff auf die Schlüsseldatei ordnungsgemäß eingeschränkt ist. Als Angreifer wären Sie viel besser dran, wenn Sie den Schlüssel direkt anstelle des Passworts erhalten würden. Die gewünschten Informationen können vielfältig durchgesickert sein, Core-Dumps, eine Debugger-Sitzung oder Auslagerungsdateien sind nur einige Beispiele.

Und wie sich herausstellt, gibt es nichts, mit dem Sie die privaten Informationen PrivateKeyaus dem Speicher löschen können, da es keine API gibt, mit der Sie die Bytes löschen können, die die entsprechenden Informationen bilden.

Dies ist eine schlechte Situation, da in diesem Dokument beschrieben wird, wie dieser Umstand möglicherweise ausgenutzt werden kann.

Die OpenSSL-Bibliothek überschreibt beispielsweise kritische Speicherabschnitte, bevor private Schlüssel freigegeben werden. Da Java durch Müll gesammelt wird, benötigen wir explizite Methoden zum Löschen und Ungültigmachen privater Informationen für Java-Schlüssel, die unmittelbar nach Verwendung des Schlüssels angewendet werden sollen.


Eine Möglichkeit, dies zu umgehen, besteht darin, eine Implementierung zu verwenden PrivateKey, die ihren privaten Inhalt nicht tatsächlich in den Speicher lädt: beispielsweise über ein PKCS # 11-Hardware-Token. Möglicherweise könnte eine Software-Implementierung von PKCS # 11 die manuelle Bereinigung des Speichers übernehmen. Vielleicht PKCS11ist es besser , so etwas wie den NSS-Store zu verwenden (der den größten Teil seiner Implementierung mit dem Store-Typ in Java teilt ). Der KeychainStore(OSX-Keystore) lädt den gesamten Inhalt des privaten Schlüssels in seine PrivateKeyInstanzen, sollte dies aber nicht müssen. (Nicht sicher, was der WINDOWS-MYKeyStore unter Windows macht.)
Bruno

@Bruno Sicher, hardwarebasierte Token leiden nicht darunter, aber was ist mit Situationen, in denen Sie mehr oder weniger gezwungen sind, einen Softwareschlüssel zu verwenden? Nicht jeder Einsatz verfügt über das Budget, um sich einen HSM leisten zu können. Software-Schlüsselspeicher müssen den Schlüssel irgendwann in den Speicher laden, daher sollten wir IMO zumindest die Option erhalten, den Speicher nach Belieben wieder zu löschen.
Prägung

Absolut, ich habe mich nur gefragt, ob sich einige Software-Implementierungen, die einem HSM entsprechen, möglicherweise besser in Bezug auf die Bereinigung des Speichers verhalten. Wenn Sie beispielsweise die Clientauthentifizierung mit Safari / OSX verwenden, wird beim Safari-Prozess der private Schlüssel nie angezeigt. Die vom Betriebssystem bereitgestellte zugrunde liegende SSL-Bibliothek kommuniziert direkt mit dem Sicherheitsdämon, der den Benutzer zur Verwendung des Schlüssels aus dem Schlüsselbund auffordert. Während dies alles in Software erfolgt, kann eine ähnliche Trennung hilfreich sein, wenn das Signieren an eine andere Entität (auch softwarebasiert) delegiert wird, die den Speicher besser entladen oder löschen würde.
Bruno

@Bruno: Interessante Idee, eine zusätzliche Indirektionsebene, die sich um das Löschen des Speichers kümmert, würde dies tatsächlich transparent lösen. Das Schreiben eines PKCS # 11-Wrappers für den Software-Schlüsselspeicher könnte bereits den Trick tun?
Prägung

51

Wie Jon Skeet feststellt, gibt es keinen anderen Weg als die Verwendung von Reflexion.

Wenn Reflexion jedoch eine Option für Sie ist, können Sie dies tun.

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);
    }
}

wenn ausgeführt

please enter a password
hello world
password: '***********'

Hinweis: Wenn das Zeichen [] des Strings als Teil eines GC-Zyklus kopiert wurde, besteht die Möglichkeit, dass sich die vorherige Kopie irgendwo im Speicher befindet.

Diese alte Kopie würde nicht in einem Heap-Dump angezeigt, aber wenn Sie direkten Zugriff auf den Rohspeicher des Prozesses haben, können Sie ihn sehen. Im Allgemeinen sollten Sie vermeiden, dass jemand einen solchen Zugang hat.


2
Besser auch etwas tun, um zu verhindern, dass die Länge des Passworts gedruckt wird, von der wir erhalten '***********'.
chux

@chux Sie können ein Zeichen mit der Breite Null verwenden, obwohl dies eher verwirrend als nützlich sein kann. Es ist nicht möglich, die Länge eines char-Arrays zu ändern, ohne Unsafe zu verwenden. ;)
Peter Lawrey

5
Aufgrund der String-Deduplizierung von Java 8 kann dies ziemlich destruktiv sein. Möglicherweise löschen Sie andere Zeichenfolgen in Ihrem Programm, die zufällig den gleichen Wert wie das Kennwort String hatten. Unwahrscheinlich, aber möglich ...
Jamp

1
@PeterLawrey Es muss mit einem JVM-Argument aktiviert werden, aber es ist da. Kann hier darüber lesen: blog.codecentric.de/de/2014/08/…
jamp

1
Es besteht eine hohe Wahrscheinlichkeit, dass sich das Kennwort noch im Scannerinternen Puffer befindet und, da Sie es nicht verwendet haben System.console().readPassword(), in lesbarer Form im Konsolenfenster angezeigt wird. In den meisten praktischen Anwendungsfällen usePasswordist die Ausführungsdauer jedoch das eigentliche Problem. Wenn Sie beispielsweise eine Verbindung zu einem anderen Computer herstellen, dauert es sehr lange und teilt einem Angreifer mit, dass es der richtige Zeitpunkt ist, nach dem Kennwort im Heap zu suchen. Die einzige Lösung besteht darin, zu verhindern, dass Angreifer den Heapspeicher lesen…
Holger

42

Dies sind alle Gründe, warum man für ein Passwort ein char [] Array anstelle von String wählen sollte .

1. Da Strings in Java unveränderlich sind, bleibt das Kennwort, wenn Sie es als einfachen Text speichern, im Speicher verfügbar, bis der Garbage Collector es löscht. Da String zur Wiederverwendbarkeit im String-Pool verwendet wird, besteht eine ziemlich hohe Wahrscheinlichkeit, dass dies der Fall ist bleiben lange im Gedächtnis, was eine Sicherheitsbedrohung darstellt.

Da jeder, der Zugriff auf den Speicherauszug hat, das Kennwort im Klartext finden kann, sollten Sie aus diesem Grund immer ein verschlüsseltes Kennwort anstelle von einfachem Text verwenden. Da Strings unveränderlich sind, kann der Inhalt von Strings auf keinen Fall geändert werden, da bei jeder Änderung ein neuer String erzeugt wird. Wenn Sie jedoch ein char [] verwenden, können Sie dennoch alle Elemente als leer oder null festlegen. Durch das Speichern eines Kennworts in einem Zeichenarray wird das Sicherheitsrisiko beim Diebstahl eines Kennworts deutlich verringert.

2. Java selbst empfiehlt die Verwendung der Methode getPassword () von JPasswordField, die ein char [] zurückgibt, anstelle der veralteten Methode getText (), die Kennwörter im Klartext unter Angabe von Sicherheitsgründen zurückgibt. Es ist gut, den Ratschlägen des Java-Teams zu folgen und sich an Standards zu halten, anstatt gegen diese zu verstoßen.

3. Bei String besteht immer das Risiko, dass einfacher Text in einer Protokolldatei oder Konsole gedruckt wird. Wenn Sie jedoch ein Array verwenden, wird der Inhalt eines Arrays nicht gedruckt, sondern der Speicherort wird gedruckt. Obwohl dies kein wirklicher Grund ist, macht es dennoch Sinn.

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

Referenziert von diesem Blog . Ich hoffe das hilft.


10
Dies ist überflüssig. Diese Antwort ist eine genaue Version der Antwort von @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . Bitte kopieren Sie dieselbe Antwort nicht, fügen Sie sie nicht zweimal ein oder duplizieren Sie sie.
Glücklicher

Was ist der Unterschied zwischen 1.) System.out.println ("Character password:" + charPassword); und 2.) System.out.println (charPassword); Weil es das gleiche "Unbekannte" wie die Ausgabe gibt.
Vaibhav_Sharma

3
@Lucky Leider wurde die ältere Antwort, auf die Sie verlinkt haben, aus demselben Blog wie diese Antwort plagiiert und wurde nun gelöscht. Siehe meta.stackoverflow.com/questions/389144/… . Diese Antwort wurde einfach aus demselben Blog ausgeschnitten und eingefügt und nichts hinzugefügt. Es hätte also einfach ein Kommentar sein müssen, der auf die ursprüngliche Quelle verweist.
Skomisa

41

Bearbeiten: Wenn ich nach einem Jahr Sicherheitsforschung auf diese Antwort zurückkomme, stelle ich fest, dass dies die unglückliche Folgerung ist, dass Sie jemals Klartext-Passwörter vergleichen würden. Bitte nicht. Verwenden Sie einen sicheren Einweg-Hash mit einem Salz und einer angemessenen Anzahl von Iterationen . Erwägen Sie die Verwendung einer Bibliothek: Dieses Zeug ist schwer richtig zu machen!

Ursprüngliche Antwort: Was ist mit der Tatsache, dass String.equals () eine Kurzschlussauswertung verwendet und daher für einen Timing-Angriff anfällig ist? Es mag unwahrscheinlich sein, aber Sie könnten theoretisch den Passwortvergleich zeitlich festlegen, um die richtige Zeichenfolge zu bestimmen.

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;
}

Weitere Ressourcen zum Timing von Angriffen:


Aber das könnte auch im char [] -Vergleich vorhanden sein, irgendwo würden wir das auch bei der Passwortüberprüfung tun. Wie ist char [] besser als string?
Mohit Kanwar

3
Sie haben absolut Recht, der Fehler kann so oder so gemacht werden. Das Wissen über das Problem ist hier das Wichtigste, da es in Java keine explizite Kennwortvergleichsmethode für String-basierte oder char [] -basierte Kennwörter gibt. Ich würde sagen, die Versuchung, compare () für Strings zu verwenden, ist ein guter Grund, sich für char [] zu entscheiden. Auf diese Weise haben Sie zumindest die Kontrolle darüber, wie der Vergleich durchgeführt wird (ohne String zu erweitern, was ein Schmerz imo ist).
Graphentheorie

Neben dem Vergleich Passwörter im Klartext ohnehin nicht das Richtige ist, die Versuchung , zu verwenden Arrays.equalsfür char[]so hoch wie für String.equals. Wenn sich jemand darum kümmerte, gab es eine dedizierte Schlüsselklasse, die das eigentliche Passwort kapselte und sich um die Probleme kümmerte. Oh, warten Sie, die echten Sicherheitspakete haben dedizierte Schlüsselklassen. In diesen Fragen und Antworten geht es nur um eine Gewohnheit außerhalb von ihnen, z. B. JPasswordFieldum die Verwendung char[]statt String(wo die eigentlichen Algorithmen byte[]sowieso verwenden).
Holger

Sicherheitsrelevante Software sollte sleep(secureRandom.nextInt())sowieso etwas tun, bevor sie einen Anmeldeversuch ablehnt. Dies beseitigt nicht nur die Möglichkeit von Timing-Angriffen, sondern wirkt auch Brute-Force-Versuchen entgegen.
Holger

36

Es gibt nichts, was Ihnen das char-Array gegen String gibt, es sei denn, Sie bereinigen es nach der Verwendung manuell, und ich habe noch niemanden gesehen, der das tatsächlich tut. Für mich ist die Präferenz von char [] gegenüber String etwas übertrieben.

Schauen Sie sich hier die weit verbreitete Spring Security-Bibliothek an und fragen Sie sich, ob Spring Security-Leute inkompetent sind oder char [] -Kennwörter einfach nicht viel Sinn machen. Wenn ein böser Hacker Speicherabbilder Ihres Arbeitsspeichers abruft, stellen Sie sicher, dass er alle Kennwörter erhält, auch wenn Sie sie auf ausgeklügelte Weise ausblenden.

Java ändert sich jedoch ständig, und einige beängstigende Funktionen wie die String-Deduplizierungsfunktion von Java 8 können String-Objekte ohne Ihr Wissen internieren. Aber das ist ein anderes Gespräch.


Warum ist String-Deduplizierung beängstigend? Dies gilt nur, wenn mindestens zwei Zeichenfolgen denselben Inhalt haben. Welche Gefahr besteht also, wenn diese beiden bereits identischen Zeichenfolgen dasselbe Array verwenden? Oder lassen Sie uns umgekehrt fragen: Wenn es keine String-Deduplizierung gibt, welcher Vorteil ergibt sich aus der Tatsache, dass beide Strings ein unterschiedliches Array (mit demselben Inhalt) haben? In beiden Fällen wird es eine Reihe dieser Inhalte geben, die mindestens so lange lebendig sind, wie die am längsten lebende Zeichenfolge dieser Inhalte lebt…
Holger

@Holger Alles, was außerhalb Ihrer Kontrolle liegt, ist ein potenzielles Risiko. Wenn beispielsweise zwei Benutzer dasselbe Passwort haben, speichert diese wunderbare Funktion beide in einem einzigen Zeichen [], wodurch deutlich wird, dass sie gleich sind, und nicht sicher, ob dies der Fall ist ein großes Risiko, aber immer noch
Oleg Mikheev

Wenn Sie Zugriff auf den Heapspeicher und beide Zeichenfolgeninstanzen haben, spielt es keine Rolle, ob die Zeichenfolgen auf dasselbe Array oder auf zwei Arrays mit demselben Inhalt verweisen. Jedes ist leicht herauszufinden. Zumal es sowieso irrelevant ist. Wenn Sie sich an diesem Punkt befinden, greifen Sie auf beide Kennwörter zu, unabhängig davon, ob sie identisch sind oder nicht. Der eigentliche Fehler liegt in der Verwendung von Klartext-Passwörtern anstelle von gesalzenen Hashes.
Holger

@Holger, um das Passwort zu überprüfen, muss es einige Zeit im Klartext im Speicher sein, 10 ms, auch wenn es nur darum geht, einen gesalzenen Hash daraus zu erstellen. Wenn dann auch für 10 ms zwei identische Passwörter im Speicher verbleiben, kann die Deduplizierung ins Spiel kommen. Wenn es wirklich Strings interniert, bleiben sie viel länger im Speicher. Ein System, das monatelang nicht neu gestartet wird, sammelt viele davon. Nur theoretisieren.
Oleg Mikheev

Es scheint, dass Sie ein grundlegendes Missverständnis bezüglich der String-Deduplizierung haben. Es werden keine "internen Zeichenfolgen" verwendet, sondern nur Zeichenfolgen mit demselben Inhalt auf dasselbe Array verweisen, wodurch die Anzahl der Array-Instanzen, die das Klartext-Kennwort enthalten , tatsächlich verringert wird , da alle bis auf eine Array-Instanz zurückgefordert und überschrieben werden können durch andere Objekte sofort. Diese Zeichenfolgen werden weiterhin wie jede andere Zeichenfolge gesammelt. Vielleicht hilft es, wenn Sie verstehen, dass die Deduplizierung tatsächlich vom Garbage Collector durchgeführt wird, für Zeichenfolgen, die nur mehrere GC-Zyklen überstanden haben.
Holger

30

Zeichenfolgen sind unveränderlich und können nach ihrer Erstellung nicht mehr geändert werden. Wenn Sie ein Kennwort als Zeichenfolge erstellen, verbleiben streunende Verweise auf das Kennwort auf dem Heap oder im Zeichenfolgenpool. Wenn jemand einen Heap-Dump des Java-Prozesses erstellt und sorgfältig durchsucht, kann er möglicherweise die Kennwörter erraten. Natürlich werden diese nicht verwendeten Zeichenfolgen durch Müll gesammelt, aber das hängt davon ab, wann der GC startet.

Auf der anderen Seite sind char [] veränderbar, sobald die Authentifizierung abgeschlossen ist. Sie können sie mit einem beliebigen Zeichen wie allen Ms oder Backslashes überschreiben. Selbst wenn jemand einen Heap-Dump erstellt, kann er möglicherweise nicht die Kennwörter abrufen, die derzeit nicht verwendet werden. Dies gibt Ihnen mehr Kontrolle in dem Sinne, dass Sie den Objektinhalt selbst löschen, anstatt darauf zu warten, dass der GC dies tut.


Sie werden nur dann GC-geprüft, wenn die betreffende JVM> 1,6 ist. Vor 1.7 wurden alle Strings in Permgen gespeichert.
avgvstvs

@avgvstvs: "Alle Zeichenfolgen wurden in Permgen gespeichert" ist einfach absolut falsch. Dort wurden nur internierte Zeichenfolgen gespeichert, und wenn sie nicht aus Zeichenfolgenliteralen stammen, auf die durch Code verwiesen wird, wurden sie immer noch mit Müll gesammelt. Denken Sie nur darüber nach. Wenn Zeichenfolgen in JVMs vor 1.7 im Allgemeinen nie GC-fähig waren, wie könnte eine Java-Anwendung länger als ein paar Minuten überleben?
Holger

@Holger Das ist falsch. Interniert stringsUND der StringPool (Pool zuvor verwendeter Strings) wurde BEIDE vor 1.7 in Permgen gespeichert. Siehe auch Abschnitt 5.1: docs.oracle.com/javase/specs/jvms/se6/html/… Die JVM hat immer überprüft Strings, ob sie denselben Referenzwert haben, und hat String.intern()FOR YOU aufgerufen . Das Ergebnis war, dass jedes Mal, wenn die JVM identische Strings im constant_pooloder -Haufen entdeckte, diese in das Permgen verschoben wurden. Und ich habe bis 1.7 an mehreren Anwendungen mit "Creeping Permgen" gearbeitet. Es war ein echtes Problem.
avgvstvs

Um es noch einmal zusammenzufassen: Bis 1.7 begannen Strings im Heap, als sie verwendet wurden, wurden sie in das constant_poolIN-Permgen gelegt, und wenn ein String mehr als einmal verwendet wurde, wurde er interniert.
avgvstvs

@avgvstvs: Es gibt keinen "Pool zuvor verwendeter Zeichenfolgen". Sie werfen ganz andere Dinge zusammen. Es gibt einen Laufzeit-String-Pool, der String-Literale und explizit internierte Strings enthält, aber keinen anderen. Und jede Klasse hat einen konstanten Pool, der Konstanten zur Kompilierungszeit enthält . Diese Zeichenfolgen werden automatisch zum Laufzeitpool hinzugefügt, aber nur diese , nicht jede Zeichenfolge.
Holger

21

String ist unveränderlich und geht in den String-Pool. Einmal geschrieben, kann es nicht überschrieben werden.

char[] ist ein Array, das Sie überschreiben sollten, sobald Sie das Kennwort verwendet haben. So sollte es gemacht werden:

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');
}

Ein Szenario, in dem der Angreifer es verwenden könnte, ist ein Absturzspeicherauszug. Wenn die JVM abstürzt und einen Speicherauszug generiert, wird das Kennwort angezeigt.

Das ist nicht unbedingt ein böswilliger externer Angreifer. Dies kann ein Support-Benutzer sein, der zu Überwachungszwecken Zugriff auf den Server hat. Er konnte in einen Crashdump schauen und die Passwörter finden.


2
ch = null; Sie können dies nicht tun
Yugerten

Aber request.getPassword()erstellt der String nicht schon und fügt ihn dem Pool hinzu?
Tvde1

1
ch = '0'ändert die lokale Variable ch; es hat keine Auswirkung auf das Array. Und Ihr Beispiel ist sowieso sinnlos. Sie beginnen mit einer von Ihnen aufgerufenen Zeichenfolgeninstanz toCharArray(), erstellen ein neues Array, und selbst wenn Sie das neue Array korrekt überschreiben, ändert es die Zeichenfolgeninstanz nicht, sodass es keinen Vorteil gegenüber der Verwendung der Zeichenfolge hat Beispiel.
Holger

@ Holger danke. Der Bereinigungscode für das Char-Array wurde korrigiert.
ACV

3
Sie können einfach verwendenArrays.fill(pass, '0');
Holger

18

Die kurze und unkomplizierte Antwort wäre, weil sie char[]veränderlich ist, während StringObjekte dies nicht sind.

Stringsin Java sind unveränderliche Objekte. Aus diesem Grund können sie nach ihrer Erstellung nicht mehr geändert werden. Daher besteht die einzige Möglichkeit, ihren Inhalt aus dem Speicher zu entfernen, darin, sie mit Müll zu sammeln. Erst dann kann der vom Objekt freigegebene Speicher überschrieben werden und die Daten gehen verloren.

Jetzt erfolgt die Speicherbereinigung in Java nicht in einem garantierten Intervall. Das Stringkann somit für eine lange Zeit im Speicher verbleiben, und wenn ein Prozess während dieser Zeit abstürzt, kann der Inhalt der Zeichenfolge in einem Speicherauszug oder einem Protokoll enden.

Mit einem Zeichenarray können Sie das Kennwort lesen, die Arbeit so schnell wie möglich beenden und dann sofort den Inhalt ändern.


@fallenidol Überhaupt nicht. Lesen Sie sorgfältig und Sie werden die Unterschiede finden.
Pritam Banerjee

12

String in Java ist unveränderlich. Wenn also eine Zeichenfolge erstellt wird, bleibt sie im Speicher, bis sie durch Müll gesammelt wird. Jeder, der Zugriff auf den Speicher hat, kann den Wert der Zeichenfolge lesen.
Wenn der Wert der Zeichenfolge geändert wird, wird eine neue Zeichenfolge erstellt. So bleiben sowohl der ursprüngliche Wert als auch der geänderte Wert im Speicher, bis der Müll gesammelt wird.

Mit dem Zeichenarray kann der Inhalt des Arrays geändert oder gelöscht werden, sobald der Zweck des Kennworts erfüllt ist. Der ursprüngliche Inhalt des Arrays wird nach dem Ändern und noch vor dem Start der Speicherbereinigung nicht im Speicher gefunden.

Aus Sicherheitsgründen ist es besser, das Kennwort als Zeichenarray zu speichern.


2

Es ist fraglich, ob Sie String oder Char [] für diesen Zweck verwenden sollten, da beide ihre Vor- und Nachteile haben. Dies hängt davon ab, was der Benutzer benötigt.

Da Strings in Java unveränderlich sind, wird bei jedem Versuch, Ihren String zu manipulieren, ein neues Objekt erstellt, und der vorhandene String bleibt davon unberührt. Dies könnte als Vorteil für das Speichern eines Kennworts als Zeichenfolge angesehen werden, das Objekt bleibt jedoch auch nach der Verwendung im Speicher. Wenn also jemand irgendwie den Speicherort des Objekts hat, kann diese Person Ihr an diesem Speicherort gespeichertes Passwort leicht nachverfolgen.

Char [] ist veränderlich, hat jedoch den Vorteil, dass der Programmierer nach seiner Verwendung das Array explizit bereinigen oder Werte überschreiben kann. Wenn es fertig ist, wird es gereinigt und niemand kann jemals etwas über die Informationen erfahren, die Sie gespeichert haben.

Basierend auf den oben genannten Umständen kann man sich ein Bild davon machen, ob man für seine Anforderungen mit String oder mit Char [] arbeiten soll.


0

Fallzeichenfolge:

    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

Fall 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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.