HMACSHA512 contro Rfc2898DeriveBytes per hash della password

11

Attualmente stiamo utilizzando HMACSHA512 in .net, con una chiave di convalida 128Char (64byte) Il sale è 64 caratteri generati casualmente stringa. Abbiamo assegnato 2048 lunghezza al database per il risultato della stringa base64 hash. Sarà un sito Web pubblico. Questo approccio è ragionevole o dovrebbe essere modificato in un altro approccio come Rfc2898DeriveBytes?

 public string HashEncode(string password, string salt, string validationKey) {
        byte[] hashKey = BosUtilites.HexStringToByteArray(validationKey);
        var sha512 = new HMACSHA512(hashKey);
        var hashInput = BosUtilites.StringToByteArray(password + salt);
        byte[] hash = sha512.ComputeHash(hashInput);
        return Convert.ToBase64String(hash);
    }

 public string GenerateSimpleSalt(int Size = 64) {
        var alphaSet = new char[64]; // use 62 for strict alpha... that random generator for alphas only
        //nicer results with set length * int i = 256. But still produces excellent random results.
        //alphaset plus 2.  Reduce to 62 if alpha requried
        alphaSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890#=".ToCharArray();
        var tempSB = GenerateRandomString(Size, alphaSet);
        return tempSB.ToString();
    }

    public StringBuilder GenerateRandomString(int Size, char[] alphaSet) {
        using (var crypto = new RNGCryptoServiceProvider()) {
            var bytes = new byte[Size];
            crypto.GetBytes(bytes); //get a bucket of very random bytes
            var tempSB = new StringBuilder(Size);
            foreach (var b in bytes) { // use b , a random from 0-255 as the index to our source array. Just mod on length set
                tempSB.Append(alphaSet[b%(alphaSet.Length)]);
            }

            return tempSB;
        }

EDIT2: nel caso in cui qualcuno lo trovi tramite google, ho incluso le lezioni apprese Il campione medio nei test sulle workstation era di circa 300 msec. Questo non dovrebbe essere troppo evidente durante l'accesso. E non c'è più bisogno di una chiave di convalida. Che è un sollievo: -)

 SCrypt package installed via nuget. and rfc2898 PBKDF2 changed to be large number or iterations but only 20bytes output.  SAme CPU time.

Le nuove password sono codificate in SCRYPT per impostazione predefinita,

  <package id="CryptSharpOfficial" version="2.0.0.0" targetFramework="net451" />
  // save salt, hash algorithm used and resulting encoding on user record
  public string PasswordEncode(string password, byte[] salt, HashAlgorithm hashAlgorithm ) {
        switch (hashAlgorithm) {
            case HashAlgorithm.PBKDF2:
                    var deriver2898 = new Rfc2898DeriveBytes(password, salt,<Use a number around 50K>); // approx 300msecs on workstation
                    byte[] hash = deriver2898.GetBytes(20); // 
                    return Convert.ToBase64String(hash);
            case HashAlgorithm.Scrypt:
                var key = Encoding.UTF8.GetBytes(password);
                byte[] hashScrypt =  SCrypt.ComputeDerivedKey(key: key, salt: salt, 
                                    cost: 65536, // must be a power of 2 !, on PC, singlethread this is approx 1 sec
                                    blockSize: 8, 
                                    parallel: 1,
                                    maxThreads: 1, 
                                    derivedKeyLength: 128);

                    return Convert.ToBase64String(hashScrypt);
            default:
                throw new ArgumentOutOfRangeException("hashAlgorithm");
        }
    }
    
posta soadyp 02.05.2013 - 18:53
fonte

4 risposte

14

Rfc2898DeriveBytes implementa PBKDF2 : una funzione che trasforma una password (con un salt) in una sequenza di lunghezza arbitraria di byte. PBKDF2 viene spesso utilizzato per l'hashing della password (cioè per calcolare e memorizzare un valore che è sufficiente per verificare una password) perché ha le caratteristiche necessarie per le funzioni di hashing della password: a salt e lentezza configurabile .

Queste caratteristiche sono necessarie perché le password sono deboli : si adattano al cervello umano. In quanto tali, sono vulnerabili alla ricerca esauriente: è possibile, in linea generale, enumerare la maggior parte delle password che gli utenti umani troveranno e ricorderanno. L'attacco presuppone che l'utente malintenzionato abbia ricevuto una copia della password salt e hash e quindi "prova le password" sulla propria macchina. Si chiama attacco per dizionario offline .

