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

发布于 2024-08-18 18:25:59 字数 5250 浏览 10 评论 0原文

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

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

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

错误消息是

System.Security.Cryptography.CryptographyException:填充无效且无法删除。 在 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;
        }

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

I know other questions have been asked on this but none so far have provided a solution or are exactly the issue I have.

The class below handles the encryption and decryption of strings, the key and vector passed in are ALWAYS the same.

The strings being encrypted and decrypted are always numbers, most work but the occasional one fails when decrypting (but only on the production server). I should mention that both local and production environments are in IIS6 on Windows Server 2003, the code that uses the class sits in a .ashx handler. The example that fails on the production server is "0000232668"

The error message is

System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.
at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)

And for the code

 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: Thankyou for all of your help however your answers did not un-cover the problem, which turned out to be something stupidly simple. I was generating an encrypted string on one server and handing it over to a handler on another server for decrpytion and processing, but it turns out that the results of encryption differ when run on different servers, hence the receiving server could not decrypt it. One of the answers stumbled across the hint at this by accident, which is why I accepted it

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

素衣风尘叹 2024-08-25 18:25:59

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

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

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

I tend to explicitly call the FlushFinalBlock method on CryptoStream before closing it. That would mean doing the following in your encrypt method:

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

If you don't do this, it may be that the encrypted data is being truncated - this would result in an "invalid padding" scenario. Padding is always present when using PKCS7, even if the data being encrypted is aligned to the block length of the cipher.

带刺的爱情 2024-08-25 18:25:59

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

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

You will sometimes get a message about invalid padding when encryption and decryption for whatever reason have not used the same key or initialisation vector. Padding is a number of bytes added to the end of your plaintext to make it up to a full number of blocks for the cipher to work on. In PKCS7 padding each byte is equal to the number of bytes added, so it can always be removed after decryption. Your decryption has led to a string where the last n bytes are not equal to the value n of the last byte (hope that sentence makes sense). So I would double check all your keys.

Alternatively, in your case, I would suggest making sure that you create and dispose an instance of RijndaelManagedTransform for each encryption and decryption operation, initialising it with the key and vector. This problem could very well be caused by reusing this transform object, which means that after the first use, it is no longer in the right initial state.

这会导致字符值无法在 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 解析,那么您需要确保仅在第一个 = 符号上进行拆分。

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 编码时,您无需替换其中的任何字符。

this results in character values that cannot be passed in a URL

Is there reason why you are using your own encoding, StrToByteArray, instead of Base64 encoding?

If you make these changes:

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

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

then things should work a lot better.

Edit:
Regarding problem with ToBase64String and QueryString:
If you do your own QueryString parsing then you need to make sure you only Split on the 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);
}

This way you don't need to replace any characters in your QueryString when it's Base64 encoded.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文