Crittografia / hashing delle password in testo semplice nel database

Ho ereditato un’app Web che ho appena scoperto memorizza oltre 300.000 nomi utente / password in testo semplice in un database SQL Server. Mi rendo conto che questa è una cosa molto ctriggers ™.

Sapendo che dovrò aggiornare i processi di login e di aggiornamento della password per crittografare / decifrare e con il minimo impatto sul resto del sistema, cosa consiglieresti come il modo migliore per rimuovere le password in testo semplice dal database?

Qualsiasi aiuto è apprezzato.

Modifica: Scusa se non sono chiaro, volevo chiedere quale sarebbe la procedura per crittografare / hash le password, non specifici metodi di crittografia / hashing.

Dovrei solo:

  1. Effettua un backup del DB
  2. Aggiorna login / aggiornamento codice password
  3. Dopo ore, passa attraverso tutti i record nella tabella degli utenti con hashing della password e sostituendo ciascuno di essi
  4. Verificare che gli utenti possano ancora accedere / aggiornare le password

Immagino che la mia preoccupazione sia maggiore dal numero di utenti, quindi voglio assicurarmi di farlo correttamente.

Immagino che dovrai aggiungere una colonna al database per la password crittografata quindi eseguire un lavoro batch su tutti i record che ottiene la password corrente, la crittografa (come altri hanno menzionato un hash come md5 è una modifica abbastanza standard : ma non dovrebbe essere usato da solo – vedi altre risposte per buone discussioni ), lo memorizza nella nuova colonna e controlla che tutto sia andato per il verso giusto .

Quindi dovrai aggiornare il tuo front-end per cancellare la password immessa dall’utente al momento dell’accesso e verificarlo rispetto all’hash memorizzato, piuttosto che verificare il testo normale-vs-in chiaro.

Mi sembrerebbe prudente lasciare entrambe le colonne sul posto per un po ‘per assicurarsi che non succedesse niente di strano, prima di rimuovere finalmente le password in chiaro insieme.

Non dimenticare inoltre che ogni volta che la password viene inserita, il codice dovrà essere modificato, ad esempio le richieste di modifica / promemoria della password. Ovviamente perderai la possibilità di inviare via e-mail password dimenticate, ma questa non è una cosa negativa. Dovrai invece utilizzare un sistema di reimpostazione della password.

Modifica: un ultimo punto, potresti prendere in considerazione di evitare l’errore che ho fatto al mio primo tentativo in un sito Web di accesso sicuro al banco di prova:

Quando si elabora la password dell’utente, considerare dove avviene l’hashing. Nel mio caso l’hash è stato calcolato dal codice PHP in esecuzione sul server web, ma la password è stata trasmessa alla pagina dalla macchina dell’utente in testo in chiaro! Questo era ok (ish) nell’ambiente in cui stavo lavorando, come invece era all’interno di un sistema https (rete uni). Ma, nel mondo reale, immagino che vorresti hash la password prima che lasci il sistema utente, usando javascript ecc. E poi trasmetti l’hash al tuo sito.

EDIT (2016): utilizzare Argon2 , scrypt , bcrypt o PBKDF2 , in questo ordine di preferenza. Usa un fattore di rallentamento grande come è fattibile per la tua situazione. Utilizzare un’implementazione esistente controllata. Assicurati di utilizzare un sale adeguato (anche se le librerie che stai usando dovrebbero essere sicure di questo per te).


Quando usi l’hash delle password NON USARE PLAIN MD5 .

Usa PBKDF2 , che in pratica significa usare un salt casuale per prevenire gli attacchi di rainbow table , e iterare (reinghing) abbastanza volte per rallentare l’hashing – non tanto che l’applicazione impiega troppo tempo, ma abbastanza che un attacker bruta – forzando un noterà un gran numero di password diverse

Dal documento:

  • Scorrere almeno 1000 volte, preferibilmente più tempo per la tua implementazione per vedere quante iterazioni sono fattibili per te.
  • Sono sufficienti 8 byte (64 bit) di sale e il random non deve essere sicuro (il sale non è criptato, non siamo preoccupati che qualcuno lo indovini).
  • Un buon modo per applicare il sale quando hashing è usare HMAC con il tuo algoritmo hash preferito, usando la password come chiave HMAC e il salt come testo per l’hash (vedi questa sezione del documento).

