【问题标题】:Padding is invalid and cannot be removed?填充无效,无法删除?
【发布时间】:2012-01-24 20:51:19
【问题描述】:

我已在网上查找此异常与我的程序有关的含义,但似乎找不到解决方案或我的特定程序发生此异常的原因。我一直在使用我的 msdn 提供的示例来使用 Rijndael 算法加密和解密 XmlDocument。加密工作正常,但当我尝试解密时,出现以下异常:

填充无效,无法移除

谁能告诉我我能做些什么来解决这个问题?下面的代码是我获取密钥和其他数据的地方。如果cryptoMode为false,就会调用decrypt方法,也就是异常发生的地方:

public void Cryptography(XmlDocument doc, bool cryptographyMode)
{
    RijndaelManaged key = null;
    try
    {
    // Create a new Rijndael key.
    key = new RijndaelManaged();
    const string passwordBytes = "Password1234"; //password here 

    byte[] saltBytes = Encoding.UTF8.GetBytes("SaltBytes");
    Rfc2898DeriveBytes p = new Rfc2898DeriveBytes(passwordBytes, saltBytes);
    // sizes are devided by 8 because [ 1 byte = 8 bits ] 
    key.IV = p.GetBytes(key.BlockSize/8);
    key.Key = p.GetBytes(key.KeySize/8);

    if (cryptographyMode)
    {
        Ecrypt(doc, "Content", key);
    }
    else
    {
        Decrypt(doc, key);
    }

    }
    catch (Exception ex)
    {
    MessageBox.Show(ex.Message);
    }
    finally
    {
    // Clear the key.
    if (key != null)
    {
        key.Clear();
    }
    }

}

private void Decrypt(XmlDocument doc, SymmetricAlgorithm alg)
{
    // Check the arguments.  
    if (doc == null)
    throw new ArgumentNullException("Doc");
    if (alg == null)
    throw new ArgumentNullException("alg");

    // Find the EncryptedData element in the XmlDocument.
    XmlElement encryptedElement = doc.GetElementsByTagName("EncryptedData")[0] as XmlElement;

    // If the EncryptedData element was not found, throw an exception.
    if (encryptedElement == null)
    {
    throw new XmlException("The EncryptedData element was not found.");
    }


    // Create an EncryptedData object and populate it.
    EncryptedData edElement = new EncryptedData();
    edElement.LoadXml(encryptedElement);

    // Create a new EncryptedXml object.
    EncryptedXml exml = new EncryptedXml();


    // Decrypt the element using the symmetric key.
    byte[] rgbOutput = exml.DecryptData(edElement, alg); <----  I GET THE EXCEPTION HERE
    // Replace the encryptedData element with the plaintext XML element.
    exml.ReplaceData(encryptedElement, rgbOutput);

}

【问题讨论】:

  • 您可以尝试一下吗,将加密和解密的填充模式显式设置为相同,使其相同。例如:alg.Padding = PaddingMode.NONE;
  • Encrypt() 方法是什么样的?
  • 感谢工作的人。
  • @NetSquirrel:感谢您对 PaddingMode.NONE 的提醒。它让我摆脱了这个错误(对另一个错误)......在 Java 和 C# 中执行 AES,现在不知道为什么 C# 抱怨 Java 填充,尽管两者都使用 PKCS#7

标签: c# cryptography


【解决方案1】:

Rijndael/AES 是一种块密码。它以 128 位(16 个字符)块加密数据。 Cryptographic padding 用于确保消息的最后一个块的大小始终正确。

您的解密方法期望它的默认填充是什么,并且没有找到它。正如@NetSquirrel 所说,您需要为加密和解密显式设置填充。除非您有理由不这样做,否则请使用 PKCS#7 填充。

【讨论】:

  • 如何显式设置填充??
  • 谢谢我找到了 rj.Padding=PaddingMode.none; :)
  • @AhmadHajjar 没有填充有安全隐患,不要使用它。
  • 嗨,我明确设置了填充,但不起作用。我不知道我做错了什么步骤。请帮忙。 alg.Padding = PaddingMode.PKCS7;
  • 我意识到这是一个旧线程。但是,对于那些访问的人,请确保在加密数据时刷新最后一个块。
