Aktualisieren des Kennwort-Hashings, ohne ein neues Kennwort für vorhandene Benutzer zu erzwingen

32

Sie pflegen eine vorhandene Anwendung mit einer etablierten Benutzerbasis. Mit der Zeit wird entschieden, dass die aktuelle Kennwort-Hashing-Technik veraltet ist und aktualisiert werden muss. Außerdem möchten Sie aus UX-Gründen nicht, dass vorhandene Benutzer gezwungen werden, ihr Kennwort zu aktualisieren. Das gesamte Passwort-Hashing-Update muss hinter dem Bildschirm erfolgen.

Nehmen Sie für Benutzer ein 'vereinfachtes' Datenbankmodell an, das Folgendes enthält:

  1. ICH WÜRDE
  2. Email
  3. Passwort

Wie geht man vor, um eine solche Anforderung zu lösen?


Meine aktuellen Gedanken sind:

  • Erstellen Sie eine neue Hash-Methode in der entsprechenden Klasse
  • Aktualisieren Sie die Benutzertabelle in der Datenbank, um ein zusätzliches Kennwortfeld aufzunehmen
  • Sobald sich ein Benutzer erfolgreich mit dem veralteten Kennwort-Hash anmeldet, füllen Sie das zweite Kennwortfeld mit dem aktualisierten Hash aus

Dies führt zu dem Problem, dass ich nicht sinnvoll zwischen Benutzern mit und Benutzern ohne Passwortaktualisierung unterscheiden kann und daher gezwungen bin, beide zu überprüfen. Dies scheint schrecklich fehlerhaft.

Darüber hinaus bedeutet dies im Grunde genommen, dass die alte Hashing-Technik so lange bestehen bleiben muss, bis jeder einzelne Benutzer sein Passwort aktualisiert hat. Erst in diesem Moment konnte ich anfangen, die alte Hashing-Prüfung zu entfernen und das überflüssige Datenbankfeld zu entfernen.

Ich suche hier hauptsächlich nach einigen Designtipps, da meine aktuelle "Lösung" schmutzig ist, unvollständig und was nicht, aber wenn tatsächlicher Code zur Beschreibung einer möglichen Lösung erforderlich ist, können Sie eine beliebige Sprache verwenden.

Willem
quelle
4
Warum können Sie nicht zwischen einem gesetzten und einem nicht gesetzten sekundären Hash unterscheiden? Machen Sie einfach die Datenbankspalte nullfähig und suchen Sie nach null.
Kilian Foth
6
Verwenden Sie als neue Spalte einen Hash-Typ anstelle eines Feldes für den neuen Hash. Wenn Sie den Hash aktualisieren, ändern Sie das Typfeld. Auf diese Weise sind Sie, wenn dies in Zukunft erneut geschieht, bereits in der Lage, Geschäfte zu tätigen, und es besteht keine Chance, dass Sie an einem alten (vermutlich weniger sicheren) Hash festhalten.
Michael Kohne
1
Ich denke, Sie sind auf dem richtigen Weg. In 6 Monaten oder einem Jahr können Sie alle verbleibenden Benutzer dazu zwingen, ihr Kennwort zu ändern. Ein Jahr später oder wie auch immer, können Sie das alte Feld loswerden.
GlenPeterson
3
Warum nicht einfach den alten Hasch haschen?
Siyuan Ren
weil Sie möchten, dass das Passwort gehasht wird (nicht der alte Hash), so dass das Aufheben des Hashs (dh der Vergleich mit einem vom Benutzer angegebenen Hash) Ihnen ... das Passwort gibt - keinen weiteren Wert, der dann "enthasht" werden muss nach der alten Methode. Möglich aber nicht sauberer.
Michael Durrant

Antworten:

25

Ich würde vorschlagen, ein neues Feld "hash_method" hinzuzufügen, mit einer 1, um die alte Methode zu kennzeichnen, und einer 2, um die neue Methode zu kennzeichnen.

