Aktualizacja skrótu hasła bez wymuszania nowego hasła dla istniejących użytkowników

32

Utrzymujesz istniejącą aplikację z ustaloną bazą użytkowników. Z czasem zdecydowano, że obecna technika haszowania haseł jest nieaktualna i wymaga aktualizacji. Ponadto, z powodów UX, nie chcesz, aby istniejący użytkownicy byli zmuszani do aktualizacji swojego hasła. Cała aktualizacja skrótu hasła musi odbywać się za ekranem.

Załóżmy „uproszczony” model bazy danych dla użytkowników, który zawiera:

  1. ID
  2. E-mail
  3. Hasło

Jak podchodzi się do rozwiązania takiego wymogu?


Moje obecne myśli to:

  • utwórz nową metodę mieszania w odpowiedniej klasie
  • zaktualizuj tabelę użytkowników w bazie danych, aby zawierała dodatkowe pole hasła
  • Gdy użytkownik pomyślnie zaloguje się przy użyciu nieaktualnego skrótu hasła, wypełnij drugie pole hasła zaktualizowanym skrótem

Pozostawia mi to problem polegający na tym, że nie mogę rozsądnie rozróżnić między użytkownikami, którzy mają, a tymi, którzy nie zaktualizowali skrótu hasła, i dlatego będę zmuszony sprawdzić oba. To wydaje się okropnie wadliwe.

Co więcej, oznacza to w zasadzie, że stara technika haszująca może zostać zmuszona do pozostania na czas nieokreślony, dopóki każdy użytkownik nie zaktualizuje swojego hasła. Dopiero w tym momencie mogłem rozpocząć usuwanie starego testu mieszania i usunąć zbędne pole bazy danych.

Głównie szukam tutaj kilku wskazówek projektowych, ponieważ moje obecne „rozwiązanie” jest brudne, niekompletne, a co nie, ale jeśli rzeczywisty kod jest wymagany do opisania możliwego rozwiązania, możesz użyć dowolnego języka.

Willem
źródło
4
Dlaczego nie byłbyś w stanie odróżnić zestawu od nieuzbrojonego mieszania wtórnego? Po prostu ustaw kolumnę bazy danych na wartość null i sprawdź, czy jest pusta.
Kilian Foth
6
Wybierz typ skrótu jako nową kolumnę zamiast pola dla nowego skrótu. Po zaktualizowaniu skrótu zmień pole typu. W ten sposób, kiedy to powtórzy się w przyszłości, będziesz już w stanie się zająć i nie będziesz mieć szansy, że utrzymasz stary (prawdopodobnie mniej bezpieczny) skrót.
Michael Kohne
1
Myślę, że jesteś na dobrej drodze. Za 6 miesięcy lub za rok możesz zmusić pozostałych użytkowników do zmiany hasła. Rok później czy cokolwiek innego możesz pozbyć się starego pola.
GlenPeterson
3
Dlaczego nie tylko haszować stary hasz?
Siyuan Ren,
ponieważ chcesz, aby hasło zostało zaszyfrowane (nie stary hash), aby odblokowanie go (tj. porównanie z hashowanym hasłem podanym przez użytkownika) dało ... hasło - nie inną wartość, którą należy następnie „odhasować” według starej metody. Możliwe, ale nie czystsze.
Michael Durrant

Odpowiedzi:

25

Sugerowałbym dodanie nowego pola, „hash_method”, gdzie być może 1 oznacza starą metodę, a 2 oznacza nową metodę.

Rozsądnie mówiąc, jeśli zależy ci na tego rodzaju rzeczach, a Twoja aplikacja jest stosunkowo długa (co już najwyraźniej już jest), prawdopodobnie stanie się to ponownie, ponieważ kryptografia i bezpieczeństwo informacji to tak ewoluująca, raczej nieprzewidywalna dziedzina. Był czas, kiedy zwykłe uruchamianie przez MD5 było standardem, jeśli w ogóle użyto haszowania! Wtedy można by pomyśleć, że powinni użyć SHA1, a teraz jest solenie, globalna sól + pojedyncza losowa sól, SHA3, różne metody generowania liczb losowych gotowych na krypto ... to nie będzie po prostu „zatrzymywać”, więc możesz napraw to w rozszerzalny, powtarzalny sposób.

Powiedzmy teraz, że masz coś takiego (mam nadzieję, że w pseudo-javascript):

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

Teraz, gdy zdajesz sobie sprawę, że istnieje lepsza metoda, wystarczy podać drobne zmiany:

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

Przy tworzeniu tej odpowiedzi nie ucierpieli tajni agenci ani programiści.

