Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

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

  • Учитывая размер модуля, вы добавляете бит 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();

Ответ 3

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

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

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

MemoryStream ms = new MemoryStream(cipherText)

Ответ 9

Я также получаю, что заполнение недействительно и не может быть удалено. Как сказал выше, причиной было несколько буферизованных байтов в CryptoStream. Вот как это было исправлено, вызвав метод FlushFinalBlock():

using (CryptoStream cryptoStream = new CryptoStream(memoryStream, algorithm.CreateDecryptor(), CryptoStreamMode.Write)) {
    cryptoStream.Write(bytes, 0, bytes.Length);
    cryptoStream.FlushFinalBlock();
    result = Encoding.UTF8.GetString(memoryStream.ToArray());
    return result;
}

Ответ 10

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

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