1

Padding is invalid and cannot be removed尝试解密某些数据时出现运行时错误。我已经阅读了我在 stackoverflow 上找到的所有类似线程,并考虑了以下问题:

  1. 给定一个与用于加密的密钥不同的解密密钥可能会带来上述问题。但是,正如您在用于测试目的的源代码中看到的那样,我对密钥进行了硬编码,但仍然出现运行时错误。

  2. 应用程序可能会尝试使用与用于加密的填充模式不同的填充模式进行解密。在加密或解密过程中明确设置模式并不能解决问题。

  3. 有人说您需要使用该FlushFinalBlock方法,以便告诉应用程序将最后的位放入blocksize. 试着把它放在 Write 方法下面,也没有用。

using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
    //Write all data to the stream.
    swEncrypt.Write(plainText);
    csEncrypt.FlushFinalBlock();
}

以下是我的代码片段:

// Encrypt 
private byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
    Key = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };
    IV = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };

    // Check arguments. 
    if (plainText == null || plainText.Length <= 0)
        throw new ArgumentNullException("plainText");
    if (Key == null || Key.Length <= 0)
        throw new ArgumentNullException("Key");
    if (IV == null || IV.Length <= 0)
        throw new ArgumentNullException("Key");

    byte[] encrypted;

    // Create an RijndaelManaged object 
    // with the specified key and IV. 
    using (RijndaelManaged rijAlg = new RijndaelManaged())
    {
        rijAlg.Key = Key;
        rijAlg.IV = IV;
        rijAlg.Padding = PaddingMode.PKCS7;

        // Create a decrytor to perform the stream transform.
        ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

        // Create the streams used for encryption. 
        using (MemoryStream msEncrypt = new MemoryStream())
        {
            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {
                using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                {
                    // Write all data to the stream.
                    swEncrypt.Write(plainText);
                    csEncrypt.FlushFinalBlock();
                }

                encrypted = msEncrypt.ToArray();
                MessageBox.Show("Encrypted str: " + Encoding.UTF8.GetString(encrypted).ToString());
            }
        }
    }

    // Update buffer length
    this.Buflen = encrypted.Length;
    MessageBox.Show("Byte count: " + (Encoding.UTF8.GetByteCount(encrypted.ToString())).ToString());
    MessageBox.Show("Decrypted: " + DecryptStringFromBytes(encrypted, this.Encryption_key, this.Encryption_iv));

    // Return the encrypted bytes from the memory stream. 
    return encrypted;
}

// Decrypt
private string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
    Key = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };
    IV = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };

    UTF8Encoding e = new UTF8Encoding();
    MessageBox.Show("ciperText " + cipherText.Length);
    //cipherText = e.GetBytes(cipherText);
    //MessageBox.Show("ciperText2 Length " + cipherText.Length);

    // Check arguments. 
    if (cipherText == null || cipherText.Length <= 0)
        throw new ArgumentNullException("cipherText");
    if (Key == null || Key.Length <= 0)
        throw new ArgumentNullException("Key");
    if (IV == null || IV.Length <= 0)
        throw new ArgumentNullException("Key");

    // Declare the string used to hold 
    // the decrypted text. 
    string plaintext = null;

    // Create an RijndaelManaged object 
    // with the specified key and IV. 
    using (RijndaelManaged rijAlg = new RijndaelManaged())
    {
        rijAlg.Key = Key;
        rijAlg.IV = IV;
        rijAlg.Padding = PaddingMode.PKCS7;

        // Create a decrytor to perform the stream transform.
        ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);

        // Create the streams used for decryption. 
        using (MemoryStream msDecrypt = new MemoryStream(cipherText))
        {
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                {
                    // Read the decrypted bytes from the decrypting stream 
                    // and place them in a string.
                    if (cipherText.Length != rijAlg.BlockSize)
                    {
                        throw new InvalidOperationException("Invalid cipherText length");
                    }

                    plaintext = srDecrypt.ReadToEnd();

                    csDecrypt.FlushFinalBlock();
                }
            }
        }
    }

    return plaintext;
}

byte[] encoded_message = new byte[16];
encoded_message = EncryptStringToBytes("test", this.Encryption_key,this.Encryption_iv);

从昨天早上开始,我一直在努力让它工作。到目前为止,我发现的解决方案都没有在我的应用程序中起作用。我知道代码可能看起来有点乱,但请不要介意没有使用的部分——一旦我得到这个工作,它肯定需要一些重构。

如您所见,在使用硬编码密钥和 IV 进行测试时,仍然会发生运行时错误,因此传递与用于加密的凭据不同的凭据进行解密不是原因。

任何人都可以提出如何解决这个问题的想法吗?谢谢。

4

1 回答 1

0

我正在为任何试图通过套接字发送加密数据的人写这个线程,因为我在过去 3 天里一直遇到麻烦。如您所知,加密数据可能包含填充字符和特殊符号,它们与通过套接字发送数据包的方式(通常以二进制形式和块形式)混合在一起,可能不会以您期望的方式从另一端出来。您通常会遇到的错误是Padding is invalid and cannot be removed*要解密的数据长度无效 *. 我在编写一个通过 UDP 套接字发送加密 Rijndael 凭据的应用程序时遇到了这个问题。所以通常你发送的不是你得到的,正在添加一个填充字符(或几个类似的),这会导致 rijndael 算法无法识别的 bugos 加密数据(请注意,这也可能在尝试解密时导致任何其他类型的加密,我想它确实如此)。因此,我在下面为您提供我的解决方案,旨在帮助正在发送加密数据并尝试正确执行此操作的任何人,(我并不是说这是唯一的,这是最正确的方法这样做,但这是我发现的工作方式,也是我个人使用的方式):

