Почему из-за неверного пароля "Заполнение недействительно и не может быть удалено"?

Мне нужно было простое шифрование строк, поэтому я написал следующий код (с большим «вдохновением» отсюда ):

    // 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());
    }

Код работает нормально, за исключением того, что при расшифровке данных с неправильным ключом я получаю CryptographicException - «Заполнение недопустимо и не может быть удалено» - в строке cs.Close () в decryptString.

пример кода:

    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

Мой вопрос: этого следует ожидать? Я бы подумал, что расшифровка неправильного пароля приведет к бессмысленному выводу, а не к исключению.

Ответов (9)

Решение

Хотя на этот вопрос уже был дан ответ, я думаю, что было бы неплохо объяснить, почему этого следует ожидать.

Схема заполнения обычно применяется, потому что большинство криптографических фильтров не являются семантически безопасными и предотвращают некоторые формы криптоатак. Например, обычно в RSA используется схема заполнения OAEP, которая предотвращает некоторые виды атак (такие как атака по выбранному открытому тексту или ослепление ).

Схема заполнения добавляет некоторый (обычно) случайный мусор к сообщению m перед отправкой сообщения. В методе OAEP, например, используются два оракула (это упрощенное объяснение):

  1. Учитывая размер модуля, вы дополняете k1 битами 0 и k0 битами со случайным числом.
  2. Затем, применив некоторое преобразование к сообщению, вы получите дополненное сообщение, которое зашифровывается и отправляется.

Это обеспечивает рандомизацию сообщений и способ проверить, является ли сообщение мусором или нет. Поскольку схема заполнения обратима, когда вы расшифровываете сообщение, в то время как вы ничего не можете сказать о целостности самого сообщения, вы фактически можете сделать некоторые утверждения о заполнении и, таким образом, вы можете узнать, правильно ли было расшифровано сообщение. или вы делаете что-то не так (например, кто-то подделал сообщение, или вы используете неправильный ключ)

У меня возникла похожая ошибка «Заполнение недействительно и не может быть удалено». исключение, но в моем случае ключ IV и отступы были правильными.

Оказалось, что промывки криптопотока - это все, чего не хватало.

Нравится:

            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();

У меня была аналогичная проблема, проблема в методе дешифрования заключалась в инициализации пустого потока памяти. когда он работал, когда я инициализировал его массивом байтов зашифрованного текста следующим образом:

MemoryStream ms = new MemoryStream(cipherText)

Другой причиной исключения может быть состояние гонки между несколькими потоками с использованием логики дешифрования - собственные реализации ICryptoTransform не являются потокобезопасными (например, SymmetricAlgorithm), поэтому его следует поместить в эксклюзивный раздел, например, с помощью блокировки . Дополнительные сведения см. Здесь: http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/

Если вы исключили ключ-несоответствие, то помимо FlushFinalBlock() (см ответа Янивого в), вызов Close() на CryptoStream хватаете также будет.

Если вы очищаете ресурсы строго с помощью using блоков, обязательно вложите блок под CryptoStream себя:

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

Меня укусило это (или подобное):

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!

У меня сработал ответ, обновленный пользователем "atconway".

Проблема заключалась не в заполнении, а в том, что ключ был другим во время шифрования и дешифрования. Ключ и iv должны быть одинаковыми во время шифрования и дешифрования одного и того же значения.

Если вы хотите, чтобы ваше использование было правильным, вы должны добавить аутентификацию в свой зашифрованный текст, чтобы вы могли убедиться, что это правильный пароль или что зашифрованный текст не был изменен. Прокладка, которую вы используете ISO10126 которое вызовет исключение только в том случае, если последний байт не расшифровывается как одно из 16 допустимых значений для заполнения (0x01-0x10). Таким образом, у вас есть 1/16 шанса, что это НЕ вызовет исключение с неправильным паролем, где, если вы аутентифицируете его, у вас есть детерминированный способ узнать, действительна ли ваша дешифровка.

Хотя использование крипто-API кажется простым, на самом деле довольно легко сделать ошибку. Например, вы используете фиксированную соль для получения ключа и iv, это означает, что каждый зашифрованный текст, зашифрованный одним и тем же паролем, будет повторно использовать его IV с этим ключом, что нарушает семантическую безопасность в режиме CBC, IV должен быть как непредсказуемым, так и уникальным для данный ключ.

По этой причине, чтобы легко сделать ошибки, у меня есть фрагмент кода, который я стараюсь обновлять и обновлять (комментарии, проблемы приветствуются):

Современные примеры симметричного аутентифицированного шифрования строки C#.

Если вы используете его, AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password) когда используется неправильный пароль, null возвращается, если зашифрованный текст или iv был изменен null, возвращается пост-шифрование , вы никогда не получите обратно ненужные данные или исключение заполнения.

Да, этого следовало ожидать, или, по крайней мере, это именно то, что происходит, когда наши криптографические подпрограммы получают данные, не поддающиеся расшифровке.

В CryptoStream могут быть непрочитанные байты. Закрытие перед полным чтением потока вызывало ошибку в моей программе.