【解决方案2】:

确保您用于加密解密的密钥相同。即使未明确设置填充方法,仍应允许正确解密/加密(如果未设置,它们将相同)。但是,如果您出于某种原因使用不同的密钥集进行解密而不是用于加密,您收到此错误:

填充无效,无法移除

如果您使用某种算法来动态生成不起作用的密钥。对于加密和解密,它们需要相同。一种常见的方法是让调用者在加密方法类的构造函数中提供密钥,以防止加密/解密过程参与创建这些项目。它专注于手头的任务(加密和解密数据),并要求调用者提供ivkey

【讨论】:

  • 这个技巧非常有用,因为有时密钥存储在 app.config 中,我们必须始终确保用于加密的密钥与用于解密的密钥相同。
  • 我建议在日常使用中,这可能是人们遇到此错误的最可能原因。特别是如果您没有弄乱填充设置。
【解决方案3】:

为了人们搜索的利益,检查被解密的输入可能是值得的。在我的情况下,发送用于解密的信息(错误地)作为空字符串输入。这导致了填充错误。

这可能与rossum的回答有关,但认为值得一提。

【讨论】:

  • 我同意,发生了同样的事情,在进行其他检查之前检查输入被解密。我得到的比我加密的多 1 个字节......
  • 一个空字符串也是我的罪魁祸首。
  • 我的情况是未设置密码(是的,我知道),但这个答案让我找到了正确的方向。
  • 我的问题是要解密的字符串在我尝试解密之前被转换为小写。我沉迷于填充和密码以及所有这些东西,但结果证明这只是糟糕的输入。有时你只需要退后一步!
【解决方案4】:

如果编码和解码使用相同的key和初始化向量,这个问题不是来自数据解码,而是来自数据编码。

在 CryptoStream 对象上调用 Write 方法后,必须始终在 Close 方法之前调用 FlushFinalBlock 方法。

有关 CryptoStream.FlushFinalBlock 方法的 MSDN 文档说:
"调用 Close 方法会调用 FlushFinalBlock ..."
https://msdn.microsoft.com/en-US/library/system.security.cryptography.cryptostream.flushfinalblock(v=vs.110).aspx
这是错误的。调用 Close 方法只是关闭 CryptoStream 和输出 Stream。
如果在写入要加密的数据后在 Close 之前未调用 FlushFinalBlock,则在解密数据时,对 CryptoStream 对象的 Read 或 CopyTo 方法的调用将引发 CryptographicException 异常(消息:“填充无效且无法删除”)。

这可能适用于从 SymmetricAlgorithm 派生的所有加密算法(Aes、DES、RC2、Rijndael、TripleDES),尽管我刚刚验证了 AesManaged 和 MemoryStream 作为输出流。

因此,如果您在解密时收到此 CryptographicException 异常,请在写入要加密的数据后读取输出 Stream Length 属性值,然后调用 FlushFinalBlock 并再次读取其值。如果它发生了变化,您就知道调用 FlushFinalBlock 不是可选的。

并且您不需要以编程方式执行任何填充,或选择另一个 Padding 属性值。填充是 FlushFinalBlock 方法的工作。

…………

对凯文的补充说明:

是的,CryptoStream 在调用 Close 之前调用了 FlushFinalBlock,但是为​​时已晚:当调用 CryptoStream Close 方法时,输出流也被关闭了。

如果您的输出流是 MemoryStream,则在关闭后您将无法读取其数据。因此,在使用写入 MemoryStream 的加密数据之前,您需要在 CryptoStream 上调用 FlushFinalBlock。

如果您的输出流是 FileStream,情况会更糟,因为写入是缓冲的。结果是如果在 FileStream 上调用 Flush 之前关闭输出流,最后写入的字节可能不会写入文件。因此,在 CryptoStream 上调用 Close 之前,您首先需要在 CryptoStream 上调用 FlushFinalBlock,然后在 FileStream 上调用 Flush。

