programmieren - java passworteingabe




Warum wird char[] String für Kennwörter vorgezogen? (12)

In Swing hat das Kennwortfeld eine getPassword() (Returns char[] ) -Methode anstelle der üblichen getText() (Returns String ) -Methode. Ebenso bin ich auf einen Vorschlag gestoßen, String nicht für die Handhabung von Passwörtern zu verwenden.

Warum stellt String eine Gefahr für die Sicherheit von Passwörtern dar? Es ist unbequem, char[] .


  1. Strings sind in Java nicht veränderbar, wenn Sie das Kennwort als Klartext speichern. Es wird im Speicher verfügbar sein, bis der Garbage Collector es löscht. Da Strings im String-Pool für die Wiederverwendbarkeit verwendet werden, ist die Wahrscheinlichkeit groß, dass es lange im Speicher verbleibt Dauer, die eine Sicherheitsbedrohung darstellt. Jeder, der Zugriff auf den Speicherabzug hat, kann das Kennwort im Klartext finden
  2. Java-Empfehlung mit der getPassword() Methode von JPasswordField, die eine char [] - und eine veraltete getText() Methode zurückgibt, die das Kennwort im Klartext unter Angabe des Sicherheitsgrunds zurückgibt.
  3. toString () Es besteht immer die Gefahr, dass in der Protokolldatei oder in der Konsole Nur-Text gedruckt wird. Wenn Sie Array verwenden, wird jedoch nicht der Inhalt des Arrays gedruckt, sondern der Speicherort wird gedruckt.

    String strPwd = "passwd";
    char[] charPwd = new char[]{'p','a','s','s','w','d'};
    System.out.println("String password: " + strPwd );
    System.out.println("Character password: " + charPwd );
    

    String Passwort: passwd

    Zeichenpasswort: [C @ 110b2345

Schlussgedanke: Obwohl die Verwendung von char [] nicht ausreicht, müssen Sie Inhalte löschen, um sicherer zu sein. Ich empfehle außerdem, mit gehashten oder verschlüsselten Kennwörtern anstelle von Klartext zu arbeiten und es aus dem Speicher zu löschen, sobald die Authentifizierung abgeschlossen ist.


Die Antwort wurde bereits gegeben, aber ich möchte ein Thema teilen, das ich kürzlich mit Java-Standardbibliotheken entdeckt habe. Während sie jetzt mit großer Sorgfalt darauf achten, Passwortzeichenfolgen überall durch char[] ersetzen (was natürlich eine gute Sache ist), scheinen andere sicherheitskritische Daten übersehen zu werden, wenn sie aus dem Speicher gelöscht werden.

Ich denke an zB die PrivateKey Klasse. Stellen Sie sich ein Szenario vor, in dem Sie einen privaten RSA-Schlüssel aus einer PKCS # 12-Datei laden und diesen verwenden, um einen Vorgang auszuführen. In diesem Fall würde das Sniffing des Kennworts allein nicht viel helfen, solange der physische Zugriff auf die Schlüsseldatei ordnungsgemäß eingeschränkt ist. Als Angreifer wäre es viel besser, wenn Sie den Schlüssel direkt anstelle des Kennworts erhalten. Die gewünschten Informationen können durchgesickert sein, Kerndumps, eine Debugger-Sitzung oder Auslagerungsdateien sind nur einige Beispiele.

Wie sich herausstellt, können Sie die privaten Informationen eines PrivateKey aus dem Speicher PrivateKey , da es keine API gibt, mit der Sie die Bytes PrivateKey aus denen die entsprechenden Informationen bestehen.

Dies ist eine schlechte Situation, da in diesem paper beschrieben wird, wie dieser Umstand potenziell ausgenutzt werden kann.

Die OpenSSL-Bibliothek beispielsweise überschreibt kritische Speicherbereiche, bevor private Schlüssel freigegeben werden. Da Java nicht mehr vorhanden ist, benötigen wir explizite Methoden, um private Informationen für Java-Schlüssel zu löschen und für ungültig zu erklären, die unmittelbar nach der Verwendung des Schlüssels angewendet werden müssen.


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

Aktualisieren

Dank der Kommentare muss ich meine Antwort aktualisieren. Offensichtlich 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 kann. Dennoch denke ich, dass es für die meisten Anwendungsfälle zu viel ist.

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

Wenn möglich, würde das Deaktivieren von Core-Dumps und der Auslagerungsdatei beide Probleme lösen. Sie würden jedoch Administratorrechte erfordern und möglicherweise die Funktionalität einschränken (weniger Arbeitsspeicher). Das Herausnehmen des Arbeitsspeichers von einem laufenden System wäre nach wie vor ein Problem.


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

Ich denke, die Motivation möchte sicherstellen, dass Sie alle Spuren des Kennworts im Speicher nach der Verwendung sofort und mit Sicherheit löschen können. Mit einem char[] Sie jedes Element des Arrays mit einem Leerzeichen oder etwas sicherem überschreiben. Sie können den internen Wert eines String diese Weise bearbeiten.

Aber das allein ist keine gute Antwort. Warum nicht einfach sicherstellen, dass ein Verweis auf das char[] oder String nicht entgeht? Dann gibt es keine Sicherheitslücke. Die Sache ist jedoch, dass String Objekte theoretisch intern() bearbeitet und im konstanten Pool am Leben erhalten werden können. Ich vermute, char[] verbietet diese Möglichkeit.


Während andere Vorschläge hier gültig erscheinen, gibt es noch einen weiteren guten Grund. Mit plain String Sie wesentlich höhere Chancen, das Passwort versehentlich auf Protokolle , Monitore oder andere unsichere Stellen 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: [[email protected]

Wie Jon Skeet sagt, gibt es keine andere Möglichkeit, als Reflektion einzusetzen.

Wenn die 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 laufen

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

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

Diese alte Kopie erscheint nicht in einem Heap-Dump, aber wenn Sie direkten Zugriff auf den Rohspeicher des Prozesses haben, können Sie sie sehen. Im Allgemeinen sollten Sie vermeiden, dass jemand solchen Zugriff hat.


Edit: Als ich nach einem Jahr der Sicherheitsrecherche auf diese Antwort zurückkam, ist mir klar, dass dies die unglückliche Andeutung ergibt, dass Sie eigentlich Klartext-Passwörter vergleichen würden. Bitte nicht. Verwenden Sie einen sicheren Einweghash mit einem Salt und einer angemessenen Anzahl von Iterationen . Erwägen Sie die Verwendung einer Bibliothek: Dieses Zeug ist schwer zu finden!

Ursprüngliche Antwort: Was ist mit der Tatsache, dass String.equals () eine Kurzschlussbewertung verwendet und daher anfällig für einen Timing-Angriff ist? Es ist zwar unwahrscheinlich, aber Sie können den Passwortvergleich theoretisch 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;
}

Einige weitere Ressourcen zum Timing von Angriffen:


Strings sind unveränderlich . Das heißt, wenn Sie den String , wenn ein anderer Prozess Speicher freigeben kann, gibt es keine Möglichkeit (außer der reflection ), die Daten zu entfernen, bevor die Garbage Collection aktiviert wird.

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

Ja, dies ist ein Sicherheitsbedenken - aber selbst wenn Sie char[] , reduziert dies nur die Möglichkeit eines Angreifers, und dies gilt nur für diese spezielle Art von Angriff.

Wie in den Kommentaren erwähnt, ist es möglich, dass Arrays, die vom Garbage Collector verschoben werden, verstreute Kopien der Daten im Speicher belassen. Ich glaube, das ist implementierungsspezifisch - der Garbage Collector löscht möglicherweise den gesamten Speicher, um dies zu vermeiden. Auch wenn dies der Fall ist, gibt es immer noch die Zeit, während der char[] die aktuellen Charaktere als Angriffsfenster enthält.


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

Stringsin Java sind unveränderliche Objekte. Deshalb können sie nach der Erstellung nicht mehr geändert werden. Der einzige Weg, den Inhalt aus dem Speicher zu entfernen, ist das Sammeln von Müll. Nur dann kann der vom Objekt freigegebene Speicher überschrieben werden und die Daten werden gelöscht.

Die Speicherbereinigung in Java erfolgt jetzt nicht in einem garantierten Intervall. Das Stringkann also lange im Speicher verbleiben, und wenn ein Prozess während dieser Zeit abstürzt, kann der Inhalt der Zeichenfolge in einem Speicherabbild oder einem Protokoll landen.

Mit einem Zeichen-Array können Sie das Passwort lesen, die Bearbeitung so bald wie möglich beenden und den Inhalt sofort ändern.


Es gibt nichts, was char Array Ihnen gegen String gibt, wenn Sie es nach der Verwendung nicht manuell bereinigen, und ich habe niemanden gesehen, der das tatsächlich getan hat. Für mich ist also die Präferenz von char [] vs String etwas übertrieben.

Werfen Sie einen Blick auf die weit verbreitete Spring Security Bibliothek here und fragen Sie sich - sind Spring Security Jungs inkompetent oder char [] Passwörter einfach nicht viel Sinn machen. Wenn ein böser Hacker Speicherauszüge Ihres Arbeitsspeichers erhält, stellen Sie sicher, dass er alle Passwörter erhält, selbst wenn Sie sie mithilfe ausgeklügelter Methoden ausblenden.

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


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 Passwort 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) {
 for (char ch: pass) {
  ch = null;
 }
}

Ein Szenario, in dem der Angreifer sie verwenden könnte, ist ein Crashdump. Wenn die JVM abstürzt und einen Speicherabzug generiert, können Sie das Kennwort sehen.

Das ist nicht unbedingt ein bösartiger Angreifer. Dies kann ein Support-Benutzer sein, der zu Überwachungszwecken auf den Server zugreifen kann. Er konnte in einen Absturzspeicher sehen und die Passwörter finden.


Zeichenfolgen sind unveränderlich und können nach ihrer Erstellung nicht mehr geändert werden. Durch das Erstellen eines Kennworts als Zeichenfolge werden auf dem Heap oder im Zeichenfolge-Pool andere Verweise auf das Kennwort angezeigt. Wenn nun ein Heap-Dump des Java-Prozesses erstellt und sorgfältig durchsucht wird, kann er die Kennwörter möglicherweise erraten. Natürlich werden diese nicht benutzten Zeichenketten 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 Passwörter erhalten, die derzeit nicht verwendet werden. Auf diese Weise haben Sie mehr Kontrolle in dem Sinne, als würden Sie den Objektinhalt selbst löschen und nicht darauf warten, dass der GC dies tut.





char