Modo corretto per crittografare le password degli utenti

5

Ho uno scenario in cui le password immesse dall'utente devono essere memorizzate per un uso successivo. Non posso usare l'hashing perché ho bisogno di ottenere la password originale per un uso successivo.

Ad esempio, si consideri un'app di invio di posta elettronica in cui un utente immette inizialmente una password per il proprio account di posta elettronica e l'applicazione lo memorizzerà in forma crittografata e quando l'applicazione deve inviare un'email, decodifica la password e la utilizza per invia la posta.

Ora, ecco cosa sto facendo:

  1. L'applicazione memorizza una chiave principale (generata utilizzando RNGCryptoServiceProvider) nel database
  2. Sia la chiave che IV per AES derivano dalla chiave principale utilizzando Rfc2898DeriveBytes (password = masterkey e salt = user_id per Rfc2898DeriveBytes)

    // the initial, one time masterkey generation, which will be used for all passwords
    byte[] masterKeyBytes = new byte[32];
    new RNGCryptoServiceProvider().GetBytes(masterKeyBytes);
    // masterkey is saved in database
    string masterKey = Convert.ToBase64String(masterKeyBytes);
    
    // password encryption of a user's password
    var derivative = new Rfc2898DeriveBytes(masterKey, 128_bit_guid_of_user_id, numberOfIterations)
    var aes = new AesCryptoServiceProvider();
    aes.Key = derivative.GetBytes(32);
    aes.IV = derivative.GetBytes(16);
    var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
    // get the encrypted password from encryptor
    .....
    .....
    

Sto facendo questo nel modo giusto? Ci sono correzioni da fare? Inoltre, le dimensioni utilizzate (per dimensioni della chiave, masterkeysize, ecc.) Sono abbastanza buone?

Poche altre domande (solo per saperne di più):

  1. Le chiavi derivate (chiave AES separata per ciascun utente) da una chiave master ad alta entropia non sono considerate degne (o peggio)? (usando semplicemente la chiave principale come tasto AES è più che sufficiente). In tal caso, perché ?, la mia argomentazione è: se qualcuno dovesse dedurre la chiave AES per un particolare utente, non sarà ancora in grado di dedurre la chiave master, quindi gli altri utenti sono al sicuro. Mi sono imbattuto in "Diversificazione chiave" che viene utilizzata in alcune app di smart card in cui derivano una chiave AES separata da una chiave principale per ogni istanza (non vale la stessa cosa qui?)

  2. Possiamo usare PBKDF per ricavare le chiavi in questo scenario, o dovremmo usare un KBKDF? in tal caso, potresti citare una libreria (preferibilmente dal framework .NET) che offre KBKDF? So che RfcDerivativeBytes utilizza un PBKDF per derivare una chiave, ma non sono riuscito a scoprire come derivare una chiave utilizzando KBKDF.

posta userx 24.09.2015 - 13:08
fonte

1 risposta

9

Quando crittografate le password, è perché avete paura di origliare; immagina un aggressore che potrebbe dare un'occhiata al contenuto del tuo database. La crittografia è lo strumento giusto per farlo, ma se inserisci la chiave di crittografia nello stesso database, hai appena fatto l'equivalente di nascondere la chiave della porta sotto lo zerbino. Di solito un utente malintenzionato che può dare un'occhiata al database può anche ottenere la chiave in questo modo. Di solito, quando il contenuto del database perde, è attraverso un metodo che impatta il database completo (gli attacchi di iniezione SQL di maggior successo consentono l'estrazione arbitraria dei dati, lo stesso vale, naturalmente, per i backup di database persi / depredati). Pertanto, avrebbe più senso memorizzare la chiave master in un posto distinto dal database, per limitare il rischio di esposizione sia della chiave che della password crittografata.

Dato che apparentemente usi C # e .NET, deduco un sistema Windows e quindi potresti voler usare DPAPI , tramite ProtectedData class.

Derivare la chiave di crittografia effettiva e l'IV dalla stessa chiave master, combinata con un valore specifico dell'istanza, è una buona cosa, a condizione che tu usi un Key Derivation Function . Tuttavia, il valore extra "randomizzazione" che utilizzi è un identificatore specifico dell'utente; questo significa che se cambia i dati crittografati per un dato utente (ad esempio, l'utente cambia la sua password), allora sia la vecchia che la nuova password saranno crittografate con la stessa chiave e la stessa IV. Per la maggior parte dei sistemi di crittografia, il riutilizzo IV è un peccato.

Invece, dovresti usare un valore casuale che rigenererai ogni volta che crittografate una nuova porzione di dati e memorizzate insieme al valore crittografato. Ci possono essere diversi metodi per questo; il più semplice è utilizzare la "chiave master" come chiave di crittografia effettiva per AES e generare un IV casuale ogni volta che si desidera crittografare qualcosa; il risultato crittografato sarà quindi la concatenazione della IV e dei dati crittografati, in questo ordine.

Ricorda che l'IV non è pensato per essere un segreto; altrimenti, lo chiameremmo un tasto . Quindi non è un problema se è reso disponibile agli attaccanti. Tuttavia, ogni sistema di crittografia ha i propri requisiti relativi alla IV; in particolare, un codice a blocchi (come AES) in modalità CBC richiede che l'IV sia casualmente generato con una strong fonte casuale ( AesCryptoServiceProvider predefinito su CBC). Pertanto, genera l'IV con un'istanza RNGCryptoServiceProvider .

Parlando di KDF, usi Rfc2898DeriveBytes , che è eccessivo. Questa classe implementa PBKDF2 , che è un KDF basato sulla password . Le "password" sono speciali perché sono compatibili con il cervello umano e ciò implica una debolezza intrinseca. Gli attacchi di forza bruta funzionano contro le password (li chiamiamo "attacchi di dizionario"). Per migliorare la resistenza, i KDF basati su password vengono rallentati con molte iterazioni ripetute. Sfortunatamente, anche questo rende KDF costoso da usare.

Poiché il tuo segreto principale non è una password, ma una chiave appropriatamente lunga e casuale generata con una strong fonte di casualità, non è vulnerabile alle forze brute. Il costo di PBKDF2 è quindi uno spreco di cicli della CPU. Se vuoi ancora utilizzare PBKDF2 per derivare le chiavi di crittografia e / o IV, devi impostare il numero di iterazioni su 1. In alternativa, come spiegato sopra, non usare affatto un KDF: crittografare i dati con la chiave master "come è ", ma con una IV casuale specifica per istanza memorizzata lungo il risultato della crittografia.

AesCryptoServiceProvider si basa sull'implementazione del codice nativo sottostante. RijndaelManaged è scritto in "puro .NET". Per crittografare elementi corti, RijndaelManaged potrebbe effettivamente essere più efficiente; ridurrà inoltre il rischio di perdita di handle se si dimentica di chiamare il metodo Dispose() laddove necessario. Di solito, le prestazioni non contano (il tempo di crittografia e decrittografia sarà trascurabile rispetto al resto del server), ma se lo fa nel tuo caso, ricorda che hai diverse opzioni per l'implementazione.

Un tasto a 32 byte (256 bit) per AES è un po 'eccessivo, ma non danneggerà molto. Una chiave a 256 bit implica un costo CPU del + 40% per la crittografia e la decrittografia, rispetto a una chiave a 128 bit, ma per i piccoli elementi crittografati dovrebbe essere trascurabile.

    
risposta data 24.09.2015 - 14:53
fonte

Leggi altre domande sui tag