java passwortabfrage - Warum wird char[]String für Kennwörter vorgezogen?




programmieren passworteingabe (15)

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[] .


Answers

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.


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.


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:


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

1. Da Strings in Java unveränderlich sind, wenn Sie das Kennwort als reinen Text speichern, wird es im Speicher verfügbar sein, bis der Garbage Collector es löscht. Da String zur Wiederverwendbarkeit im String-Pool verwendet wird, ist die Wahrscheinlichkeit groß, dass er lange im Speicher bleibt Dauer, die eine Sicherheitsbedrohung darstellen. Jeder, der Zugriff auf den Speicherabzug hat, kann das Kennwort in Klartext finden. Aus diesem Grund sollten Sie immer ein verschlüsseltes Kennwort als Nur-Text verwenden. Da Strings unveränderlich sind, kann der Inhalt von Strings nicht geändert werden, da bei jeder Änderung ein neuer String erzeugt wird. Wenn Sie char [] verwenden, können Sie trotzdem sein gesamtes Element als leer oder auf Null setzen. Das Speichern von Kennwörtern in einem Zeichenfeld verringert somit das Sicherheitsrisiko beim Stehlen von Kennwörtern.

2. Java selbst empfiehlt die Verwendung der getPassword () - Methode von JPasswordField, die eine char [] - und die veraltete getText () - Methode zurückgibt, die das Kennwort im Klartext unter Angabe des Sicherheitsgrunds zurückgibt. Es ist gut, den Ratschlägen des Java-Teams zu folgen und sich an den Standard zu halten, anstatt dagegen zu sein.

3. Mit String 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 des Arrays wird gedruckt. wenn auch kein wirklicher Grund, aber trotzdem sinnvoll.

    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: [[email protected]

Referenz von: http://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.html Hoffe, das hilft.


  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.


1) Da Strings in Java unveränderlich sind, wenn Sie das Kennwort als Klartext speichern, ist es im Speicher verfügbar, bis der Garbage Collector es löscht. Da String im String-Pool für die Wiederverwendbarkeit verwendet wird, ist die Wahrscheinlichkeit hoch, dass er im Speicher verbleibt für die lange Dauer, die eine Sicherheitsbedrohung darstellt. Da jeder, der Zugriff auf den Speicherabzug hat, das Kennwort im Klartext finden kann, sollten Sie immer ein verschlüsseltes Kennwort als Klartext verwenden. Da Strings unveränderlich sind, gibt es keine Möglichkeit, den Inhalt von Strings zu ändern, da bei jeder Änderung ein neuer String erzeugt wird. Wenn Sie char [] verwenden, können Sie trotzdem sein gesamtes Element als leer oder auf Null setzen. Durch das Speichern des Passworts in einem Character Array wird das Sicherheitsrisiko des Diebstahls des Passworts verringert.

2) Java selbst empfiehlt die Verwendung der getPassword () - Methode von JPasswordField, die eine char [] - und die veraltete getText () - Methode zurückgibt, die das Kennwort im Klartext unter Angabe des Sicherheitsgrunds zurückgibt. Es ist gut, den Ratschlägen des Java-Teams zu folgen und sich an den Standard zu halten, anstatt dagegen zu sein.


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.


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


Um ein offizielles Dokument zu zitieren, sagt der Java Cryptography Architecture-Leitfaden String Kennwörtern char[] und String (zur kennwortbasierten Verschlüsselung, hier geht es jedoch im Allgemeinen um Kennwörter):

Es erscheint logisch, das Passwort in einem Objekt vom Typ java.lang.String zu sammeln und zu speichern. Hier ist jedoch die Einschränkung: Object des Typs String sind unveränderlich, dh es sind keine Methoden definiert, mit denen Sie den Inhalt eines String nach der Verwendung ändern (überschreiben) oder auf Null setzen können. Diese Funktion macht String Objekte zum Speichern sicherheitsrelevanter Informationen, wie z. B. Benutzerkennwörter, ungeeignet. Stattdessen sollten Sie immer sicherheitsrelevante Informationen in einem char Array sammeln und speichern.

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

Richtlinie 2-2: Protokollieren Sie keine hochsensiblen Informationen

Einige Informationen, wie Sozialversicherungsnummern (SSNs) und Kennwörter, sind äußerst sensibel. Diese Informationen sollten nicht länger als nötig aufbewahrt werden, auch nicht für Administratoren. Es sollte beispielsweise nicht an Protokolldateien gesendet werden, und sein Vorhandensein sollte durch Suchvorgänge nicht erkennbar sein. Einige vorübergehende Daten können in veränderlichen Datenstrukturen wie Char-Arrays gespeichert und sofort nach der Verwendung gelöscht werden. Das Löschen von Datenstrukturen hat auf typischen Java-Laufzeitsystemen eine geringere Wirksamkeit, da Objekte transparent in den Speicher des Programmierers verschoben werden.

