Aggiornamento dell'hash delle password senza forzare una nuova password per gli utenti esistenti


32

Gestisci un'applicazione esistente con una base di utenti consolidata. Nel tempo si decide che l'attuale tecnica di hashing della password è obsoleta e deve essere aggiornata. Inoltre, per motivi di UX, non vuoi che gli utenti esistenti siano costretti ad aggiornare la loro password. L'intero aggiornamento dell'hash delle password deve avvenire dietro lo schermo.

Supponiamo un modello di database "semplicistico" per gli utenti che contiene:

  1. ID
  2. E-mail
  3. Parola d'ordine

Come si fa a risolvere un tale requisito?


I miei pensieri attuali sono:

  • creare un nuovo metodo di hashing nella classe appropriata
  • aggiorna la tabella utenti nel database per contenere un campo password aggiuntivo
  • Dopo che un utente ha effettuato correttamente l'accesso utilizzando l'hash della password non aggiornato, riempire il secondo campo della password con l'hash aggiornato

Questo mi lascia con il problema che non riesco a distinguere ragionevolmente tra gli utenti che hanno e quelli che non hanno aggiornato l'hash della password e quindi sarò costretto a controllare entrambi. Sembra orribilmente imperfetto.

Inoltre, ciò significa sostanzialmente che la vecchia tecnica di hashing potrebbe essere costretta a rimanere indefinitamente fino a quando ogni singolo utente non abbia aggiornato la propria password. Solo in quel momento potrei iniziare a rimuovere il vecchio controllo di hashing e rimuovere il campo del database superfluo.

Sono principalmente alla ricerca di alcuni suggerimenti di progettazione qui, poiché la mia attuale "soluzione" è sporca, incompleta e cosa no, ma se è necessario un codice reale per descrivere una possibile soluzione, sentiti libero di usare qualsiasi lingua.


4
Perché non saresti in grado di distinguere tra un set e un hash secondario non impostato? Basta rendere nulla la colonna del database e verificare la presenza di null.
Kilian Foth,

6
Scegli un tipo di hash come nuova colonna, anziché un campo per il nuovo hash. Quando aggiorni l'hash, modifica il campo del tipo. In questo modo, quando questo accadrà di nuovo in futuro, sarai già in grado di affrontare e non c'è alcuna possibilità che tu ti aggrappi a un vecchio hash (presumibilmente meno sicuro).
Michael Kohne,

1
Penso che tu sia sulla buona strada. Tra 6 mesi o un anno da adesso, puoi costringere tutti gli utenti rimanenti a cambiare la loro password. Un anno dopo o qualsiasi altra cosa, puoi sbarazzarti del vecchio campo.
GlenPeterson,

3
Perché non solo hash il vecchio hash?
Siyuan Ren,