Esempio di implementazione in Python, usando SHA-256 come hash sicuro:

EDIT : come citato da Eli Collins questa non è un’implementazione PBKDF2. Dovresti preferire implementazioni conformi allo standard, come PassLib .

from hashlib import sha256 from hmac import HMAC import random def random_bytes(num_bytes): return "".join(chr(random.randrange(256)) for i in xrange(num_bytes)) def pbkdf_sha256(password, salt, iterations): result = password for i in xrange(iterations): result = HMAC(result, salt, sha256).digest() # use HMAC to apply the salt return result NUM_ITERATIONS = 5000 def hash_password(plain_password): salt = random_bytes(8) # 64 bits hashed_password = pbkdf_sha256(plain_password, salt, NUM_ITERATIONS) # return the salt and hashed password, encoded in base64 and split with "," return salt.encode("base64").strip() + "," + hashed_password.encode("base64").strip() def check_password(saved_password_entry, plain_password): salt, hashed_password = saved_password_entry.split(",") salt = salt.decode("base64") hashed_password = hashed_password.decode("base64") return hashed_password == pbkdf_sha256(plain_password, salt, NUM_ITERATIONS) password_entry = hash_password("mysecret") print password_entry # will print, for example: 8Y1ZO8Y1pi4=,r7Acg5iRiZ/x4QwFLhPMjASESxesoIcdJRSDkqWYfaA= check_password(password_entry, "mysecret") # returns True 

La strategia di base è quella di utilizzare una funzione di derivazione della chiave per “hash” la password con un po ‘di sale. Il sale e il risultato dell’hash sono memorizzati nel database. Quando un utente inserisce una password, il sale e il loro input vengono sottoposti a hash nello stesso modo e confrontati con il valore memorizzato. Se corrispondono, l’utente è autenticato.

Il diavolo è nei dettagli. Innanzitutto, molto dipende dall’algoritmo di hash scelto. Un algoritmo di derivazione chiave come PBKDF2, basato su un codice di autenticazione dei messaggi basato su hash, rende “computazionalmente imansible” trovare un input (in questo caso una password) che produrrà un determinato output (ciò che un utente malintenzionato ha trovato nel database ).

Un attacco dizionario pre-calcolato utilizza l’indice o il dizionario precompilato, dagli output hash alle password. L’hashing è lento (o dovrebbe essere, comunque), quindi l’hacker ha cancellato tutte le password possibili una volta e memorizza il risultato indicizzato in modo tale che, dato un hash, può cercare una password corrispondente. Questo è un classico compromesso di spazio per il tempo. Poiché gli elenchi di password possono essere enormi, ci sono modi per ottimizzare il compromesso (come le tabelle arcobaleno), in modo che un utente malintenzionato possa rinunciare a una piccola velocità per risparmiare molto spazio.

Gli attacchi di pre-computazione vengono contrastati usando “sale crittografico”. Questo è un dato che è stato cancellato con la password. Non ha bisogno di essere un segreto, deve solo essere imprevedibile per una determinata password. Per ogni valore di sale, un utente malintenzionato avrebbe bisogno di un nuovo dizionario. Se si utilizza un byte di sale, un utente malintenzionato ha bisogno di 256 copie del proprio dizionario, ciascuna generata con un sale diverso. Innanzitutto, usava il sale per cercare il dizionario corretto, quindi usava l’output dell’hash per cercare una password utilizzabile. Ma cosa succede se aggiungi 4 byte? Ora ha bisogno di 4 miliardi di copie del dizionario. Usando un sale abbastanza grande, un attacco dizionario è precluso. In pratica, da 8 a 16 byte di dati da un generatore di numeri casuali di qualità crittografica fanno un buon sale.