Wenn Sie sich für diese Art von Dingen interessieren und Ihre Anwendung relativ langlebig ist (was es anscheinend bereits ist), wird dies wahrscheinlich wieder vorkommen, da Kryptografie und Informationssicherheit ein sich entwickelndes, eher unvorhersehbares Gebiet sind. Es gab eine Zeit, in der ein einfacher Durchlauf durch MD5 Standard war, wenn überhaupt Hashing verwendet wurde! Dann könnte man meinen, sie sollten SHA1 verwenden, und jetzt gibt es Salting, Global Salt + Individual Random Salt, SHA3, verschiedene Methoden der kryptofertigen Zufallszahlengenerierung Beheben Sie dies auf erweiterbare, wiederholbare Weise.

Nehmen wir also an, Sie haben jetzt so etwas wie (der Einfachheit halber in Pseudo-Javascript, hoffe ich):

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword());


if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword)
{
    // TODO: Learn what "hash" means
    return clearPassword + "H@$I-I";
}

Jetzt, da Sie feststellen, dass es eine bessere Methode gibt, müssen Sie nur noch ein kleines Refactoring durchführen:

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword(), user.getHashingMethod());

if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword, hashMethod)
{
    // Note: Hash doesn't mean what we thought it did. Oops...

    var hash;
    if (hashMethod == 1)
    {
        hash = clearPassword + "H@$I-I";
    }
    else if (hashMethod == 2)
    {
        // Totally gonna get it right this time.
        hash = SuperMethodTheNSASaidWasAwesome(clearPassword);
    }
    return hash;
}

Bei der Erstellung dieser Antwort wurden keine Geheimagenten oder Programmierer verletzt.