BrianH
źródło
+1 To wydaje się dość rozsądnym streszczeniem, dzięki. Chociaż może się to skończyć przeniesieniem pól skrótu i ​​metody mieszania do osobnej tabeli, gdy ją zaimplementuję.
Willem,
1
Problem z tą techniką polega na tym, że w przypadku kont się nie loguje, nie można aktualizować skrótów. Z mojego doświadczenia wynika, że ​​większość użytkowników nie zaloguje się przez lata, jeśli kiedykolwiek (chyba że usuniesz nieaktywnych użytkowników). Twoja abstrakcja też tak naprawdę nie działa, ponieważ nie uwzględnia soli. Standardowa abstrakcja ma dwie funkcje, jedną do weryfikacji, a drugą do tworzenia.
CodesInChaos,
Mieszanie haseł prawie nie ewoluowało w ciągu ostatnich 15 lat. Bcrypt został opublikowany w 1999 roku i wciąż jest jednym z zalecanych skrótów. Od tego czasu nastąpiła tylko jedna znacząca poprawa: sekwencyjne szyfrowanie pamięci, zapoczątkowane przez scrypt.
CodesInChaos,
To powoduje, że stare skróty są podatne na atak (np. Jeśli ktoś ukradnie DB). Hashowanie każdego starego skrótu za pomocą nowej, bezpieczniejszej metody łagodzi to do pewnego stopnia (nadal będziesz potrzebować typu skrótu, aby odróżnić newHash(oldHash, salt)lubnewHash(password, salt)
dbkk
42

Jeśli zależy ci na jak najszybszym wdrożeniu nowego schematu haszowania dla wszystkich użytkowników (np. Ponieważ stary jest naprawdę niepewny), istnieje sposób na natychmiastową „migrację” każdego hasła.

Chodzi przede wszystkim o haszowanie skrótu . Zamiast czekać na podanie przez użytkowników istniejącego hasła ( p) przy następnym logowaniu, od razu używasz nowego algorytmu mieszającego ( H2) na istniejącym haszu już wygenerowanym przez stary algorytm ( H1):

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

Po wykonaniu tej konwersji nadal możesz doskonale zweryfikować hasło; wystarczy wykonać obliczenia H2(H1(p'))zamiast poprzedniego, H1(p')gdy użytkownik próbuje zalogować się przy użyciu hasła p'.

Teoretycznie, technika ta może być stosowana na wielu (migracji H3, H4etc.). W praktyce chciałbyś pozbyć się starej funkcji skrótu, zarówno ze względu na wydajność, jak i czytelność. Na szczęście jest to dość łatwe: po kolejnym udanym logowaniu po prostu oblicz nowy hash hasła użytkownika i zastąp go istniejącym hash-a-hash:

hash = H2(p)

Będziesz także potrzebował dodatkowej kolumny, aby zapamiętać, jaki hash przechowujesz: hasło lub stary skrót. W niefortunnym przypadku wycieku bazy danych ta kolumna nie powinna ułatwiać pracy crackera; niezależnie od jego wartości, atakujący nadal musiałby odwrócić bezpieczny H2algorytm zamiast starego H1.

Xion
źródło
3
Olbrzymi +1: H2 (H1 (p)) jest zwykle tak samo bezpieczny, jak bezpośrednie użycie H2 (p), nawet jeśli H1 jest okropny, ponieważ (1) H2 zajmuje się solą i rozciąganiem oraz (2) problemy z „zepsutymi” skrótami, takimi jak MD5, nie wpływają na haszowanie hasła. Powiązane: crypto.stackexchange.com/questions/2945/…
orip
Interesujące, pomyślałbym, że haszowanie starego hasza stworzyłoby pewnego rodzaju „problem” bezpieczeństwa. Wydaje mi się, że się myliłem.
Willem,
4
jeśli H1 jest naprawdę okropny , nie będzie to bezpieczne. Bycie okropnym w tym kontekście polega głównie na utracie dużej ilości entropii z danych wejściowych. Nawet MD4 i MD5 są prawie doskonałe pod tym względem, więc to podejście jest niepewne tylko w przypadku niektórych skrótów homebrew lub skrótów o naprawdę krótkich wynikach (poniżej 80 bitów).
CodesInChaos
1
W takim przypadku prawdopodobnie jedyną opcją jest przyznanie się, że mocno spieprzyłeś, po prostu śmiało i zresetuj hasło dla wszystkich użytkowników. W tym momencie mniej chodzi o migrację, a więcej o kontrolę szkód.
Xion
8

Twoje rozwiązanie (dodatkowa kolumna w bazie danych) jest całkowicie do przyjęcia. Jedynym problemem jest ten, o którym już wspomniałeś: stary skrót jest nadal używany dla użytkowników, którzy nie uwierzytelnili się od czasu zmiany.

Aby uniknąć tej sytuacji, możesz poczekać, aż najbardziej aktywni użytkownicy przejdą na nowy algorytm mieszania, a następnie:

  1. Usuń konta użytkowników, którzy nie uwierzytelniają się zbyt długo. Nie ma nic złego w usuwaniu konta użytkownika, który nigdy nie odwiedził witryny przez trzy lata.

  2. Wyślij wiadomość e-mail do pozostałych użytkowników, którzy nie uwierzytelnili się przez jakiś czas, informując, że mogą być zainteresowani czymś nowym. Pomoże to jeszcze bardziej zmniejszyć liczbę kont korzystających ze starszego skrótu.

    Uważaj: jeśli masz opcję bez spamu, użytkownicy mogą sprawdzać w Twojej witrynie, nie wysyłaj wiadomości e-mail do użytkowników, którzy ją sprawdzili.

  3. Wreszcie, kilka tygodni po kroku 2, zniszcz hasło użytkowników, którzy nadal korzystają ze starej techniki. Kiedy i jeśli spróbują się uwierzytelnić, zobaczą, że ich hasło jest nieprawidłowe; jeśli nadal są zainteresowani twoją usługą, mogą ją zresetować.

Arseni Mourzenko
źródło
1

Prawdopodobnie nigdy nie będziesz mieć więcej niż kilka typów skrótów, więc możesz to rozwiązać bez rozszerzania bazy danych.

Jeśli metody mają różne długości skrótów, a długość skrótu każdej metody jest stała (powiedzmy, przejście z md5 na hmac-sha1), możesz odróżnić metodę od długości skrótu. Jeśli mają taką samą długość, możesz najpierw obliczyć skrót za pomocą nowej metody, a następnie starej metody, jeśli pierwszy test się nie powiedzie. Jeśli masz pole (niepokazane) informujące, kiedy użytkownik był ostatnio aktualizowany / tworzony, możesz użyć go jako wskaźnika dla której metody użyć.

Horyzont zdarzeń
źródło
1

Zrobiłem coś takiego i jest sposób, aby stwierdzić, czy jest to nowy skrót ORAZ uczynić go jeszcze bardziej bezpiecznym. Sądzę, że bezpieczniejsze jest dla ciebie dobre, jeśli poświęcasz czas na aktualizację swojego skrótu ;-)

Dodaj nową kolumnę do tabeli DB o nazwie „sól” i używaj jej jako soli generowanej losowo przez użytkownika. (prawie zapobiega atakom na tęczowe stoły)

W ten sposób, jeśli moje hasło to „pass123”, a losowa sól to 3333, moim nowym hasłem będzie skrót „pass1233333”.

Jeśli użytkownik ma sól, wiesz, że to nowy skrót. Jeśli sól użytkownika jest zerowa, wiesz, że to stary skrót.

Ben
źródło
0

Bez względu na to, jak dobra jest Twoja strategia migracji, jeśli baza danych została już skradziona (i nie można negatywnie udowodnić, że tak się nigdy nie stało), hasła przechowywane z niezabezpieczonymi funkcjami skrótu mogą już zostać naruszone. Jedynym ograniczeniem tego jest wymaganie od użytkowników zmiany haseł.

Nie jest to problem, który można rozwiązać (tylko) przez napisanie kodu. Rozwiązaniem jest informowanie użytkowników i klientów o zagrożeniach, na jakie zostali narażeni. Gdy tylko zechcesz być na ten temat otwarty, możesz przeprowadzić migrację, po prostu resetując hasło każdego użytkownika, ponieważ jest to najłatwiejsze i najbezpieczniejsze rozwiązanie. Wszystkie alternatywy naprawdę polegają na ukrywaniu tego, że spieprzyłeś.

Florian Winter
źródło
1
Jeden kompromisem, który zrobiłem w przeszłości jest utrzymanie starych haseł na okres czasu, zajmij je jako ludzie zalogować, ale po tym czasie zdał pan wtedy wymusić zresetowanie hasła na każdego, kto nie w trakcie logowania ten czas. Tak więc masz okres, w którym nadal masz mniej bezpieczne hasła, ale jest on ograniczony czasowo, a także minimalizujesz zakłócenia dla użytkowników, którzy logują się regularnie.
Sean Burton,