1) 请记住,大多数加密函数都将字节数组作为参数,并且大多数用于发送数据的套接字函数也这样做,首先我们以字节数组形式获取加密字符串:

byte[] encrypted_string = new byte[lengthofblocksize]; // where lengthofblocksize would be the length in bytes of the blocksize for the used algorithm, for rijndael that usually is 16
//getting the encrypted byte array
encrypted_string = EncryptStringToBytes(messageInPlainText, key, iv); 

其中 messageInPlainText 是您的纯文本消息,key 和 iv 是您的 key 和 iv 参数。

2)发送数据前通过socket发送数据时,加密成base64字符串。假设我们将字符串存储在变量“textdata”中。第一步是 string encrypted_string_base64 = Convert.ToBase64String(textdata);

3)现在你有base64字符串作为字符串。您需要的是在字节数组中获取它,这可以使用 UTF8Encoding GetBytes 方法完成: encrypted_string = enc.GetBytes(encrypted_string_base64.ToString()); 4)然后你准备好通过套接字发送数据,这样你就可以在另一端获得正确的编码二进制数据。sck.Send(encrypted_string);

5)所以另一方面,你通常在二进制数据中得到字符串,假设它在变量recvdata中。您需要做的是将字节数组转换为字符串:

 string recv_textdata = Convert.FromBase64String(recvdata);

但是,在此之前,您可能需要创建一个清除所有不必要的填充和符号的方法:

 UTF8Encoding enc = new UTF8ENcoding();
 string recv_textdata = FormatStringAsBase64(enc.GetString(recvdata)).Trim();
(please scroll to the bottom in order to see the full definition of the above method)

6)您现在拥有的是作为base64字符串的加密数据,您还没有准备好将其传递给您的解密方法,因为您需要从base64解码并将其转换为字节数组,这可以直接使用Convert.FromBase64String 方法(至少我的将字节数组作为参数) - 它以字符串作为参数并返回字节数组作为结果:

byte[] recv_textdata_encrypted_ready = new byte[lengthofblocksize]; //length of blocksize is 16 for rijndael as explained
recv_textdata_encrypted_ready = Convert.FromBase64String(recv_textdata);

7) 现在你准备好传递你的加密字节数组进行解密: string decrypted = DecryptStringFromBytes(recv_textdata_encrypted_ready, this.Encryption_key, this.Encryption_iv);

所以,回到 FormatAsBase64String 的定义以及为什么需要它。当您通过 UDP 套接字传递 base64 编码的字符串时,数据报以块的形式发送,并且可能会添加不必要的填充,这通常只能通过比较您通过套接字发送的数据的 md5 哈希值和您通过socket,或将其写入二进制文件(这是我使用的方式)并使用支持 UTF8 编码(或您在第 5 点中使用的任何其他编码)的​​文本编辑器打开结果)。因此,如果您不使用下面的方法,您发送的数据可能与您接收的数据不同,这将导致带有不必要填充的字符串无法通过 .NET 解密库进行解密,因为它们无法识别它,或者在我的情况下发生了什么,转换。FromBase64String 方法不会将其识别为有效的 base64 编码字符串,并会吐出一个错误,即该字符串包含无效的填充并且不是有效的 base64 编码格式。因此,要将您的字符串验证为有效的 base64,以便在将其转换为通常的二进制加密字节数组时不会遇到问题,您可能需要这样的东西:

    private static string FormatStringAsBase64(string message_encrypted_base64)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < message_encrypted_base64.Length; i++)
        {
            //current char
            char currchar = message_encrypted_base64[i];

            //check if we got a valid base64 symbol
            /* From Microsoft MSDN Official Convert.ToBase64String Method (http://msdn.microsoft.com/en-us/library/dhx0d524.aspx):
             * The base-64 digits in ascending order from zero are the uppercase 
             * characters "A" to "Z", the lowercase characters "a" to "z", the numerals "0" 
             * to "9", and the symbols "+" and "/". 
             * The valueless character, "=", 
             * is used for trailing padding.
             * 
             */
            if (char.IsLetterOrDigit(currchar) || currchar == '=' || currchar == '+' || currchar == '/')
            {
                sb.Append(currchar);
            }
        }
        return sb.ToString();
    }

我正在为任何试图通过 UDP 套接字发送加密的 rijndael 数据的人写这篇文章。我自己花了 3 天时间来解决这个问题(好吧,我可能看起来像个白痴,花了这么长时间才解决这个问题,但是当你有一个混乱的代码,所有的方法都试图找出发生了什么问题时)阅读后一大堆在stackoverflow上有类似错误的线程。他们都没有完全相同的问题,也没有解决方案。因此,如果您尝试通过 UDP 套接字发送加密数据并收到诸如Invalid padding 和 padding cannot be removedLength of data to decrypt is invalid 之类的错误,此线程可能会派上用场。

我相信这个帖子可能会帮助许多其他遇到所描述问题的人,并将为这个论坛上的许多用户节省时间。如果模组不相信或发现线程不必要或不合适,你可以摆脱它,我只是决定抽出一些时间来描述我遇到的问题以及如何解决它,因为它可以节省大量时间其他人,相信我这真的让我很生气,直到我找到了一个显然很容易解决的问题的解决方案。

于 2013-10-11T19:45:13.533 回答