Diese Richtlinie hat auch Auswirkungen auf die Implementierung und Verwendung von untergeordneten Bibliotheken, die kein semantisches Wissen ü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 ein SSN mit der Bibliothek parsen. Dadurch wird eine Situation geschaffen, in der die SSNs für Administratoren mit Zugriff auf die Protokolldateien verfügbar sind.


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 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.


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 in Java ist unveränderlich. Immer wenn eine Zeichenfolge erstellt wird, bleibt sie im Speicher, bis der Müll gesammelt wird. Jeder, der Zugriff auf den Speicher hat, kann also den Wert der Zeichenfolge lesen.
Wenn der Wert der Zeichenfolge geändert wird, wird am Ende 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 Zeichen-Array kann der Inhalt des Arrays geändert oder gelöscht werden, sobald der Zweck des Passworts erfüllt ist. Der ursprüngliche Inhalt des Arrays wird nach seiner Änderung und noch vor dem Beginn der Speicherbereinigung nicht im Speicher abgelegt.

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


Eine Möglichkeit, Verzweigungsvorhersagefehler zu vermeiden, besteht darin, eine Nachschlagetabelle zu erstellen und diese anhand der Daten zu indizieren. Stefan de Bruijn hat das in seiner Antwort diskutiert.

In diesem Fall wissen wir jedoch, dass Werte im Bereich [0, 255] liegen, und wir kümmern uns nur um Werte> = 128. Das bedeutet, dass wir leicht ein einzelnes Bit extrahieren können, das uns sagt, ob wir einen Wert wollen oder nicht: durch Verschieben Bei den Daten rechts 7 Bits verbleiben ein 0-Bit oder ein 1-Bit, und wir möchten den Wert nur hinzufügen, wenn wir ein 1-Bit haben. Nennen wir dieses Bit das "Entscheidungsbit".

Durch Verwendung des 0/1-Werts des Entscheidungsbits als Index in einem Array können wir Code erstellen, der gleich schnell ist, unabhängig davon, ob die Daten sortiert sind oder nicht. Unser Code fügt immer einen Wert hinzu, aber wenn das Entscheidungsbit 0 ist, fügen wir den Wert an einer Stelle hinzu, an der wir uns nicht interessieren. Hier ist der Code:

// Test
clock_t start = clock();
long long a[] = {0, 0};
long long sum;

for (unsigned i = 0; i < 100000; ++i)
{
    // Primary loop
    for (unsigned c = 0; c < arraySize; ++c)
    {
        int j = (data[c] >> 7);
        a[j] += data[c];
    }
}

double elapsedTime = static_cast<double>(clock() - start) / CLOCKS_PER_SEC;
sum = a[1];

Dieser Code verschwendet die Hälfte der Additionen, hat jedoch nie einen Verzweigungsvorhersagefehler. Es ist bei zufälligen Daten enorm schneller als bei der Version mit einer tatsächlichen if-Anweisung.

In meinen Tests war eine explizite Nachschlagetabelle etwas schneller als diese, wahrscheinlich, weil die Indizierung in eine Nachschlagetabelle etwas schneller war als die Bitverschiebung. Dies zeigt, wie mein Code die Lookup-Tabelle lutaufbaut und verwendet ( unimaginativ für "LookUp Table" im Code aufgerufen ). Hier ist der C ++ - Code:

// declare and then fill in the lookup table
int lut[256];
for (unsigned c = 0; c < 256; ++c)
    lut[c] = (c >= 128) ? c : 0;

// use the lookup table after it is built
for (unsigned i = 0; i < 100000; ++i)
{
    // Primary loop
    for (unsigned c = 0; c < arraySize; ++c)
    {
        sum += lut[data[c]];
    }
}

In diesem Fall hatte die Lookup-Tabelle nur 256 Byte, sodass sie gut in einen Cache passt und alles schnell war. Diese Technik würde nicht gut funktionieren, wenn die Daten 24-Bit-Werte hätten und wir nur die Hälfte davon wollten ... Die Nachschlagetabelle wäre viel zu groß, um praktisch zu sein. Andererseits können wir die beiden oben gezeigten Techniken kombinieren: Zuerst die Bits verschieben und dann eine Nachschlagetabelle indizieren. Bei einem 24-Bit-Wert, bei dem nur der obere Halbwert gewünscht wird, können wir die Daten möglicherweise um 12 Bit nach rechts verschieben und einen 12-Bit-Wert für einen Tabellenindex belassen. Ein 12-Bit-Tabellenindex impliziert eine Tabelle mit 4096 Werten, was praktisch sein kann.

EDIT: Eine Sache, die ich vergessen habe.

Anstatt eine ifAnweisung zu verwenden, kann die Technik des Indexierens in ein Array verwendet werden, um zu entscheiden, welcher Zeiger verwendet werden soll. Ich sah eine Bibliothek , die binären Bäume umgesetzt und anstelle von zwei genannten Zeigern mit ( pLeftund pRightoder was auch immer) hatte eine Länge-2 Array von Zeigern und verwendet , um die „Entscheidungs - Bit - Technik“ zu entscheiden , welches zu folgen. Zum Beispiel anstelle von:

if (x < node->value)
    node = node->pLeft;
else
    node = node->pRight;

Diese Bibliothek würde so etwas tun:

i = (x < node->value);
node = node->link[i];

Hier ist ein Link zu diesem Code: Red Black Trees , ewig verwirrt







java string security passwords char