perché vuoi che la password sia sottoposta a hash (non il vecchio hash) in modo che la rimozione della hash (ovvero il confronto con una hash fornita dall'utente) ti dia ... la password - non un altro valore che deve quindi essere "cancellato" secondo il vecchio metodo. Possibile ma non più pulito.
Michael Durrant,

Risposte:


25

Suggerirei di aggiungere un nuovo campo, "hash_method", con forse un 1 per indicare il vecchio metodo e un 2 per indicare il nuovo metodo.

In termini ragionevoli, se ti preoccupi di questo genere di cose e la tua applicazione ha una vita relativamente lunga (cosa che a quanto pare già lo è), ciò accadrà probabilmente di nuovo poiché la crittografia e la sicurezza delle informazioni sono un campo così in evoluzione, piuttosto imprevedibile. C'è stato un tempo in cui un semplice passaggio attraverso MD5 era standard, se si utilizzava l'hashish! Quindi si potrebbe pensare che dovrebbero usare SHA1, e ora ci sono salatura, sale globale + sale casuale individuale, SHA3, diversi metodi di generazione di numeri casuali pronti per la criptovaluta ... questo non si fermerà semplicemente, quindi potresti bene risolverlo in modo estensibile e ripetibile.

Quindi, diciamo ora che hai qualcosa del tipo (in pseudo-javascript per semplicità, spero):

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

Ora rendendoti conto che esiste un metodo migliore, devi solo dare un piccolo refactoring:

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

Nessun agente segreto o programmatore fu danneggiato nella produzione di questa risposta.


+1 Sembra un assurdo abbastanza sensato, grazie. Anche se potrei finire per spostare i campi hash e hashmethod in una tabella separata quando implemento questo.
Willem,

1
Il problema con questa tecnica è che per gli account che non effettuano l'accesso non è possibile aggiornare gli hash. Nella mia esperienza, la maggior parte degli utenti non accederà per anni, se non per sempre (a meno che non si eliminino gli utenti inattivi). Anche la tua astrazione non funziona davvero, poiché non tiene conto dei sali. L'astrazione standard prevede due funzioni, una per la verifica e una per la creazione.
CodesInChaos,

L'hash delle password si è appena evoluto negli ultimi 15 anni. Bcrypt è stato pubblicato nel 1999 ed è ancora uno degli hash consigliati. Da allora è accaduto solo un miglioramento significativo: sequenzialmente hash rigidi di memoria, introdotti da Scrypt.
CodesInChaos,

Ciò rende vulnerabili i vecchi hash (ad es. Se qualcuno ruba il DB). L'hash di ogni vecchio hash con il nuovo metodo più sicuro lo mitiga in una certa misura (avrai ancora bisogno del tipo di hash per distinguere tra newHash(oldHash, salt)onewHash(password, salt)
dbkk

42

Se ti preoccupi abbastanza di distribuire il nuovo schema di hashing a tutti gli utenti il ​​più rapidamente possibile (ad es. Perché quello vecchio è davvero insicuro), c'è effettivamente un modo per la "migrazione" istantanea di ogni password.

L'idea è fondamentalmente di hash l'hash . Anziché attendere che gli utenti forniscano la password esistente ( p) al successivo accesso, si utilizza immediatamente il nuovo algoritmo di hashing ( H2) sull'hash esistente già prodotto dal vecchio algoritmo ( H1):

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

Dopo aver effettuato quella conversione, sei ancora perfettamente in grado di eseguire la verifica della password; devi solo calcolare al H2(H1(p'))posto del precedente H1(p')ogni volta che l'utente tenta di accedere con password p'.

In teoria, questa tecnica potrebbe essere applicata a più migrazioni ( H3, H4, ecc). In pratica, vorrai eliminare la vecchia funzione hash, sia per motivi di prestazioni che di leggibilità. Fortunatamente, questo è abbastanza semplice: al prossimo accesso riuscito, calcola semplicemente il nuovo hash della password dell'utente e sostituisci l'hash-of-a-hash esistente con esso:

hash = H2(p)

Avrai anche bisogno di una colonna aggiuntiva per ricordare quale hash stai memorizzando: quello della password o del vecchio hash. Nel malaugurato caso di perdita del database, questa colonna non dovrebbe facilitare il lavoro del cracker; indipendentemente dal suo valore, l'attaccante dovrebbe comunque invertire l' H2algoritmo sicuro anziché il vecchio H1.


3
Un gigante +1: H2 (H1 (p)) di solito è sicuro come usare direttamente H2 (p), anche se H1 è terribile, perché (1) il sale e lo stretching sono curati da H2, e (2) i problemi con hash "rotti" come MD5 non influiscono sull'hash della password. Correlati: crypto.stackexchange.com/questions/2945/…
orip,

Interessante, avrei immaginato che l'hash del vecchio hash avrebbe creato una sorta di "problema" di sicurezza. Sembra che mi sia sbagliato.
Willem,

4
se H1 è davvero terribile, questo non sarà sicuro. Essere terribili in questo contesto significa soprattutto perdere molta entropia dall'input. Anche MD4 e MD5 sono quasi perfetti a questo proposito, quindi questo approccio è insicuro solo per alcuni hash o hash homebrew con output davvero breve (inferiore a 80 bit).
CodesInChaos,

1
In questo caso, probabilmente, il vostro unico è l'opzione ammettono che si avvitato duro e basta andare avanti e reimpostazione della password per tutti gli utenti. Si tratta meno della migrazione a quel punto e di più sul controllo dei danni.
Xion,

8

La tua soluzione (colonna aggiuntiva nel database) è perfettamente accettabile. L'unico problema con esso è quello che hai già menzionato: che il vecchio hashing è ancora usato per gli utenti che non si sono autenticati dalla modifica.

Per evitare questa situazione, puoi attendere che gli utenti più attivi passino al nuovo algoritmo di hashing, quindi:

  1. Rimuovi gli account degli utenti che non eseguono l'autenticazione da troppo tempo. Non c'è nulla di sbagliato nel rimuovere un account di un utente che non è mai arrivato al sito Web per tre anni.

  2. Invia un'e-mail agli utenti rimanenti tra quelli che non si sono autenticati per un po ', dicendo che potrebbero essere interessati a qualcosa di nuovo. Aiuterà a ridurre ulteriormente il numero di account che utilizzano l'hash più vecchio.

    Fai attenzione: se hai un'opzione senza spam gli utenti possono controllare sul tuo sito Web, non inviare l'e-mail agli utenti che lo hanno controllato.

  3. Infine, poche settimane dopo il passaggio 2, distruggi la password per gli utenti che stanno ancora utilizzando la vecchia tecnica. Quando e se tentano di autenticarsi, vedranno che la loro password non è valida; se sono ancora interessati al tuo servizio, potrebbero ripristinarlo.


1

Probabilmente non avrai mai più di un paio di tipi di hash, quindi puoi risolverlo senza estendere il tuo database.

Se i metodi hanno lunghezze di hash diverse e la lunghezza di hash di ogni metodo è costante (diciamo, passando da md5 a hmac-sha1), puoi dire il metodo dalla lunghezza dell'hash. Se hanno la stessa lunghezza, puoi calcolare l'hash usando prima il nuovo metodo, quindi il vecchio metodo se il primo test falliva. Se hai un campo (non mostrato) che dice quando l'utente è stato aggiornato / creato l'ultima volta, puoi usarlo come indicatore per quale metodo usare.


1

Ho fatto qualcosa del genere e c'è un modo per capire se è il nuovo hash e renderlo ancora più sicuro. Immagino che più sicuro sia una buona cosa per te se stai prendendo il tempo di aggiornare il tuo hash ;-)

Aggiungi una nuova colonna alla tua tabella DB chiamata "salt" e, bene, usala come salt generata casualmente per utente. (praticamente impedisce gli attacchi da tavolo arcobaleno)

In questo modo, se la mia password è "pass123" e salt casuale è 3333 la mia nuova password sarebbe l'hash di "pass1233333".

Se l'utente ha un sale sai che è il nuovo hash. Se il sale dell'utente è nullo, sai che è il vecchio hash.


0

Non importa quanto sia valida la tua strategia di migrazione, se il database è già stato rubato (e non puoi provare negativamente che ciò non sia mai accaduto), le password archiviate con funzioni hash non sicure potrebbero essere già compromesse. L'unica soluzione per questo è richiedere agli utenti di modificare le password.

Questo non è un problema che può essere risolto (solo) scrivendo codice. La soluzione è informare gli utenti e i clienti sui rischi a cui sono stati esposti. Una volta che sei disposto a essere aperto a riguardo, puoi eseguire la migrazione semplicemente reimpostando la password di ogni utente, poiché è la soluzione più semplice e sicura. Tutte le alternative riguardano davvero nascondere il fatto che hai rovinato tutto.


1
Un compromesso che ho fatto in passato è quello di conservare le vecchie password per un certo periodo di tempo, rimettendole a nuovo mentre le persone accedono, ma dopo che è trascorso quel tempo, si forza quindi un ripristino della password su chiunque non abbia effettuato l'accesso durante quella volta. Quindi hai un periodo in cui hai ancora le password meno sicure, ma è limitato nel tempo e minimizzi anche le interruzioni per gli utenti che accedono regolarmente.
Sean Burton,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.