Perché una password errata causa il messaggio "Il riempimento non è valido e non può essere rimosso"?

StackOverflow https://stackoverflow.com/questions/11762

  •  08-06-2019
  •  | 
  •  

Domanda

Avevo bisogno di una semplice crittografia delle stringhe, quindi ho scritto il seguente codice (con molta "ispirazione" da Qui):

    // create and initialize a crypto algorithm
    private static SymmetricAlgorithm getAlgorithm(string password) {
        SymmetricAlgorithm algorithm = Rijndael.Create();
        Rfc2898DeriveBytes rdb = new Rfc2898DeriveBytes(
            password, new byte[] {
            0x53,0x6f,0x64,0x69,0x75,0x6d,0x20,             // salty goodness
            0x43,0x68,0x6c,0x6f,0x72,0x69,0x64,0x65
        }
        );
        algorithm.Padding = PaddingMode.ISO10126;
        algorithm.Key = rdb.GetBytes(32);
        algorithm.IV = rdb.GetBytes(16);
        return algorithm;
    }

    /* 
     * encryptString
     * provides simple encryption of a string, with a given password
     */
    public static string encryptString(string clearText, string password) {
        SymmetricAlgorithm algorithm = getAlgorithm(password);
        byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);
        MemoryStream ms = new MemoryStream();
        CryptoStream cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write);
        cs.Write(clearBytes, 0, clearBytes.Length);
        cs.Close();
        return Convert.ToBase64String(ms.ToArray());
    }

    /*
     * decryptString
     * provides simple decryption of a string, with a given password
     */
    public static string decryptString(string cipherText, string password) {
        SymmetricAlgorithm algorithm = getAlgorithm(password);
        byte[] cipherBytes = Convert.FromBase64String(cipherText);
        MemoryStream ms = new MemoryStream();
        CryptoStream cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write);
        cs.Write(cipherBytes, 0, cipherBytes.Length);
        cs.Close();            
        return System.Text.Encoding.Unicode.GetString(ms.ToArray());
    }

Il codice sembra funzionare correttamente, tranne per il fatto che durante la decrittografia dei dati con una chiave errata, ottengo un'eccezione CryptographicException - "Il riempimento non è valido e non può essere rimosso" - sulla riga cs.Close() in decryptString.

codice di esempio:

    string password1 = "password";
    string password2 = "letmein";
    string startClearText = "The quick brown fox jumps over the lazy dog";
    string cipherText = encryptString(startClearText, password1);
    string endClearText = decryptString(cipherText, password2);     // exception thrown

La mia domanda è: è prevedibile?Avrei pensato che la decrittografia con la password sbagliata avrebbe portato solo a un output senza senso, piuttosto che a un'eccezione.

È stato utile?

Soluzione

Anche se a questo è già stata data risposta, penso che sarebbe una buona idea spiegarlo Perché c'è da aspettarselo.

Di solito viene applicato uno schema di riempimento perché la maggior parte dei filtri crittografici non sono semanticamente sicuri e per prevenire alcune forme di attacchi crittografici.Ad esempio, di solito in RSA il OAEP viene utilizzato uno schema di riempimento che impedisce alcuni tipi di attacchi (come un attacco di testo in chiaro scelto o accecante).

Uno schema di riempimento aggiunge alcuni elementi inutili (di solito) casuali al messaggio m prima che il messaggio venga inviato.Nel metodo OAEP, ad esempio, vengono utilizzati due Oracoli (questa è una spiegazione semplicistica):

  1. Data la dimensione del modulo, aggiungi k1 bit con 0 e k0 bit con un numero casuale.
  2. Quindi applicando qualche trasformazione al messaggio si ottiene il messaggio imbottito che viene crittografato e inviato.

Ciò fornisce una randomizzazione per i messaggi e un modo per verificare se il messaggio è spazzatura o meno.Dato che lo schema di riempimento è reversibile, quando decodifichi il messaggio mentre non puoi dire nulla sull'integrità del messaggio stesso puoi, infatti, fare qualche asserzione sul riempimento e quindi puoi sapere se il messaggio è stato decrittografato correttamente oppure stai facendo qualcosa di sbagliato (es. qualcuno ha manomesso il messaggio oppure stai usando il tasto sbagliato)