Con il precalcolo fuori dal tavolo, un attaccante ha calcolato l’hash su ogni tentativo. Quanto tempo ci vuole per trovare una password ora dipende interamente da quanto tempo ci vuole per un candidato. Questo tempo è aumentato dall’iterazione della funzione di hash. Il numero di iterazioni è generalmente un parametro della funzione di derivazione della chiave; oggi, molti dispositivi mobili utilizzano da 10.000 a 20.000 iterazioni, mentre un server può utilizzare 100.000 o più. (L’algoritmo di bcrypt utilizza il termine “fattore costo”, che è una misura logaritmica del tempo richiesto).

Segui il consiglio di Xan di mantenere la colonna della password corrente per un po ‘di tempo, quindi se le cose vanno male, puoi eseguire il rollback rapido-facile.

Per quanto riguarda la crittografia delle password:

  • usa un sale
  • usa un algoritmo hash pensato per le password (es., – è lento )

Vedi Tabelle di Thomas Ptacek Enough With The Rainbow: Che cosa è necessario sapere sugli schemi di password sicura per alcuni dettagli.

Penso che dovresti fare quanto segue:

  1. Crea una nuova colonna chiamata HASHED_PASSWORD o qualcosa di simile.
  2. Modifica il tuo codice in modo che controlli entrambe le colonne.
  3. Migra gradualmente le password dalla tabella non hash a quella hash. Ad esempio, quando un utente effettua l’accesso, trasferisce automaticamente la sua password nella colonna con hash e rimuove la versione non modificata. Tutti gli utenti appena registrati avranno password hash.
  4. Dopo ore, puoi eseguire uno script che migra una volta gli utenti
  5. Se non hai più password passate, puoi rimuovere la colonna della vecchia password (potresti non essere in grado di farlo, dipende dal database che stai utilizzando). Inoltre, è ansible rimuovere il codice per gestire le vecchie password.
  6. Hai finito!

È stato un problema mio due settimane fa. Stavamo implementando un grande progetto MIS in 975 località geografiche diverse in cui il nostro negozio di credenziali utente verrà utilizzato come autenticatore per diverse serie di applicazioni già implementate e in uso. Abbiamo già fornito sia il servizio di autenticazione basato su REST e SOAP, ma il cliente ha insistito per essere in grado di raggiungere l’archivio di credenziali utente da altre applicazioni con una sola connessione DB alla vista di sola lettura della tabella o vista correlata. Sigh … (questa pessima decisione sul design è un argomento di un’altra domanda).

Questo ci ha costretto a sederci e convertire il nostro schema di archiviazione delle password salate ed iterativamente con password in una specifica e fornire alcune implementazioni linguistiche diverse per una facile integrazione.

Lo abbiamo chiamato in parole povere password Hashed o FSHP in breve. Lo ha implementato in Python, Ruby, PHP5 e lo ha rilasciato a Public Domain. Disponibile per essere consumato, biforcato, fiammato o sputato su GitHub all’indirizzo http://github.com/bdd/fshp

FSHP è un’implementazione hashing salata e iterativa della password.

