C# Rijndael ошибка во время выполнения при попытке расшифровать Padding является недопустимым или не может быть удален
Я получаю ошибку во время выполнения Padding is invalid and cannot be removed
пытаясь расшифровать некоторые данные. Я прочитал все похожие темы, найденные в stackru, и рассмотрел следующие проблемы:
Если для расшифровки используется другой ключ, чем тот, который используется для шифрования, может возникнуть вышеуказанная проблема. Тем не менее, как вы можете видеть в исходном коде для целей тестирования, я жестко запрограммировал ключи, и все равно получаю ошибку времени выполнения.
Приложение может пытаться использовать другой режим заполнения для расшифровки, чем тот, который используется для шифрования. Установка режима явно во время процесса шифрования или дешифрования не решила проблему.
Некоторые говорят, что вам нужно будет использовать
FlushFinalBlock
метод, чтобы вы могли сказать приложению положить последние биты вblocksize
, Пробовал ставить его ниже метода Write, тоже не сработало.
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) { //Write all data to the stream. swEncrypt.Write(plainText); csEncrypt.FlushFinalBlock(); }
Ниже приведены фрагменты моего кода:
// Encrypt private byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV) { Key = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 }; IV = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 }; // Check arguments. if (plainText == null || plainText.Length <= 0) throw new ArgumentNullException("plainText"); if (Key == null || Key.Length <= 0) throw new ArgumentNullException("Key"); if (IV == null || IV.Length <= 0) throw new ArgumentNullException("Key"); byte[] encrypted; // Create an RijndaelManaged object // with the specified key and IV. using (RijndaelManaged rijAlg = new RijndaelManaged()) { rijAlg.Key = Key; rijAlg.IV = IV; rijAlg.Padding = PaddingMode.PKCS7; // Create a decrytor to perform the stream transform. ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV); // Create the streams used for encryption. using (MemoryStream msEncrypt = new MemoryStream()) { using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) { // Write all data to the stream. swEncrypt.Write(plainText); csEncrypt.FlushFinalBlock(); } encrypted = msEncrypt.ToArray(); MessageBox.Show("Encrypted str: " + Encoding.UTF8.GetString(encrypted).ToString()); } } } // Update buffer length this.Buflen = encrypted.Length; MessageBox.Show("Byte count: " + (Encoding.UTF8.GetByteCount(encrypted.ToString())).ToString()); MessageBox.Show("Decrypted: " + DecryptStringFromBytes(encrypted, this.Encryption_key, this.Encryption_iv)); // Return the encrypted bytes from the memory stream. return encrypted; } // Decrypt private string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV) { Key = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 }; IV = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 }; UTF8Encoding e = new UTF8Encoding(); MessageBox.Show("ciperText " + cipherText.Length); //cipherText = e.GetBytes(cipherText); //MessageBox.Show("ciperText2 Length " + cipherText.Length); // Check arguments. if (cipherText == null || cipherText.Length <= 0) throw new ArgumentNullException("cipherText"); if (Key == null || Key.Length <= 0) throw new ArgumentNullException("Key"); if (IV == null || IV.Length <= 0) throw new ArgumentNullException("Key"); // Declare the string used to hold // the decrypted text. string plaintext = null; // Create an RijndaelManaged object // with the specified key and IV. using (RijndaelManaged rijAlg = new RijndaelManaged()) { rijAlg.Key = Key; rijAlg.IV = IV; rijAlg.Padding = PaddingMode.PKCS7; // Create a decrytor to perform the stream transform. ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV); // Create the streams used for decryption. using (MemoryStream msDecrypt = new MemoryStream(cipherText)) { using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) { using (StreamReader srDecrypt = new StreamReader(csDecrypt)) { // Read the decrypted bytes from the decrypting stream // and place them in a string. if (cipherText.Length != rijAlg.BlockSize) { throw new InvalidOperationException("Invalid cipherText length"); } plaintext = srDecrypt.ReadToEnd(); csDecrypt.FlushFinalBlock(); } } } } return plaintext; } byte[] encoded_message = new byte[16]; encoded_message = EncryptStringToBytes("test", this.Encryption_key,this.Encryption_iv);
Я пытаюсь заставить это работать со вчерашнего утра. Ни одно из найденных мной решений не сработало в моем приложении. Я знаю, что код может выглядеть немного беспорядочно, но, пожалуйста, не обращайте внимания на части, которые не используются - как только я получу эту работу, она, безусловно, потребует некоторого рефакторинга.
Как вы можете видеть, при использовании жестко закодированного ключа и IV для целей тестирования по-прежнему возникает ошибка времени выполнения, поэтому передача учетных данных, отличных от используемых для шифрования, не является причиной.
Может кто-нибудь придумать, как решить эту проблему? Благодарю.
1 ответ
Я пишу эту ветку для всех, кто пытался отправить зашифрованные данные через сокеты, так как у меня были проблемы с этим в течение последних 3 дней. Как вы знаете, зашифрованные данные могут содержать дополняющие символы и специальные символы, которые в сочетании с тем, как пакеты отправляются через сокет (обычно в двоичной форме и в виде кусков), могут не отображаться на другой стороне так, как вы ожидаете. Обычно вы получаете следующие ошибки: Padding недействителен и не может быть удален, или *Длина данных для расшифровки недопустима *. Я сталкивался с этим во время написания приложения, которое отправляет зашифрованные учетные данные Rijndael через сокет UDP. Поэтому обычно вы отправляете не то, что получаете, добавляется символ-заполнитель (или несколько подобных), что приводит к зашифрованным данным с ошибками, которые не могут быть распознаны алгоритмом rijndael (обратите внимание, что это может также произойти при попытке расшифровать любой другой тип шифрования, и я полагаю, что это действительно так). Итак, я даю вам свое решение ниже с намерением помочь всем, кто находится в процессе отправки зашифрованных данных и пытается сделать это правильно (я не говорю, что это ЕДИНСТВЕННЫЙ и это самый правильный способ сделать это, но это способ, который я нашел для работы, и что я лично использовал):
1) Учитывая, что большинство функций шифрования принимают байтовый массив в качестве параметра, и большинство функций сокетов для отправки данных также делают это, сначала мы получаем нашу зашифрованную строку в виде байтового массива:
byte[] encrypted_string = new byte[lengthofblocksize]; // where lengthofblocksize would be the length in bytes of the blocksize for the used algorithm, for rijndael that usually is 16
//getting the encrypted byte array
encrypted_string = EncryptStringToBytes(messageInPlainText, key, iv);
где messageInPlainText - это ваше текстовое сообщение, ключ и iv - ваш ключ и параметры iv.
2) Перед отправкой данных При отправке данных через сокет зашифруйте их в строку base64. Предположим, мы храним нашу строку в переменной "textdata". Первым шагом будет строка encrypted_string_base64 = Convert.ToBase64String (textdata);
3) теперь у вас есть строка base64 в виде строки. Вам нужно получить его в байтовом массиве, что можно сделать с помощью метода GetBytes UTF8Encoding: encrypted_string = enc.GetBytes(encrypted_string_base64.ToString()); 4) и ТОГДА вы готовы отправлять данные через сокет, чтобы вы могли получить правильные закодированные двоичные данные на другой стороне. sck.Send (encrypted_string);
5) Итак, с другой стороны, вы получите строку обычно в двоичных данных, давайте предположим, что она находится в переменной recvdata. Что вам нужно сделать, это преобразовать байтовый массив в строку:
string recv_textdata = Convert.FromBase64String(recvdata);
Однако перед этим вам может понадобиться создать метод, который очищает все ненужные отступы и символы:
UTF8Encoding enc = new UTF8ENcoding();
string recv_textdata = FormatStringAsBase64(enc.GetString(recvdata)).Trim();
(please scroll to the bottom in order to see the full definition of the above method)
6) Теперь у вас есть зашифрованные данные в виде строки base64, которую вы все еще не готовы передать в метод расшифровки, так как вам нужно декодировать их из base64 и преобразовать в массив байтов, что можно сделать напрямую с помощью Метод Convert.FromBase64String (по крайней мере, мой принимает байтовый массив в качестве параметра) - он принимает строку в качестве параметра и возвращает байтовый массив в результате:
byte[] recv_textdata_encrypted_ready = new byte[lengthofblocksize]; //length of blocksize is 16 for rijndael as explained
recv_textdata_encrypted_ready = Convert.FromBase64String(recv_textdata);
7) ТЕПЕРЬ вы готовы передать свой зашифрованный байтовый массив для расшифровки: string decrypted = DecryptStringFromBytes(recv_textdata_encrypted_ready, this.Encryption_key, this.Encryption_iv);
Итак, вернемся к определению FormatAsBase64String и зачем оно вам нужно. Когда вы пропускаете закодированную строку base64 через сокет UDP, дейтаграмма отправляется порциями и может добавить ненужное заполнение, которое обычно можно увидеть только путем сравнения хеша md5 данных, которые вы отправляете через сокет, и данных, которые вы получаете через сокета, или записав его в двоичный файл (так я и использовал) и открыв результат с помощью текстового редактора, поддерживающего кодировку UTF8 (или любую другую кодировку, которую вы использовали в пункте 5)). Поэтому, если вы не используете метод, указанный ниже, отправленные вами данные могут не совпадать с данными, которые вы ПОЛУЧАЕТЕ, что приведет к строке с ненужным заполнением, не подходящей для дешифрования через библиотеки расшифровки.NET, так как они не распознают ее, или что произошло в моем случае, метод Convert.FromBase64String не распознает ее как допустимую строку в кодировке base64 и выдаст ошибку, что строка содержит недопустимый отступ и не имеет допустимый кодированный формат base64. Итак, чтобы проверить вашу строку как действительную base64 только для того, чтобы у вас не возникало проблем при преобразовании ее в обычно двоичный зашифрованный массив байтов, вам может понадобиться что-то вроде этого:
private static string FormatStringAsBase64(string message_encrypted_base64)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < message_encrypted_base64.Length; i++)
{
//current char
char currchar = message_encrypted_base64[i];
//check if we got a valid base64 symbol
/* From Microsoft MSDN Official Convert.ToBase64String Method (http://msdn.microsoft.com/en-us/library/dhx0d524.aspx):
* The base-64 digits in ascending order from zero are the uppercase
* characters "A" to "Z", the lowercase characters "a" to "z", the numerals "0"
* to "9", and the symbols "+" and "/".
* The valueless character, "=",
* is used for trailing padding.
*
*/
if (char.IsLetterOrDigit(currchar) || currchar == '=' || currchar == '+' || currchar == '/')
{
sb.Append(currchar);
}
}
return sb.ToString();
}
Я пишу это для тех, кто пытается отправить зашифрованные данные rijndael через сокет UDP. Я потерял 3 дня, чтобы выяснить это сам (ну, может, я выгляжу как придурок, который потратил столько времени, чтобы понять это, но когда у вас есть запутанный код со всеми способами, пытающимися выяснить, в чем проблема, что происходит) после прочтения целая куча потоков с похожими ошибками на stackru. Ни у кого из них не было точно такой же проблемы или решения. Поэтому, если вы пытаетесь отправить зашифрованные данные через сокет UDP и получаете сообщение об ошибке, например, Неверное заполнение и заполнение не могут быть удалены или Длина данных для расшифровки недопустима, этот поток может пригодиться.
Я считаю, что эта ветка может помочь многим другим, кто сталкивался с описанной проблемой, и сэкономит время многим пользователям на этом форуме. Если моды не верят или находят нить ненужной или неуместной, вы можете от нее избавиться, я просто решил потратить некоторое время, чтобы описать проблему, с которой я столкнулся, и способы ее решения, так как это может сэкономить много времени на другие, поверьте мне, это действительно сводило меня с ума, пока я не нашел решение, которое, очевидно, довольно легко исправить.