【讨论】:

  • 为什么说错了? Stream.Close() 的代码调用 this.Dispose(true)CryptoStream.Dispose(bool) 的代码是:if (disposing) { if (!this._finalBlockTransformed) { this.FlushFinalBlock(); } this._stream.Close(); }
  • 这解决了我的问题。我正确地处理了cryptoStream,但正如你所说,处理调用发生得“太晚了”。如前所述,这导致了“无效填充”错误。通过添加 cryptoStream.FlushFinalBlock(),解决了无效填充错误。谢谢!
  • 但是请注意,如果使用 StreamWriter,您需要先在 streamWriter 上调用 Flush(),然后再在 cryptoStream 上调用 FlushFinalBlock(),至少这是我的经验。
【解决方案5】:

折腾了好几回,终于解决了问题。
(注意:我使用标准 AES 作为对称算法。这个答案可能不适合 适合所有人。)

  1. 更改算法类。将 RijndaelManaged 类替换为 AESManaged 一个。
  2. 不要显式设置算法类的KeySize,保持默认。
    (这是非常重要的一步。我认为 KeySize 属性存在错误。)

这里是您要检查的列表,您可能错过了哪个参数:

  • 钥匙
    (字节数组,对于不同的密钥大小,长度必须恰好是 16、24、32 字节之一。)
  • IV
    (字节数组,16字节)
  • 密码模式
    (CBC、CFB、CTS、ECB、OFB 之一)
  • 填充模式
    (ANSIX923、ISO10126、无、PKCS7、零之一)