Il principio di progettazione è simile alla specifica PBKDF1 in RFC 2898 (pseudonimo: PKCS # 5: Cryptography Specification Version 2.0.) FSHP consente di scegliere la lunghezza del sale, il numero di iterazioni e la funzione hash crittografica sottostante tra SHA-1 e SHA-2 (256, 384, 512). Il meta prefisso autodefinente all’inizio di ogni output lo rende portatile consentendo al consumatore di scegliere la propria baseline di sicurezza per l’archiviazione delle password.

SICUREZZA :

L’FSHP1 predefinito usa 8 byte di sali, con 4096 iterazioni di hashing SHA-256. – Il sale a 8 byte rende poco pratico l’attacco alla tavola arcobaleno moltiplicando lo spazio richiesto con 2 ^ 64. – 4096 iterazioni causano attacchi di forza bruta abbastanza costosi. – Non sono noti attacchi contro SHA-256 per trovare collisioni con uno sforzo computazionale di meno di 2 ^ 128 operazioni al momento di questa versione.

IMPLEMENTAZIONI:

  • Python: testato con 2.3.5 (con hashlib), 2.5.1, 2.6.1
  • Ruby: testato con 1.8.6
  • PHP5: testato con 5.2.6

Tutti sono più che benvenuti a creare implementazioni linguistiche mancanti o a lucidare quelli attuali.

OPERAZIONE DI BASE (con Python) :

 >>> fsh = fshp.crypt('OrpheanBeholderScryDoubt') >>> print fsh {FSHP1|8|4096}GVSUFDAjdh0vBosn1GUhzGLHP7BmkbCZVH/3TQqGIjADXpc+6NCg3g== >>> fshp.validate('OrpheanBeholderScryDoubt', fsh) True 

PERSONALIZZANDO LA CRIPTA:

Facciamo indebolire il nostro schema di hashing delle password. – Diminuisci la lunghezza del sale dall’impostazione predefinita 8 a 2. – Diminuisci il round di iterazione da predefinito 4096 a 10. – Seleziona FSHP0 con SHA-1 come algoritmo di hash sottostante.

 >>> fsh = fshp.crypt('ExecuteOrder66', saltlen=2, rounds=10, variant=0) >>> print fsh {FSHP0|2|10}Nge7yRT/vueEGVFPIxcDjiaHQGFQaQ== 

Come gli altri hanno menzionato, non vuoi decifrare se puoi aiutarlo. La best practice standard è quella di crittografare utilizzando un hash unidirezionale, e quindi quando l’utente accede all’hash della propria password per confrontarlo.

Altrimenti dovrai usare una crittografia forte per crittografare e quindi decifrare. Lo consiglierei solo se i motivi politici sono forti (ad esempio, i tuoi utenti sono abituati a poter chiamare l’help desk per recuperare la loro password, e tu hai una forte pressione dall’alto per non cambiarlo). In tal caso, inizierei con la crittografia e quindi inizierei a creare un business case per passare all’hashing.

Ai fini dell’autenticazione, evitare di memorizzare le password utilizzando la crittografia reversibile, ovvero è necessario memorizzare l’hash della password e controllare l’hash della password fornita dall’utente contro l’hash memorizzato. Tuttavia, questo approccio ha uno svantaggio: è vulnerabile agli attacchi delle tabelle arcobaleno , nel caso in cui un utente malintenzionato si impadronisca del database dell’archivio delle password.

Quello che dovresti fare è memorizzare gli hash di un valore di sale preselezionato (e segreto) + la password. Cioè, concatenate il sale e la password, cancellate il risultato e memorizzate questo hash. Quando esegui l’autenticazione, fai lo stesso: concatena il tuo valore di sale e la password fornita dall’utente, hash, quindi controlla l’uguaglianza. Questo rende gli attacchi da tavolo arcobaleno non fattibili.

Ovviamente, se l’utente invia password attraverso la rete (ad esempio, se si sta lavorando su un’applicazione web o client-server), non si deve inviare la password in chiaro, quindi invece di memorizzare hash (sale + password) è necessario archiviare e verificare con hash (salt + hash (password)) e chiedere al client di pre-hash la password fornita dall’utente e inviarla tramite la rete. Questo protegge anche la password dell’utente, nel caso in cui l’utente (come molti) riutilizzi la stessa password per più scopi.

  • Crittografa usando qualcosa come MD5, codificalo come una stringa esadecimale
  • Hai bisogno di un sale; nel tuo caso, il nome utente può essere usato come il sale (deve essere unico, il nome utente dovrebbe essere il valore più unico disponibile 😉
  • usa il vecchio campo password per memorizzare l’MD5, ma tagga l’MD5 (ieg “MD5: 687A878 ….”) in modo che le vecchie password (testo normale) e nuove (MD5) possano coesistere
  • cambiare la procedura di login per verificare contro MD5 se c’è un MD5, e contro la semplice password altrimenti
  • cambia le funzioni “cambia password” e “nuovo utente” per creare solo password MD5
  • ora è ansible eseguire il processo di conversione batch, che potrebbe richiedere il tempo necessario
  • dopo che la conversione è stata eseguita, rimuovere il supporto legacy

Passaggio 1: aggiungere il campo crittografato al database

Passaggio 2: modificare il codice in modo che quando la password viene modificata, aggiorna entrambi i campi, ma l’accesso utilizza ancora il vecchio campo.

Passaggio 3: eseguire lo script per popolare tutti i nuovi campi.

Passaggio 4: modificare il codice in modo che l’accesso utilizzi un nuovo campo e la modifica delle password interrompa l’aggiornamento del vecchio campo.

Passaggio 5: rimuovere le password non crittografate dal database.

Questo dovrebbe consentire di eseguire il passaggio senza interruzioni all’utente finale.

Inoltre: Qualcosa che vorrei fare è denominare il nuovo campo del database qualcosa che non è completamente correlato alla password come “LastSessionID” o qualcosa di altrettanto noioso. Quindi, invece di rimuovere il campo della password, basta compilare con hash di dati casuali. Quindi, se il tuo database viene mai compromesso, possono passare tutto il tempo che vogliono cercando di decifrare il campo “password”.

Questo potrebbe non realizzare nulla, ma è divertente pensare a qualcuno seduto lì che cerca di scoprire informazioni inutili

Come con tutte le decisioni di sicurezza, ci sono dei compromessi. Se si hash la password, che è probabilmente la mossa più semplice, non è ansible offrire una funzione di recupero della password che restituisce la password originale, né il personale può cercare la password di una persona per accedere al proprio account.

È ansible utilizzare la crittografia simmetrica, che ha i propri limiti di sicurezza. (Se il tuo server è compromesso, anche la chiave di crittografia simmetrica potrebbe essere compromise).

È ansible utilizzare la crittografia a chiave pubblica ed eseguire il recupero della password / il servizio clienti su una macchina separata che memorizza la chiave privata isolata dall’applicazione web. Questo è il più sicuro, ma richiede un’architettura a due macchine e probabilmente un firewall in mezzo.

Non sono un esperto di sicurezza, ma la raccomandazione attuale è di usare bcrypt / blowfish o una variante SHA-2, non MD5 / SHA1.

Probabilmente hai bisogno di pensare anche in termini di un controllo di sicurezza completo

MD5 e SHA1 hanno mostrato un po ‘di debolezza (due parole possono portare allo stesso hash), quindi si consiglia di usare SHA256-SHA512 / hash iterativo per cancellare la password.

Scriverò un piccolo programma nella lingua in cui è scritta l’applicazione che va e genera un salt casuale che è unico per ogni utente e un hash della password. La ragione per cui tendo ad usare la stessa lingua della verifica è che le diverse librerie crittografiche possono fare le cose in modo leggermente diverso (es. Padding), quindi usare la stessa libreria per generare l’hash e verificare che elimini tale rischio. Questa applicazione potrebbe inoltre verificare l’accesso dopo che la tabella è stata aggiornata, se lo si desidera in quanto conosce ancora la password in testo semplice.

  1. Non utilizzare MD5 / SHA1
  2. Generare un buon sale casuale (molte librerie crittografiche hanno un generatore di sale)
  3. Un algoritmo di hash iterativo raccomandato da orip
  4. Assicurarsi che le password non vengano trasmesse in chiaro sul cavo

Vorrei suggerire un miglioramento al grande esempio python pubblicato da Orip . Vorrei ridefinire la funzione random_bytes per essere:

 def random_bytes(num_bytes): return os.urandom(num_bytes) 

Ovviamente, dovresti importare il modulo os . La funzione os.urandom fornisce una sequenza casuale di byte che può essere utilizzata in modo sicuro in applicazioni crittografiche. Vedere l’aiuto di riferimento di questa funzione per ulteriori dettagli.

Per cancellare la password è ansible utilizzare la funzione HashBytes . Restituisce un varbinary, quindi dovresti creare una nuova colonna e quindi eliminare quella vecchia varchar.

Piace

 ALTER TABLE users ADD COLUMN hashedPassword varbinary(max); ALTER TABLE users ADD COLUMN salt char(10); --Generate random salts and update the column, after that UPDATE users SET hashedPassword = HashBytes('SHA1',salt + '|' + password); 

Quindi si modifica il codice per convalidare la password, utilizzando una query come

 SELECT count(*) from users WHERE hashedPassword = HashBytes('SHA1',salt + '|' + ) 

dove è il valore inserito dall’utente.

li hash con md5. questo è ciò che viene solitamente fatto con le password.