Почему из-за неверного пароля "Заполнение недействительно и не может быть удалено"?
Мне нужно было простое шифрование строк, поэтому я написал следующий код (с большим «вдохновением» отсюда ):
// 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)9
Хотя на этот вопрос уже был дан ответ, я думаю, что было бы неплохо объяснить, почему этого следует ожидать.
Схема заполнения обычно применяется, потому что большинство криптографических фильтров не являются семантически безопасными и предотвращают некоторые формы криптоатак. Например, обычно в RSA используется схема заполнения OAEP, которая предотвращает некоторые виды атак (такие как атака по выбранному открытому тексту или ослепление ).
Схема заполнения добавляет некоторый (обычно) случайный мусор к сообщению m перед отправкой сообщения. В методе OAEP, например, используются два оракула (это упрощенное объяснение):
- Учитывая размер модуля, вы дополняете k1 битами 0 и k0 битами со случайным числом.
- Затем, применив некоторое преобразование к сообщению, вы получите дополненное сообщение, которое зашифровывается и отправляется.
Это обеспечивает рандомизацию сообщений и способ проверить, является ли сообщение мусором или нет. Поскольку схема заполнения обратима, когда вы расшифровываете сообщение, в то время как вы ничего не можете сказать о целостности самого сообщения, вы фактически можете сделать некоторые утверждения о заполнении и, таким образом, вы можете узнать, правильно ли было расшифровано сообщение. или вы делаете что-то не так (например, кто-то подделал сообщение, или вы используете неправильный ключ)
У меня возникла похожая ошибка «Заполнение недействительно и не может быть удалено». исключение, но в моем случае ключ 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();
Другой причиной исключения может быть состояние гонки между несколькими потоками с использованием логики дешифрования - собственные реализации 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!
Если вы хотите, чтобы ваше использование было правильным, вы должны добавить аутентификацию в свой зашифрованный текст, чтобы вы могли убедиться, что это правильный пароль или что зашифрованный текст не был изменен. Прокладка, которую вы используете ISO10126 которое вызовет исключение только в том случае, если последний байт не расшифровывается как одно из 16 допустимых значений для заполнения (0x01-0x10). Таким образом, у вас есть 1/16 шанса, что это НЕ вызовет исключение с неправильным паролем, где, если вы аутентифицируете его, у вас есть детерминированный способ узнать, действительна ли ваша дешифровка.
Хотя использование крипто-API кажется простым, на самом деле довольно легко сделать ошибку. Например, вы используете фиксированную соль для получения ключа и iv, это означает, что каждый зашифрованный текст, зашифрованный одним и тем же паролем, будет повторно использовать его IV с этим ключом, что нарушает семантическую безопасность в режиме CBC, IV должен быть как непредсказуемым, так и уникальным для данный ключ.
По этой причине, чтобы легко сделать ошибки, у меня есть фрагмент кода, который я стараюсь обновлять и обновлять (комментарии, проблемы приветствуются):
Современные примеры симметричного аутентифицированного шифрования строки C#.
Если вы используете его, AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)
когда используется неправильный пароль, null
возвращается, если зашифрованный текст или iv был изменен null
, возвращается пост-шифрование , вы никогда не получите обратно ненужные данные или исключение заполнения.