代码之家  ›  专栏  ›  技术社区  ›  Nick Allen

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

  •  15
  • Nick Allen  · 技术社区  · 15 年前

    我知道还有其他问题要问,但到目前为止还没有提供解决方案,也没有一个问题是我的问题。

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

    被加密和解密的字符串始终是数字,大多数是工作的,但在解密时偶尔会失败(但仅在生产服务器上)。我应该提到,本地和生产环境都在WindowsServer2003的IIS6中,使用类的代码位于.ashx处理程序中。在生产服务器上失败的示例是“0000232668”

    错误消息是

    System.Security.Cryptography.CryptographicException:填充无效,无法删除。 at system.security.cryptography.rijndaelmanagedtransform.decryptdata(byte[]inputbuffer,int32 inputfset,int32 inputcount,byte[]&outputbuffer,int32 outputfset,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;
            }
    

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

    3 回复  |  直到 15 年前
        1
  •  11
  •   David M    12 年前

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

    或者,在您的情况下,我建议确保您创建并释放 RijndaelManagedTransform 对于每个加密和解密操作,用密钥和向量初始化它。这个问题很可能是由重用这个转换对象引起的,这意味着在第一次使用之后,它不再处于正确的初始状态。

        2
  •  12
  •   Dave Cluderay    15 年前

    我倾向于明确地称 FlushFinalBlock 方法,然后关闭它。这意味着在加密方法中执行以下操作:

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

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

        3
  •  2
  •   Sani Huttunen    15 年前

    这将导致无法在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的问题:
    如果您自己进行查询字符串分析,那么您需要确保只在第一个=-符号上进行拆分。

    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编码时,就不需要替换它中的任何字符。