RijndaelManaged "Заполнение недействительно и не может быть удалено", которое возникает только при расшифровке в процессе производства
Я знаю, что по этому вопросу были заданы другие вопросы, но до сих пор ни одно из них не дало решения или не было именно той проблемой, которая у меня есть.
Следующий класс обрабатывает шифрование и дешифрование строк, передаваемых ключей и векторов ВСЕГДА то же самое.
Строки, зашифрованные и дешифрованные, всегда являются числами, большинство из них работают, но случайная ошибка возникает при расшифровке (но только на рабочем сервере). Следует упомянуть, что как локальная, так и производственная среда находится в IIS6 на Windows Server 2003, код, который использует класс, находится в обработчике .ashx. Пример, который сбой на рабочем сервере - "0000232668"
Сообщение об ошибке
System.Security.Cryptography.CryptographicException: Заполнение недопустимо и не может быть удалено. в System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte [] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte [] & outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)
А для кода
public class Aes
{
private byte[] Key;
private byte[] Vector;
private ICryptoTransform EncryptorTransform, DecryptorTransform;
private System.Text.UTF8Encoding UTFEncoder;
public Aes(byte[] key, byte[] vector)
{
this.Key = key;
this.Vector = vector;
// our encyption method
RijndaelManaged rm = new RijndaelManaged();
rm.Padding = PaddingMode.PKCS7;
// create an encryptor and decyptor using encryption method. key and vector
EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector);
DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector);
// used to translate bytes to text and vice versa
UTFEncoder = new System.Text.UTF8Encoding();
}
/// Encrypt some text and return a string suitable for passing in a URL.
public string EncryptToString(string TextValue)
{
return ByteArrToString(Encrypt(TextValue));
}
/// Encrypt some text and return an encrypted byte array.
public byte[] Encrypt(string TextValue)
{
//Translates our text value into a byte array.
Byte[] bytes = UTFEncoder.GetBytes(TextValue);
Byte[] encrypted = null;
//Used to stream the data in and out of the CryptoStream.
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write))
{
cs.Write(bytes, 0, bytes.Length);
}
encrypted = memoryStream.ToArray();
}
return encrypted;
}
/// The other side: Decryption methods
public string DecryptString(string EncryptedString)
{
return Decrypt(StrToByteArray(EncryptedString));
}
/// Decryption when working with byte arrays.
public string Decrypt(byte[] EncryptedValue)
{
Byte[] decryptedBytes = null;
using (MemoryStream encryptedStream = new MemoryStream())
{
using (CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write))
{
decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length);
}
decryptedBytes = encryptedStream.ToArray();
}
return UTFEncoder.GetString(decryptedBytes);
}
/// Convert a string to a byte array. NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so).
// System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
// return encoding.GetBytes(str);
// However, this results in character values that cannot be passed in a URL. So, instead, I just
// lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100).
public byte[] StrToByteArray(string str)
{
if (str.Length == 0)
throw new Exception("Invalid string value in StrToByteArray");
byte val;
byte[] byteArr = new byte[str.Length / 3];
int i = 0;
int j = 0;
do
{
val = byte.Parse(str.Substring(i, 3));
byteArr[j++] = val;
i += 3;
}
while (i < str.Length);
return byteArr;
}
// Same comment as above. Normally the conversion would use an ASCII encoding in the other direction:
// System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
// return enc.GetString(byteArr);
public string ByteArrToString(byte[] byteArr)
{
byte val;
string tempStr = "";
for (int i = 0; i <= byteArr.GetUpperBound(0); i++)
{
val = byteArr[i];
if (val < (byte)10)
tempStr += "00" + val.ToString();
else if (val < (byte)100)
tempStr += "0" + val.ToString();
else
tempStr += val.ToString();
}
return tempStr;
}
EDIT: Благодарим за вашу помощь, однако ваши ответы не устранили проблему, которая оказалась чем-то глупо простым. Я генерировал зашифрованную строку на одном сервере и передавал ее обработчику на другом сервере для decrpytion и обработки, но оказалось, что результаты шифрования различаются при запуске на разных серверах, поэтому принимающий сервер не может его расшифровать. Один из ответов наткнулся на намек на это случайно, поэтому я принял его
Ответы
Ответ 1
Иногда вы получите сообщение о недопустимом заполнении, когда шифрование и дешифрование по какой-либо причине не использовали один и тот же ключ или вектор инициализации. Заполнение - это количество байтов, добавленных в конец вашего открытого текста, чтобы довести его до полного числа блоков для работы шифрования. В PKCS7 заполнение каждого байта равно количеству добавленных байтов, поэтому его можно всегда удалять после дешифрования. Ваше дешифрование привело к строке, в которой последние n байтов не равны значению n последнего байта (надеемся, что это предложение имеет смысл). Поэтому я бы дважды проверял все ваши ключи.
В качестве альтернативы, в вашем случае я бы предложил убедиться, что вы создаете и удаляете экземпляр RijndaelManagedTransform
для каждой операции шифрования и дешифрования, инициализируя его ключом и вектором. Эта проблема может быть вызвана повторным использованием этого объекта преобразования, а это означает, что после первого использования он больше не находится в правильном начальном состоянии.
Ответ 2
Я пытаюсь явно вызвать метод FlushFinalBlock в CryptoStream, прежде чем закрывать его. Это будет означать следующее в вашем методе шифрования:
using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write))
{
cs.Write(bytes, 0, bytes.Length);
cs.FlushFinalBlock();
}
Если вы этого не сделаете, может быть, что зашифрованные данные усекаются - это приведет к сценарию "недействительного дополнения". Заполнение всегда присутствует при использовании PKCS7, даже если зашифрованные данные выравниваются с длиной блока шифрования.
Ответ 3
это приводит к символьным значениям, которые не могут быть переданы в URL
Есть ли причина, по которой вы используете свою собственную кодировку StrToByteArray
вместо Base64 кодирование?
Если вы внесете эти изменения:
public string EncryptToString(string TextValue)
{
return Convert.ToBase64String(Encrypt(TextValue));
}
public string DecryptToString(string TextValue)
{
return Decrypt(Convert.FromBase64String(TextValue));
}
тогда все должно работать намного лучше.
Edit:
Что касается проблемы с ToBase64String и QueryString:
Если вы выполняете собственный анализ QueryString, вам нужно убедиться, что вы разделили только на first = -sign.
var myURL = "http://somewhere.com/default.aspx?encryptedID=s9W/h7Sls98sqw==&someKey=someValue";
var myQS = myURL.SubString(myURL.IndexOf("?") + 1);
var myKVPs = myQS.Split("&");
foreach (var kvp in myKVPs) {
// It is important you specify a maximum number of 2 elements
// since the Base64 encoded string might contain =-signs.
var keyValue = kvp.Split("=", 2);
var key = keyValue[0];
var value = keyValue[1];
if (key == "encryptedID")
var decryptedID = myAES.DecryptToString(value);
}
Таким образом вам не нужно заменять любые символы в QueryString при кодировании Base64.