RijndaelManaged“填充无效且无法删除”仅在生产中解密时才会发生

时间:2010-01-22 10:32:20

标签: .net cryptography aes encryption rijndaelmanaged

我知道其他问题已经被问到,但到目前为止还没有提供解决方案,或者正是我的问题。

下面的类处理字符串的加密和解密,传入的密钥和向量总是相同的。

加密和解密的字符串总是数字,大多数工作但偶尔会在解密时失败(但仅在生产服务器上)。我应该提到本地和生产环境都在Windows Server 2003上的IIS6中,使用该类的代码位于.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;
        }

编辑:感谢您的所有帮助,但是您的答案并没有解决问题,结果证明是非常简单的。我在一台服务器上生成一个加密的字符串,并将其交给另一台服务器上的处理程序进行decrpytion和处理,但事实证明,当在不同的服务器上运行时,加密结果会有所不同,因此接收服务器无法对其进行解密。其中一个答案偶然发现了这个暗示,这就是我接受它的原因

3 个答案:

答案 0 :(得分:12)

当加密和解密因任何原因未使用相同的密钥或初始化向量时,有时会收到有关无效填充的消息。填充是添加到明文末尾的一些字节,以使密码可以处理完整数量的块。在PKCS7填充中,每个字节等于添加的字节数,因此在解密后总是可以删除它。你的解密导致了一个字符串,其中最后一个 n 字节不等于最后一个字节的值 n (希望句子有意义)。所以我会仔细检查你所有的钥匙。

或者,在您的情况下,我建议您确保为每个加密和解密操作创建并配置RijndaelManagedTransform的实例,并使用密钥和向量对其进行初始化。重新使用此转换对象可能会导致此问题,这意味着在第一次使用后,它不再处于正确的初始状态。

答案 1 :(得分:12)

我倾向于在关闭之前在CryptoStream上显式调用FlushFinalBlock方法。这意味着在您的加密方法中执行以下操作:

using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write))
{
    cs.Write(bytes, 0, bytes.Length);
    cs.FlushFinalBlock();        
}

如果不这样做,可能是加密数据被截断 - 这将导致“无效填充”方案。使用PKCS7时始终存在填充,即使加密的数据与密码的块长度对齐。

答案 2 :(得分:2)

  

这导致无法在URL中传递的字符值

您是否有理由使用自己的编码StrToByteArray而不是Base64编码?

如果您进行了这些更改:

public string EncryptToString(string TextValue)
{
  return Convert.ToBase64String(Encrypt(TextValue));
}

public string DecryptToString(string TextValue)
{
  return Decrypt(Convert.FromBase64String(TextValue));
}

那么事情应该会好很多。

修改
关于ToBase64String和QueryString的问题:
如果您自己进行QueryString解析,那么您需要确保在第一个= -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);
}

这样,当它的Base64编码时,你不需要替换QueryString中的任何字符。