Мне нужно было простое шифрование строк, поэтому я написал следующий код (с большим «вдохновением» из здесь):
// 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
Мой вопрос: этого следует ожидать? Я бы подумал, что расшифровка неправильного пароля приведет к бессмысленному выводу, а не к исключению.





Да, этого следовало ожидать, или, по крайней мере, это именно то, что происходит, когда наши криптографические подпрограммы получают данные, не поддающиеся расшифровке.
Хотя на этот вопрос уже был дан ответ, я думаю, что было бы неплохо объяснить Почему, этого следовало ожидать.
Схема заполнения обычно применяется, потому что большинство криптографических фильтров не являются семантически безопасными и предотвращают некоторые формы криптоатак. Например, обычно в RSA используется схема заполнения OAEP, которая предотвращает некоторые виды атак (например, атаку по выбранному открытому тексту или ослепляющий).
Схема заполнения добавляет некоторый (обычно) случайный мусор к сообщению m перед отправкой сообщения. В методе OAEP, например, используются два оракула (это упрощенное объяснение):
Это обеспечивает рандомизацию сообщений и способ проверить, является ли сообщение мусором или нет. Поскольку схема заполнения обратима, когда вы расшифровываете сообщение, в то время как вы не можете ничего сказать о целостности самого сообщения, вы фактически можете сделать некоторые утверждения о заполнении и, таким образом, вы можете узнать, правильно ли было расшифровано сообщение. или вы делаете что-то не так (например, кто-то подделал сообщение, или вы используете неправильный ключ)
Хорхе, спасибо за объяснение. Я наблюдаю такое же поведение, как описано, данные, которые расшифровываются, верны. я должен есть это исключение, или (надеюсь) есть что-то, что я делаю неправильно, что я могу исправить? что происходит не так, когда генерируется исключение? все сообщения, которые я прочитал, похоже, написаны людьми, которые больше заинтересованы в том, чтобы исключение исчезло. в моем случае я хочу, чтобы мое использование было правильным :)
Это не отвечает на вопрос OP, вопрос касается симметричного блочного шифра Rijndael, а не асимметричного шифра, такого как RSA. Для блочного шифра добавляется заполнение, чтобы данные, подлежащие шифрованию, были кратны размеру блока, обычно с использованием PKCS # 7 (урожденная PKCS # 5). С этими схемами заполнения добавляются случайные байты нет. Обратите внимание, что дополнение ISO 10126 было удалено, а случайные байты не добавляли безопасности. Ответ был бы точным, если бы он говорил о симметричном заполнении шифрования. Предложение: исправьте ответ, чтобы ответить на вопрос. ЗОМГ, старший разработчик.
@zaph Я просто объясняю в общих чертах, почему вы получаете исключение недопустимого заполнения вместо мусора при расшифровке с использованием недопустимого пароля. RSA был просто примером, конкретная использованная схема заполнения была и примером. Он отвечает на вопрос op, потому что он говорит: Да, это ожидается, и вот почему, а также объяснение этой странной ссылки на «заполнение», которую вы можете понять, только если вы понимаете, что такое схема заполнения и почему она используется, но позволит вам озадачен, если вы этого не сделаете (какое отношение шифрование имеет к заполнению ???)
Вопрос касается заполнения Rijndael, и вы объясняете, что заполнение RSA очень отличается и выполняется по разным причинам. Хорошо, я понимаю, что люди не понимают разницы между симметричным и асимметричным шифрованием, и что в последнее время RSA кажется более заметным, и многие люди используют RSA, когда AES лучше подходит. Но было бы лучше, если бы ответ соответствовал вопросу.
В 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();.
Возможно, вам стоит использовать using для этих объектов, поскольку они одноразовые. Этого было бы достаточно.
Также обратите внимание, что простой Flush(), который вы обычно делаете с потоком, - это нет, такой же, как FlushFinalBlock(), необходимый для CryptoStream. Это немного двусмысленно в описании, предшествующем коду.
Это не связано с вопросом, спрашивающий знает причину исключения, неверный пароль (не сбрасывается) и спрашивает, почему неверный пароль вызывает это конкретное исключение.
У меня была аналогичная проблема, проблема в методе дешифрования заключалась в инициализации пустого потока памяти. когда он работал, когда я инициализировал его массивом байтов зашифрованного текста следующим образом:
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).
OP знает, что это несоответствие ключей вызывает ошибку, и спрашивает, почему несоответствие ключей вызывает эту ошибку.
Это сэкономило мне столько времени с вашим комментарием:
"The code appears to work fine, except that when decrypting data with an incorrect key"I поклялся Я скопировал ключи, но, глядя вдвое, я этого не делал. Надеюсь, это поможет кому-то еще, прежде чем взглянуть на механизм заполнения или изменить код.