Nel tuo caso, hai un terzo elemento: una chiave di convalida . È un tasto , apparentemente segreto. Se l'utente malintenzionato potrebbe afferrare le password salate e hash ma non la chiave di convalida, non potrà eseguire l'attacco del dizionario sulle proprie macchine; in queste condizioni (la chiave di convalida rimane segreta e l'algoritmo di validazione è robusto - HMAC / SHA-512 va bene per quello), la lentezza configurabile di PBKDF2 non è necessaria. Questo tipo di convalida con una chiave segreta viene talvolta chiamato "peppering".

Si noti, tuttavia, che quando assumiamo che l'attaccante possa prendere una copia delle password con hash, allora diventa delicata supporre che la chiave sia rimasta intatta dalle sue occhiatacce. Questo dipende dal contesto. La maggior parte degli attacchi SQL injection sarà in grado di leggere parte di tutto il database, ma non il resto dei file sulla macchina. Tuttavia, il tuo server deve in qualche modo essere in grado di avviarsi e avviarsi senza intervento umano, quindi la chiave di convalida è da qualche parte sul disco. Un utente malintenzionato che ruba l'intero disco (o un nastro di backup ...) riceverà anche la chiave di convalida - a quel punto si ritorna alla necessità di una lentezza configurabile.

In generale, raccomanderei PBKDF2 (alias Rfc2898DeriveBytes in .NET) su una costruzione personalizzata, anche se devo dire che sembra utilizzare HMAC correttamente (le costruzioni fatte in casa raramente raggiungono quel livello di correttezza). Se insisti di avere una "chiave di convalida" (e sei pronto ad assumere il sovraccarico procedurale della gestione delle chiavi, ad esempio backup speciali per quella chiave), allora ti suggerisco di usare PBKDF2 e allora applicare HMAC sul Uscita PBKDF2.

Vedi questa risposta per una discussione dettagliata sulla password hashing.

    
risposta data 02.05.2013 - 19:26
fonte
9

Sto rispondendo in modo specifico all'EDIT delle lezioni apprese nella domanda originale.

Rfc2898DeriveBytes is called with 1000 iterations. using 1024 byte output. The password size in Db was designed 2k fortunately. Average sample in tests on workstations was around 300 msecs

Riepilogo rapido: se ti piace il carico corrente della CPU e Rfc2898DeriveBytes, cambia da 1000 iterazioni e un output di 1024 byte a 52000 iterazioni e 20 byte di output (20 byte è l'output nativo di SHA-1, che è cosa. NET 4.5 Rfc2898DeriveBytes è basato su).

La spiegazione del perché, inclusi i riferimenti:

Per evitare di dare agli aggressori un vantaggio su di te, NON utilizzare un PBKDF2 / RFC2898 / PKCS # 5 (o un HMAC, che è usato internamente in PBKDF2 e altri) con una dimensione di uscita maggiore dell'output nativo dell'hash funzione usata Dal momento che l'implementazione di .NET (a partire da 4.5) hardcodes SHA-1, dovresti usare un massimo di 160 bit di output, non 8192 bit di output!

La ragione di ciò è che come ci riferiamo alla specifica RFC2898 , se la dimensione dell'output (dkLen, es. Lunghezza chiave derivata) è maggiore della dimensione di output hash nativo (hLen, cioè Lunghezza hash). A pagina 9, vediamo

Passaggio 2: "Sia il numero di blocchi hLen-ottetto nella chiave derivata,          arrotondando per eccesso "

Passaggio 3: " T_1 = F (P, S, c, 1), T_2 = F (P, S, c, 2), ... T_l = F (P, S, c, l), "

E a pagina 10: Passaggio 4: "DK = T_1 || T_2 || ... || T_l < 0..r-1 >" dove DK è la chiave derivata (uscita PBKDF2) e || è l'operatore di concatenazione.

Pertanto, possiamo vedere che per la vostra chiave a 8192 bit e HMAC-SHA-1 di .NET, abbiamo 8192/160 = 51.2 blocchi e CEIL (51.2) = 52 blocchi richiesti per PBKDF2, cioè da T_1 a T_52 (l = 52). I riferimenti alle specifiche di ciò che accade con .2 in modo diverso da un blocco completo non rientrano nell'ambito di questa discussione (suggerimento: troncamento dopo il calcolo del risultato completo).

Quindi, esegui un set di 1000 iterazioni sulla tua password per un totale di 52 volte e concatenando l'output. Quindi, per una password, stai effettivamente eseguendo iterazioni 52000!

Un attaccante intelligente eseguirà solo 1000 iterazioni e confronterà il loro risultato a 160 bit con i primi 160 bit dei 8192 bit di output - se fallisce, è un'ipotesi sbagliata, vai avanti. Se riesce, è quasi certamente una supposizione di successo (attacco).

Quindi, stai eseguendo 52.000 iterazioni su una CPU, e un utente malintenzionato sta eseguendo 1.000 iterazioni su qualsiasi cosa abbiano (probabilmente alcune GPU, ognuna delle quali supera in modo massivo la CPU per SHA-1 in primo luogo); hai dato agli attaccanti un vantaggio 52: 1 oltre ai vantaggi dell'hardware.

Per fortuna, una buona funzione PBKDF2 è facile da regolare; cambia semplicemente la tua lunghezza di output a 160 bit e il tuo numero di iterazioni a 52.000, e userai la stessa quantità di tempo CPU, memorizzerai chiavi più piccole e renderà 52 volte più costoso per qualsiasi utente malintenzionato senza costi di runtime! / p>

Se vuoi ulteriori attacchi di hacker con GPU, potresti voler passare a PBKDF2-HMAC-SHA-512 (o scrypt o bcrypt) e una dimensione di output di 64 byte o meno (la dimensione di output nativo di SHA- 512), che riduce significativamente la quantità di GPU attuali (all'inizio del 2014) di vantaggio rispetto alle CPU a causa delle istruzioni a 64 bit che si trovano nella CPU ma non nelle GPU. Tuttavia, questo non è nativamente disponibile in .NET 4.5.

  • Tuttavia, @Jither ha creato un bell'esempio di aggiunta di funzionalità PBKDF2-HMAC-SHA256, PBKDF2-HMAC-SHA384, PBKDF2-HMAC-SHA512, ecc. a .NET e ho incluso una variante con un insieme ragionevole di prova i vettori in mio repository Github come riferimento.

Per un altro riferimento relativo a un difetto di progettazione reale in 1Password, vedi questo thread del forum di Hashcat - "Per ogni iterazione di PBKDF2-HMAC-SHA1 si chiama 4 volte la trasformazione SHA1, ma questo è solo per produrre una chiave a 160 bit. Per produrre la chiave a 320 bit richiesta, la si chiama 8 volte. "

P.S. se lo si desidera, per una piccola modifica del progetto è possibile risparmiare un po 'di spazio in più nel database se si memorizza l'output in una colonna VARBINARY o BINARY invece di eseguire la codifica Base64.

P.P.S. Ad esempio, modificare il codice di test nella modifica come segue (2000 * 52 = 104000); nota che il tuo testo diceva 1000 e il tuo test elencava 2000, quindi testo in testo e codice da codificare, quindi è ideale.

//var hash = libCrypto.HashEncode2(password, salt, 2000);
var hash = libCrypto.HashEncode2(password, salt, 104000);
// skip down into HashEncode2
//byte[] hash = deriver.GetBytes(1024);
byte[] hash = deriver.GetBytes(20);
    
risposta data 13.02.2014 - 05:46
fonte
4

La famiglia SHA2 non è una buona scelta per l'archiviazione delle password. È significativamente migliore di md5, ma dovresti davvero usare bcrypt (o scrypt!).

RNGCryptoServiceProvider è una buona fonte di entropia. Idealmente un sale non è base 64, ma base 256, come in un intero byte. Per capirlo meglio, devi sapere come sono generate le tavole arcobaleno. L'input per generare una tabella arcobaleno richiede uno spazio per le chiavi. Ad esempio, è possibile generare un arcobaleno per la ricerca: simbolo alfanumerico da 7-12 caratteri. Ora per decifrare questo schema di sale proposto l'attaccante dovrebbe generare un simbolo alfanumerico con 71-76 caratteri per compensare il sale di 64 caratteri (che è grande). Rendere il sale un byte completo, aumenterebbe notevolmente lo spazio delle chiavi che il tavolo arcobaleno avrebbe dovuto esaurire.

    
risposta data 02.05.2013 - 19:26
fonte
0

C'è, penso, un bug nella funzione RandomString della domanda (oltre a un paio di problemi di sintassi minori). Ha un bias ogni volta che il parametro Length non è un fattore di 256. Se Length era 62, come suggerisce il codice come opzione, 'a' e 'b' si sarebbero verificati più frequentemente di qualsiasi altro carattere (invece di un bel pari 1 / 62 distribuzione per tutti i caratteri, a e b si verificherebbero due volte più degli altri caratteri, 2/64, mentre i restanti 60 caratteri verrebbero visualizzati 1/64).

Perché c'è un pregiudizio, c'è una maggiore possibilità di generare sali identici. Non è molto probabile, ma è lì. Così com'è, però, se usi tutti i 64 caratteri va bene.

    
risposta data 22.01.2015 - 02:10
fonte

Leggi altre domande sui tag