BrianH
quelle
+1 Das scheint eine vernünftige Zusammenfassung zu sein, danke. Wenn ich dies implementiere, verschiebe ich möglicherweise die Felder hash und hashmethod in eine separate Tabelle.
Willem
1
Das Problem bei dieser Technik ist, dass Sie bei Konten, die sich nicht anmelden, die Hashes nicht aktualisieren können. Meiner Erfahrung nach werden sich die meisten Benutzer jahrelang nicht anmelden (es sei denn, Sie löschen inaktive Benutzer). Ihre Abstraktion funktioniert auch nicht wirklich, da sie keine Salze berücksichtigt. Die Standardabstraktion hat zwei Funktionen, eine zur Verifizierung und eine zur Erstellung.
CodesInChaos
Passwort-Hashing hat sich in den letzten 15 Jahren kaum entwickelt. Bcrypt wurde 1999 veröffentlicht und ist immer noch einer der empfohlenen Hashes. Seitdem ist nur eine wesentliche Verbesserung eingetreten: sequentielles Speichern von Hard-Hashes, das durch Verschlüsselung entwickelt wurde.
CodesInChaos
Dadurch werden die alten Hashes angreifbar (z. B. wenn jemand die DB stiehlt). Hashing jeden alten Hash mit der neuen sichereren Methode mildert diese bis zu einem gewissen Grad (Sie immer noch den Hash - Typen zu unterscheiden brauchen, newHash(oldHash, salt)odernewHash(password, salt)
dbkk
42

Wenn Sie genug Wert darauf legen, das neue Hashing-Schema so schnell wie möglich für alle Benutzer bereitzustellen (z. B. weil das alte wirklich unsicher ist), gibt es tatsächlich eine Möglichkeit für die sofortige "Migration" jedes Kennworts.

Die Idee ist im Grunde, den Hash zu hacken . Anstatt pbeim nächsten Login darauf zu warten, dass Benutzer ihr vorhandenes Passwort ( ) angeben, verwenden Sie sofort den neuen Hashing-Algorithmus ( H2) für den vorhandenen Hash, der bereits vom alten Algorithmus ( H1) erstellt wurde:

hash = H2(hash)  # where hash was previously equal to H1(p) 

Nach dieser Konvertierung können Sie die Kennwortüberprüfung weiterhin problemlos durchführen. Sie müssen nur H2(H1(p'))anstelle der vorherigen Berechnungen durchführen, H1(p')wenn der Benutzer versucht, sich mit einem Kennwort anzumelden p'.

Theoretisch könnte diese Technik auf mehrere Migrationen angewendet werden ( H3, H4usw.). In der Praxis sollten Sie die alte Hash-Funktion aus Gründen der Leistung und Lesbarkeit entfernen. Glücklicherweise ist dies ganz einfach: Berechnen Sie bei der nächsten erfolgreichen Anmeldung einfach den neuen Hash des Benutzerpassworts und ersetzen Sie den vorhandenen Hash eines Hashs durch diesen:

hash = H2(p)

Sie benötigen außerdem eine zusätzliche Spalte, um sich zu merken, welchen Hash Sie speichern: den des Passworts oder des alten Hashs. Im unglücklichen Fall eines Datenbanklecks sollte diese Spalte die Arbeit des Crackers nicht einfacher machen. Unabhängig von seinem Wert müsste der Angreifer den sicheren H2Algorithmus und nicht den alten umkehren H1.

Xion
quelle
3
Ein Riese +1: H2 (H1 (p)) ist normalerweise so sicher wie die direkte Verwendung von H2 (p), auch wenn H1 schrecklich ist, weil (1) H2 für Salz und Dehnung sorgt und (2) die Probleme Bei "kaputten" Hashes wie MD5 hat dies keinen Einfluss auf das Hashing von Passwörtern. Siehe auch
orip
Interessanterweise hätte ich gedacht, dass das Haschieren mit dem alten Hasch eine Art "Sicherheitsproblem" schaffen würde. Scheint, ich habe mich geirrt.
Willem
4
Wenn H1 wirklich schrecklich ist, ist dies nicht sicher. In diesem Zusammenhang fürchterlich zu sein, bedeutet vor allem, viel Entropie aus dem Input zu verlieren. Sogar MD4 und MD5 sind in dieser Hinsicht nahezu perfekt, so dass dieser Ansatz nur für einige Homebrew-Hashes oder Hashes mit sehr kurzer Ausgabe (unter 80 Bit) unsicher ist.
CodesInChaos
1
In diesem Fall zugeben , wahrscheinlich die einzige Möglichkeit ist , dass Sie vermasselte hart und gehen Sie einfach weiter und Reset - Passwort für alle Benutzer. An diesem Punkt geht es weniger um Migration als vielmehr um Schadensbegrenzung.
Xion
8

Ihre Lösung (zusätzliche Spalte in der Datenbank) ist vollkommen akzeptabel. Das einzige Problem dabei ist das, das Sie bereits erwähnt haben: Das alte Hashing wird immer noch für Benutzer verwendet, die sich seit der Änderung nicht authentifiziert haben.

Um dies zu vermeiden, müssen Sie möglicherweise warten, bis Ihre aktivsten Benutzer auf den neuen Hashing-Algorithmus umgestellt haben.

  1. Entfernen Sie die Konten von Benutzern, die sich zu lange nicht authentifiziert haben. Es ist nichts Falsches daran, einen Account eines Benutzers zu entfernen, der drei Jahre lang nicht auf die Website gekommen ist.

  2. Senden Sie eine E-Mail an die verbleibenden Benutzer, die sich eine Weile nicht authentifiziert haben, und teilen Sie ihnen mit, dass sie möglicherweise an etwas Neuem interessiert sind. Dies wird dazu beitragen, die Anzahl der Konten, die das ältere Hashing verwenden, weiter zu reduzieren.

    Seien Sie vorsichtig: Wenn Sie eine spamfreie Option haben, die Benutzer auf Ihrer Website überprüfen können, senden Sie die E-Mail nicht an Benutzer, die sie überprüft haben.

  3. Schließlich zerstören Sie einige Wochen nach Schritt 2 das Kennwort für die Benutzer, die noch die alte Technik verwenden. Wann und wenn sie versuchen, sich zu authentifizieren, werden sie feststellen, dass ihr Passwort ungültig ist. Wenn sie immer noch an Ihrem Dienst interessiert sind, können sie ihn zurücksetzen.

Arseni Mourzenko
quelle
1

Sie werden wahrscheinlich nie mehr als ein paar Hash-Typen haben, daher können Sie dies lösen, ohne Ihre Datenbank zu erweitern.

Wenn die Methoden unterschiedliche Hash-Längen haben und die Hash-Länge jeder Methode konstant ist (z. B. Übergang von md5 zu hmac-sha1), können Sie die Methode anhand der Hash-Länge unterscheiden. Wenn sie dieselbe Länge haben, können Sie den Hash zuerst mit der neuen Methode und dann mit der alten Methode berechnen, wenn der erste Test fehlgeschlagen ist. Wenn in einem Feld (nicht angezeigt) angegeben ist, wann der Benutzer zuletzt aktualisiert / erstellt wurde, können Sie dies als Indikator für die zu verwendende Methode verwenden.

Ereignishorizont
quelle
1

Ich habe so etwas gemacht und es gibt eine Möglichkeit festzustellen, ob es sich um den neuen Hash handelt UND ihn noch sicherer zu machen. Ich vermute, sicherer ist eine gute Sache für Sie, wenn Sie sich die Zeit nehmen, Ihren Hash zu aktualisieren ;-)

Fügen Sie Ihrer DB-Tabelle eine neue Spalte mit dem Namen "salt" hinzu, und verwenden Sie sie als pro Benutzer zufällig generiertes Salt. (beugt Regenbogentischangriffen so gut wie vor)

Auf diese Weise wäre mein neues Passwort der Hash von "pass1233333", wenn mein Passwort "pass123" und "random salt" 3333 ist.

Wenn der Benutzer ein Salz hat, wissen Sie, dass es der neue Hash ist. Wenn das Salt des Benutzers null ist, wissen Sie, dass es der alte Hash ist.

Ben
quelle
0

Egal wie gut Ihre Migrationsstrategie ist, wenn die Datenbank bereits gestohlen wurde (und Sie nicht negativ nachweisen können, dass dies noch nie geschehen ist), sind Kennwörter, die mit unsicheren Hash-Funktionen gespeichert wurden, möglicherweise bereits gefährdet. Die einzige Abhilfe besteht darin, dass Benutzer ihre Kennwörter ändern müssen.

Dies ist kein Problem, das (nur) durch Schreiben von Code gelöst werden kann. Die Lösung besteht darin, Benutzer und Kunden über die Risiken zu informieren, denen sie ausgesetzt waren. Sobald Sie dazu bereit sind, können Sie die Migration durchführen, indem Sie einfach das Kennwort jedes Benutzers zurücksetzen, da dies die einfachste und sicherste Lösung ist. Bei allen Alternativen geht es wirklich darum, die Tatsache zu verbergen, dass Sie Mist gebaut haben.

Florian Winter
quelle
1
Ein Kompromiss , dass ich in der Vergangenheit getan haben , ist die alten Passwörter für einen bestimmten Zeitraum zu halten, so dass sie wieder aufzuwärmen , wie die Menschen in sich einzuloggen, dann aber nach , dass Zeit vergangen ist man dann zwingen , ein Passwort - Reset auf alle , die nicht melden Sie sich an bei diese Zeit. Sie haben also einen Zeitraum, in dem Sie immer noch die weniger sicheren Kennwörter haben, der jedoch zeitlich begrenzt ist, und Sie minimieren die Unterbrechungen für die Benutzer, die sich regelmäßig anmelden.
Sean Burton