【讨论】:

  • 未明确设置 KeySize 立即为我修复了它。哦.NET的怪癖:-(
  • 请注意,这似乎是 .NET Framework 本身的回归。我的代码曾经与 RijndaelManaged 一起工作,但停止工作,只需将其更改为 AesManaged / AesCryptoServiceProvider,它就可以再次工作。我什至没有任何代码明确设置 KeySize。因此,如果您对此感到困扰,请感觉好点 - 问题可能不在于您,而在于 .NET Framework 本身。
【解决方案6】:

在将代码从传统的 using 块重构为 the new C# 8.0 using declaration style 时,我发现这是一个回归错误,其中当变量在方法结束时超出范围时块结束。

旧式:

//...
using (MemoryStream ms = new MemoryStream())
{
    using (CryptoStream cs = new CryptoStream(ms, aesCrypto.CreateDecryptor(), CryptoStreamMode.Write))
    {
        cs.Write(rawCipherText, 0, rawCipherText.Length);
    }

    return Encoding.Unicode.GetString(ms.ToArray());
}

新的更少缩进的样式:

//...
using MemoryStream ms = new MemoryStream();
using CryptoStream cs = new CryptoStream(ms, aesCrypto.CreateDecryptor(), CryptoStreamMode.Write);

cs.Write(rawCipherText, 0, rawCipherText.Length);
cs.FlushFinalBlock();

return Encoding.Unicode.GetString(ms.ToArray());

在旧样式中,CryptoStream 的 using 块终止,并且在 return 语句中读取内存流之前调用了终结器,因此 CryptoStream 被自动刷新。

使用新样式,在调用 CryptoStream 终结器之前读取内存流,因此我必须在从内存流读取之前手动调用 FlushFinalBlock() 以解决此问题。当加密和解密方法以新的using 样式编写时,我不得不手动刷新最后一个块。

【讨论】:

    【解决方案7】:

    我的问题是加密的密码与解密的密码不匹配......所以它抛出了这个错误......有点误导。

    【讨论】:

    • 实际上,我们确实使用 PaddingMode.PKCS7 进行加密和解密,但我收到了相同的错误消息。此外,我们还有具有不同键值的 Stage 和 Dev 环境。当我使用正确的环境特定键时,此异常已解决...
    • 尽管以上所有答案都很好,并且您必须对 Encrypt 和 Decrypt 使用相同的填充(不建议使用任何填充!)实际上这个答案也可以是正确的。当我使用正确的特定环境时,键入异常“System.Security.Cryptography.CryptographicException:填充无效且无法删除”。解决了。所以是的,这可能会产生误导。
    • 如果您通过“passPhrase”谈论加密/解密的确切值(不是使用错误密钥的问题),那么是的,这是我的问题。我的情况是原始加密值比我的数据库表字段允许的长,因此在我没有意识到的情况下它被截断以适应。然后在解密该截断值时引发此异常。
    【解决方案8】:

    解决我的问题是我无意中将不同的密钥应用于加密和解密方法。

    【讨论】:

    • 这解决了我的问题。建议在此处使用更复杂的解决方案之前仔细检查正在使用的密钥。
    【解决方案9】:

    我在尝试将 Go 程序移植到 C# 时遇到了同样的问题。这意味着很多数据已经被 Go 程序加密了。现在必须使用 C# 解密此数据。

    最终的解决方案是PaddingMode.None 或者更确切地说是PaddingMode.Zeros

    Go 中的加密方法:

    import (
        "crypto/aes"
        "crypto/cipher"
        "crypto/sha1"
        "encoding/base64"
        "io/ioutil"
        "log"
    
        "golang.org/x/crypto/pbkdf2"
    )
    
    func decryptFile(filename string, saltBytes []byte, masterPassword []byte) (artifact string) {
    
        const (
            keyLength         int = 256
            rfc2898Iterations int = 6
        )
    
        var (
            encryptedBytesBase64 []byte // The encrypted bytes as base64 chars
            encryptedBytes       []byte // The encrypted bytes
        )
    
        // Load an encrypted file:
        if bytes, bytesErr := ioutil.ReadFile(filename); bytesErr != nil {
            log.Printf("[%s] There was an error while reading the encrypted file: %s\n", filename, bytesErr.Error())
            return
        } else {
            encryptedBytesBase64 = bytes
        }
    
        // Decode base64:
        decodedBytes := make([]byte, len(encryptedBytesBase64))
        if countDecoded, decodedErr := base64.StdEncoding.Decode(decodedBytes, encryptedBytesBase64); decodedErr != nil {
            log.Printf("[%s] An error occur while decoding base64 data: %s\n", filename, decodedErr.Error())
            return
        } else {
            encryptedBytes = decodedBytes[:countDecoded]
        }
    
        // Derive key and vector out of the master password and the salt cf. RFC 2898:
        keyVectorData := pbkdf2.Key(masterPassword, saltBytes, rfc2898Iterations, (keyLength/8)+aes.BlockSize, sha1.New)
        keyBytes := keyVectorData[:keyLength/8]
        vectorBytes := keyVectorData[keyLength/8:]
    
        // Create an AES cipher:
        if aesBlockDecrypter, aesErr := aes.NewCipher(keyBytes); aesErr != nil {
            log.Printf("[%s] Was not possible to create new AES cipher: %s\n", filename, aesErr.Error())
            return
        } else {
    
            // CBC mode always works in whole blocks.
            if len(encryptedBytes)%aes.BlockSize != 0 {
                log.Printf("[%s] The encrypted data's length is not a multiple of the block size.\n", filename)
                return
            }
    
            // Reserve memory for decrypted data. By definition (cf. AES-CBC), it must be the same lenght as the encrypted data:
            decryptedData := make([]byte, len(encryptedBytes))
    
            // Create the decrypter:
            aesDecrypter := cipher.NewCBCDecrypter(aesBlockDecrypter, vectorBytes)
    
            // Decrypt the data:
            aesDecrypter.CryptBlocks(decryptedData, encryptedBytes)
    
            // Cast the decrypted data to string:
            artifact = string(decryptedData)
        }
    
        return
    }
    

    ...和...

    import (
        "crypto/aes"
        "crypto/cipher"
        "crypto/sha1"
        "encoding/base64"
        "github.com/twinj/uuid"
        "golang.org/x/crypto/pbkdf2"
        "io/ioutil"
        "log"
        "math"
        "os"
    )
    
    func encryptFile(filename, artifact string, masterPassword []byte) (status bool) {
    
        const (
            keyLength         int = 256
            rfc2898Iterations int = 6
        )
    
        status = false
        secretBytesDecrypted := []byte(artifact)
    
        // Create new salt:
        saltBytes := uuid.NewV4().Bytes()
    
        // Derive key and vector out of the master password and the salt cf. RFC 2898:
        keyVectorData := pbkdf2.Key(masterPassword, saltBytes, rfc2898Iterations, (keyLength/8)+aes.BlockSize, sha1.New)
        keyBytes := keyVectorData[:keyLength/8]
        vectorBytes := keyVectorData[keyLength/8:]
    
        // Create an AES cipher:
        if aesBlockEncrypter, aesErr := aes.NewCipher(keyBytes); aesErr != nil {
            log.Printf("[%s] Was not possible to create new AES cipher: %s\n", filename, aesErr.Error())
            return
        } else {
    
            // CBC mode always works in whole blocks.
            if len(secretBytesDecrypted)%aes.BlockSize != 0 {
                numberNecessaryBlocks := int(math.Ceil(float64(len(secretBytesDecrypted)) / float64(aes.BlockSize)))
                enhanced := make([]byte, numberNecessaryBlocks*aes.BlockSize)
                copy(enhanced, secretBytesDecrypted)
                secretBytesDecrypted = enhanced
            }
    
            // Reserve memory for encrypted data. By definition (cf. AES-CBC), it must be the same lenght as the plaintext data:
            encryptedData := make([]byte, len(secretBytesDecrypted))
    
            // Create the encrypter:
            aesEncrypter := cipher.NewCBCEncrypter(aesBlockEncrypter, vectorBytes)
    
            // Encrypt the data:
            aesEncrypter.CryptBlocks(encryptedData, secretBytesDecrypted)
    
            // Encode base64:
            encodedBytes := make([]byte, base64.StdEncoding.EncodedLen(len(encryptedData)))
            base64.StdEncoding.Encode(encodedBytes, encryptedData)
    
            // Allocate memory for the final file's content:
            fileContent := make([]byte, len(saltBytes))
            copy(fileContent, saltBytes)
            fileContent = append(fileContent, 10)
            fileContent = append(fileContent, encodedBytes...)
    
            // Write the data into a new file. This ensures, that at least the old version is healthy in case that the
            // computer hangs while writing out the file. After a successfully write operation, the old file could be
            // deleted and the new one could be renamed.
            if writeErr := ioutil.WriteFile(filename+"-update.txt", fileContent, 0644); writeErr != nil {
                log.Printf("[%s] Was not able to write out the updated file: %s\n", filename, writeErr.Error())
                return
            } else {
                if renameErr := os.Rename(filename+"-update.txt", filename); renameErr != nil {
                    log.Printf("[%s] Was not able to rename the updated file: %s\n", fileContent, renameErr.Error())
                } else {
                    status = true
                    return
                }
            }
    
            return
        }
    }
    

    现在,用 C# 解密:

    public static string FromFile(string filename, byte[] saltBytes, string masterPassword)
    {
        var iterations = 6;
        var keyLength = 256;
        var blockSize = 128;
        var result = string.Empty;
        var encryptedBytesBase64 = File.ReadAllBytes(filename);
    
        // bytes -> string:
        var encryptedBytesBase64String = System.Text.Encoding.UTF8.GetString(encryptedBytesBase64);
    
        // Decode base64:
        var encryptedBytes = Convert.FromBase64String(encryptedBytesBase64String);
        var keyVectorObj = new Rfc2898DeriveBytes(masterPassword, saltBytes.Length, iterations);
        keyVectorObj.Salt = saltBytes;
        Span<byte> keyVectorData = keyVectorObj.GetBytes(keyLength / 8 + blockSize / 8);
        var key = keyVectorData.Slice(0, keyLength / 8);
        var iv = keyVectorData.Slice(keyLength / 8);
    
        var aes = Aes.Create();
        aes.Padding = PaddingMode.Zeros;
        // or ... aes.Padding = PaddingMode.None;
        var decryptor = aes.CreateDecryptor(key.ToArray(), iv.ToArray());
        var decryptedString = string.Empty;
    
        using (var memoryStream = new MemoryStream(encryptedBytes))
        {
            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
            {
                using (var reader = new StreamReader(cryptoStream))
                {
                    decryptedString = reader.ReadToEnd();
                }
            }
        }
    
        return result;
    }
    

    如何解释填充问题?就在加密之前,Go 程序检查填充:

    // CBC mode always works in whole blocks.
    if len(secretBytesDecrypted)%aes.BlockSize != 0 {
        numberNecessaryBlocks := int(math.Ceil(float64(len(secretBytesDecrypted)) / float64(aes.BlockSize)))
        enhanced := make([]byte, numberNecessaryBlocks*aes.BlockSize)
        copy(enhanced, secretBytesDecrypted)
        secretBytesDecrypted = enhanced
    }
    

    重要的是:

    enhanced := make([]byte, numberNecessaryBlocks*aes.BlockSize)
    copy(enhanced, secretBytesDecrypted)
    

    创建一个具有适当长度的新数组,因此长度是块大小的倍数。这个新数组用零填充。然后,copy 方法将现有数据复制到其中。确保新数组大于现有数据。因此,数组末尾有零。

    因此,C# 代码可以使用PaddingMode.Zeros。替代 PaddingMode.None 只是忽略任何填充,这也有效。我希望这个答案对必须将代码从 Go 移植到 C# 等的任何人都有帮助。

    【讨论】:

    • 先生,您太棒了。一个精彩的解释。
    【解决方案10】:

    这将解决问题:

    aes.Padding = PaddingMode.Zeros;
    

    【讨论】:

    • 嗨,亚当。您能否简要解释一下这段代码的作用以及为什么它比这个问题已经存在的许多答案更可取?亲切的问候。
    【解决方案11】:

    我在尝试将未加密的文件路径传递给 Decrypt 方法时遇到此错误。解决方案是在尝试解密之前检查传递的文件是否已加密

    if (Sec.IsFileEncrypted(e.File.FullName))
    {
        var stream = Sec.Decrypt(e.File.FullName);
    } 
    else
    {
        // non-encrypted scenario  
    }
    

    【讨论】:

    • 我就这个解决方案的有效性向任何“肇事逃逸”的懦夫提出质疑。
    • +1 因为当您解密两次或解密未加密的内容时会引发此异常。所以我把这个答案读作“你确定数据实际上是加密的吗?”。
    【解决方案12】:

    另一种情况,同样是为了人们搜索的好处。

    对我来说,这个错误发生在 Dispose() 方法中,它掩盖了之前与加密无关的错误。

    一旦其他组件被修复,这个异常就消失了。

    【讨论】:

    • 上一个与加密无关的错误是什么?
    【解决方案13】:

    当我手动编辑文件中的加密字符串(使用记事本)时,我遇到了这个填充错误,因为我想测试如果我的加密内容被手动更改,解密功能将如何表现。

    我的解决方案是放置一个

            try
                decryption stuff....
            catch
                 inform decryption will not be carried out.
            end try
    

    就像我说的我的填充错误是因为我使用记事本手动输入解密的文本。可能是我的回答可能会引导您找到解决方案。

    【讨论】:

      【解决方案14】:

      我有同样的错误。就我而言,这是因为我已将加密数据存储在 SQL 数据库中。存储数据的表具有二进制(1000)数据类型。当从数据库中检索数据时,它会解密这 1000 个字节,而实际上是 400 个字节。因此,从结果中删除尾随零 (600) 即可解决问题。

      【讨论】:

        【解决方案15】:

        我遇到了这个错误,并且明确设置了块大小:aesManaged.BlockSize = 128;

        一旦我删除它,它就起作用了。

        【讨论】:

          【解决方案16】:

          如果您设置了错误的加密密钥并设置了填充模式,也会发生这种情况。

          我在测试并发问题并弄乱了我的测试平台时看到了这一点。我在没有设置密钥的情况下为每个转换(加密/解密)创建了一个新的 AES 类实例,当我尝试解密结果时,这被抛出了。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-07-18
            • 2020-12-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-06-17
            相关资源
            最近更新 更多