Почему неправильный пароль вызывает сообщение «Заполнение недопустимо и не может быть удалено»?

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

  •  08-06-2019
  •  | 
  •  

Вопрос

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

    // 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

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

Это было полезно?

Решение

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

Схема заполнения обычно применяется, поскольку большинство криптографических фильтров не являются семантически безопасными и для предотвращения некоторых форм криптоатак.Например, обычно в ЮАР ОАЭП используется схема заполнения, которая предотвращает некоторые виды атак (например, атаку с использованием выбранного открытого текста или ослепляющий).

Схема заполнения добавляет к сообщению некоторый (обычно) случайный мусор перед отправкой сообщения.Например, в методе 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();

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

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

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

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

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

Если вы исключили несовпадение ключей, то кроме того 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!

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

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

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

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

MemoryStream ms = new MemoryStream(cipherText)

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top