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

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

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

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

Это сэкономило мне столько времени с вашим комментарием: "The code appears to work fine, except that when decrypting data with an incorrect key" I поклялся Я скопировал ключи, но, глядя вдвое, я этого не делал. Надеюсь, это поможет кому-то еще, прежде чем взглянуть на механизм заполнения или изменить код.

atconway 31.07.2013 19:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
40
1
70 949
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

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

Ответ принят как подходящий

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

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

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

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

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

Хорхе, спасибо за объяснение. Я наблюдаю такое же поведение, как описано, данные, которые расшифровываются, верны. я должен есть это исключение, или (надеюсь) есть что-то, что я делаю неправильно, что я могу исправить? что происходит не так, когда генерируется исключение? все сообщения, которые я прочитал, похоже, написаны людьми, которые больше заинтересованы в том, чтобы исключение исчезло. в моем случае я хочу, чтобы мое использование было правильным :)

stuck 23.07.2010 02:11

Это не отвечает на вопрос OP, вопрос касается симметричного блочного шифра Rijndael, а не асимметричного шифра, такого как RSA. Для блочного шифра добавляется заполнение, чтобы данные, подлежащие шифрованию, были кратны размеру блока, обычно с использованием PKCS # 7 (урожденная PKCS # 5). С этими схемами заполнения добавляются случайные байты нет. Обратите внимание, что дополнение ISO 10126 было удалено, а случайные байты не добавляли безопасности. Ответ был бы точным, если бы он говорил о симметричном заполнении шифрования. Предложение: исправьте ответ, чтобы ответить на вопрос. ЗОМГ, старший разработчик.

zaph 08.07.2016 22:52

@zaph Я просто объясняю в общих чертах, почему вы получаете исключение недопустимого заполнения вместо мусора при расшифровке с использованием недопустимого пароля. RSA был просто примером, конкретная использованная схема заполнения была и примером. Он отвечает на вопрос op, потому что он говорит: Да, это ожидается, и вот почему, а также объяснение этой странной ссылки на «заполнение», которую вы можете понять, только если вы понимаете, что такое схема заполнения и почему она используется, но позволит вам озадачен, если вы этого не сделаете (какое отношение шифрование имеет к заполнению ???)

Jorge Córdoba 08.07.2016 23:36

Вопрос касается заполнения Rijndael, и вы объясняете, что заполнение RSA очень отличается и выполняется по разным причинам. Хорошо, я понимаю, что люди не понимают разницы между симметричным и асимметричным шифрованием, и что в последнее время RSA кажется более заметным, и многие люди используют RSA, когда AES лучше подходит. Но было бы лучше, если бы ответ соответствовал вопросу.

zaph 09.07.2016 00:51

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

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

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

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

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

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

У меня возникла похожая ошибка «Заполнение недействительно и не может быть удалено». исключение, но в моем случае ключ 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();

То же самое должно делать encStream.Close();.

sharpener 30.08.2014 18:41

Возможно, вам стоит использовать using для этих объектов, поскольку они одноразовые. Этого было бы достаточно.

user2173353 18.05.2016 14:14

Также обратите внимание, что простой Flush(), который вы обычно делаете с потоком, - это нет, такой же, как FlushFinalBlock(), необходимый для CryptoStream. Это немного двусмысленно в описании, предшествующем коду.

Marc L. 13.10.2016 13:35

Это не связано с вопросом, спрашивающий знает причину исключения, неверный пароль (не сбрасывается) и спрашивает, почему неверный пароль вызывает это конкретное исключение.

jbtule 08.05.2017 21:52

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

MemoryStream ms = new MemoryStream(cipherText)

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

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

Другой причиной исключения может быть состояние гонки между несколькими потоками с использованием логики дешифрования - собственные реализации 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!

Но если вы используете encStream.FlushFinalBlock(), я бы предположил, что не имеет значения, где вы делаете ms.ToArray() (вне или внутри CryptoStreamusing).

nawfal 08.05.2017 10:24

OP знает, что это несоответствие ключей вызывает ошибку, и спрашивает, почему несоответствие ключей вызывает эту ошибку.

jbtule 08.05.2017 21:55

Другие вопросы по теме