Altri suggerimenti

Ho sperimentato una simile "L'imbottitura non è valida e non può essere rimossa". Eccezione, ma nel mio caso la chiave IV e l'imbottitura erano corrette.

Si è scoperto che svuotare il flusso crittografico era tutto ciò che mancava.

Come questo:

            MemoryStream msr3 = new MemoryStream();
            CryptoStream encStream = new CryptoStream(msr3, RijndaelAlg.CreateEncryptor(), CryptoStreamMode.Write);
            encStream.Write(bar2, 0, bar2.Length);
            // unless we flush the stream we would get "Padding is invalid and cannot be removed." exception when decoding
            encStream.FlushFinalBlock();
            byte[] bar3 = msr3.ToArray();

Se vuoi che il tuo utilizzo sia corretto, dovresti aggiungere autenticazione al tuo testo cifrato in modo da poter verificare che sia la password corretta o che il testo cifrato non sia stato modificato.L'imbottitura che stai utilizzando ISO10126 genererà un'eccezione solo se l'ultimo byte non viene decrittografato come uno dei 16 valori validi per il riempimento (0x01-0x10).Quindi hai 1/16 di possibilità che NON lanci l'eccezione con la password sbagliata, dove se la autentichi hai un modo deterministico per sapere se la tua decrittazione è valida.

Usare le API crittografiche, anche se apparentemente facile, in realtà è piuttosto facile commettere errori.Ad esempio, usi un salt fisso per la tua chiave e la derivazione iv, ciò significa che ogni testo cifrato crittografato con la stessa password riutilizzerà il suo IV con quella chiave, che interrompe la sicurezza semantica con la modalità CBC, l'IV deve essere sia imprevedibile che unico per una determinata chiave.

Per questo motivo è facile commettere errori, ho uno snippet di codice, che cerco di mantenere rivisto e aggiornato (commenti, problemi sono benvenuti):

Esempi moderni di crittografia autenticata simmetrica di una stringa C#.

Se lo usi è AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password) quando viene utilizzata la password sbagliata, null viene restituito se il testo cifrato o iv è stato modificato dopo la crittografia null viene restituito, non riceverai mai indietro dati spazzatura o un'eccezione di riempimento.

Se hai escluso la mancata corrispondenza della chiave, allora inoltre FlushFinalBlock() (vedi la risposta di Yaniv), chiamando Close() sul CryptoStream basterà anche.

Se stai ripulendo le risorse rigorosamente con using blocchi, assicurati di nidificare il blocco per il file CryptoStream si:

using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
{
  using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
  {
    encStream.Write(bar2, 0, bar2.Length);
  } // implicit close
  byte[] encArray = ms.ToArray();
}

Sono stato morso da questo (o simile):

using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
{
  encStream.Write(bar2, 0, bar2.Length);
  byte[] encArray = ms.ToArray();
} // implicit close -- too late!

Sì, c'è da aspettarselo, o almeno è esattamente ciò che accade quando le nostre routine crittografiche ottengono dati non decodificabili

Un altro motivo dell'eccezione potrebbe essere una condizione di competizione tra diversi thread che utilizzano la logica di decrittografia: le implementazioni native di ICryptoTransform sono non thread-safe (per esempio.SymmetricAlgorithm), quindi dovrebbe essere inserito nella sezione esclusiva, ad es.utilizzando serratura.Si prega di fare riferimento qui per maggiori dettagli: http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/

Potrebbero esserci alcuni byte non letti nel CryptoStream.La chiusura prima della lettura completa dello stream causava l'errore nel mio programma.

Ho avuto un problema simile, il problema nel metodo di decrittografia stava inizializzando un flusso di memoria vuoto.quando ha funzionato quando l'ho inizializzato con l'array di byte di testo cifrato in questo modo:

MemoryStream ms = new MemoryStream(cipherText)

La risposta aggiornata dall'utente "atconway" ha funzionato per me.

Il problema non era il riempimento ma la chiave che era diversa durante la crittografia e la decrittografia.La chiave e iv dovrebbero essere gli stessi durante la crittografia e la decrittografia dello stesso valore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top