【问题标题】:RijndaelManaged "Padding is invalid and cannot be removed" that only occurs when decrypting in productionRijndaelManaged“填充无效且无法删除”,仅在生产中解密时出现
【发布时间】:2010-01-22 10:32:20
【问题描述】:

我知道有人对此提出了其他问题,但到目前为止还没有提供解决方案或正是我遇到的问题。

下面的类处理字符串的加解密,传入的key和vector总是一样的。

被加密和解密的字符串总是数字,大多数都可以,但偶尔会在解密时失败(但仅在生产服务器上)。我应该提到,本地和生产环境都在 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;
        }

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

【问题讨论】:

    标签: .net cryptography aes encryption rijndaelmanaged


    【解决方案1】:

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

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

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

    【讨论】:

    • FlushFinalBlock() 在处理 CryptoStream 时调用,这会通过 using 语句自动发生
    • 也许他们修复了这个缺陷,但 CryptoStream.Dispose 确实曾经没有调用 FlushFinalBlock! bytes.com/topic/c-sharp/answers/…
    • 看起来他们在 .NET 2.0 中修复了它,但我绝对建议养成自己刷新最后一个块的习惯。
    • 好吧,不知何故,我添加了 FlushFinalBlock 语句,这有助于我在解密时摆脱无效填充错误。
    • 在 using 块之外显式添加 FlushFinalBlock 也解决了我在 .NET 4.5 上的问题......
    【解决方案2】:

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

    或者,在您的情况下,我建议您确保为每个加密和解密操作创建和处置RijndaelManagedTransform 的实例,并使用密钥和向量对其进行初始化。这个问题很可能是重用这个transform对象造成的,也就是说第一次使用后,它就不再是正确的初始状态了。

    【讨论】:

    • 呃,对 cme​​ts 很抱歉,这是另一个 SO 问题的“借用代码”。 “它是从哪里来的”字符串还是代码?
    • 如果是这种情况,为什么它会在本地环境中工作?不质疑你的答案,只是看起来很奇怪
    • 字符串是来自图像库的图像代码
    • 好的,但是这些的编码是什么?这是来自上面代码的加密字符串,然后您尝试使用上面的代码解密?只是需要更多的上下文。
    • 这里的更新是不正确的,根本不正确的是“在不同的服务器上运行时加密的结果不同”。对称加密的要点是,当给定相同的源文本、密钥和 IV 供应时,加密/解密算法会产生相同的结果。对于不同的 .NET 配置,默认值肯定会有所不同 - 检查两台服务器上的 RijndaelManaged 实例的 Padding 属性。还要验证您的所有编码是否匹配,因此在传递加密和编码字符串时不会丢失数据。
    【解决方案3】:

    这会导致无法在 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);
    }
    

    这样,当 QueryString 为 Base64 编码时,您无需替换任何字符。

    【讨论】:

    • 这是因为我需要将加密字符串作为查询字符串参数传递。我正在使用 ToBase64String 并在字符“/”、“=”和其他字符上进行字符串替换,这让我现在无法理解,长数字格式似乎更简单
    • 在查询字符串中使用结果时,您不需要进行任何替换。我已经使用 ToBase64String 多年,在查询字符串中没有任何问题。
    • 嗯,我不明白如何在查询字符串值中传递和 =、+ 或 / 字符而不破坏 url
    • URL 和 QueryString 解析器很聪明。它们的工作方式与上面的示例类似。如果 / 或 + 在 ?那么它被视为QueryString 的一部分。 KeyValuePair 字符串中只能有一个 =-sign,即第一个。其余的被视为价值的一部分。
    猜你喜欢
    • 2014-06-17
    • 1970-01-01
    • 1970-01-01
    • 2021-03-15
    • 1970-01-01
    • 2012-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多