填充无效,无法删除?


124

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

填充无效,无法删除

谁能告诉我该如何解决这个问题?下面的代码是我获取密钥和其他数据的地方。如果cryptoMode为false,它将调用解密方法,这是发生异常的地方:

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);

}

12
您是否可以通过将填充模式明确设置为在加密和解密上相同的方式来尝试一下?例如:alg.Padding = PaddingMode.NONE;
NetSquirrel 2011年

Encrypt()方法是什么样的?
csharptest.net 2011年

1
谢谢你们的工作。
Brown Love

2
@NetSquirrel:谢谢您提醒PaddingMode.NONE。这使我摆脱了这个错误的(到另一个)...在Java和C#做AES,现在不知道为什么C#抱怨Java的填充,虽然两者使用PKCS#7
晃龙

Answers:


80

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

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


7
如何显式设置填充?
艾哈迈德·哈贾尔

7
谢谢,我找到了它rj.Padding = PaddingMode.none; :)
艾哈迈德·哈贾尔

7
@AhmadHajjar没有填充会带来安全隐患,请不要使用它。
deviantfan

1
嗨,我明确地设置了填充,但是没有用。我不知道我做错了什么步骤。请帮忙。alg.Padding = PaddingMode.PKCS7;
约翰尼

20
我意识到这是一个旧线程。但是,对于那些访问者,请确保在加密数据时刷新最后一块。
Markus

51

确保密钥您用来加密解密相同的。即使未明确设置填充方法,也应允许正确的解密/加密(如果未设置,则它们将是相同的)。但是,如果由于某种原因您使用的解密密钥集与加密使用的密钥集不同,则会出现以下错误:

填充无效,无法删除

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


该技巧非常有用,因为有时密钥存储在app.config中,我们必须始终确保用于加密的密钥与用于解密的密钥相同。
马里奥Meyrelles

@atconway你介意看看我的问题吗?我有一个类似的问题,但在C ++ / CLI中:stackoverflow.com/questions/57139447/…–
简单

我建议在日常使用中,这可能是人们遇到此错误的最可能原因。尤其是在您不弄乱填充设置的情况下。

28

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

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


我同意,我也是一样,在进行其他检查之前检查输入是否已解密。我得到的比我想要的要多1个字节...
Andrea Antonangeli 2014年

空字符串也是我的罪魁祸首。
dotNET

我的情况是密码短语没有设置(是的,我知道),但是这个答案使我朝着正确的方向前进。
吉姆(Jim)

2
我的问题是要解密的字符串在尝试解密之前已转换为小写。我一直对填充和密码以及所有这些东西感到痴迷,但事实证明,这只是糟糕的输入。有时您只需要退后一步!
Tom Gerken

15

如果将相同的密钥和初始化向量用于编码和解码,则此问题不是来自数据解码,而是来自数据编码。

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

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

对于所有从SymmetricAlgorithm派生的加密算法(Aes,DES,RC2,Rijndael,TripleDES)来说,这可能都是正确的,尽管我刚刚验证了AesManaged和MemoryStream作为输出Stream的情况。

因此,如果在解密时收到此CryptographicException异常,请在写入要加密的数据后读取输出Stream Length属性值,然后调用FlushFinalBlock并再次读取其值。如果已更改,则说明调用FlushFinalBlock不是可选的。

而且,您无需以编程方式执行任何填充,也无需选择另一个“填充”属性值。填充是FlushFinalBlock方法的工作。

......

凯文的补充说明:

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

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

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


1
你为什么说错了?Stream.Close()调用代码this.Dispose(true)。的代码CryptoStream.Dispose(bool)是:if (disposing) { if (!this._finalBlockTransformed) { this.FlushFinalBlock(); } this._stream.Close(); }
Kevin Doyon

1
这解决了我的问题。正如您所说的那样,我正确地处理了cryptoStream,但是dispose调用发生得“太迟了”。如所描述的,这导致了“无效的填充”错误。通过添加cryptoStream.FlushFinalBlock(),解决了无效的填充错误。谢谢!
丹尼尔·兰伯特

13

经过几次奴役的战斗,我终于解决了问题。
(注意:我使用标准AES作为对称算法。此答案可能并不适合所有人。)

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

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

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

3
没有明确设置KeySize立即为我修复。哦,.NET的怪癖:-(
约翰·

请注意,这似乎是.NET Framework本身的回归。我有用于RijndaelManaged的代码,但已停止工作,只需将其更改为AesManaged / AesCryptoServiceProvider,它即可再次工作。我什至没有任何代码明确设置KeySize。因此,如果您对此感到不满意,请感觉更好-错误可能不在于您,而在于.NET Framework本身。
Usas

6

我的问题是加密的passPhrase与解密的passPhrase不匹配...因此它引发了此错误..有点误导。


的确,我们使用PaddingMode.PKCS7进行加密和解密,但这是相同的错误消息。此外,我们还有具有不同键值的Stage和Dev环境。当我使用适当的-environment-specific-key时,此异常已解决...
Major Major

尽管以上所有答案都不错,并且您必须对Encrypt和Decrypt使用相同的填充(不建议使用任何一个!)实际上,该答案也可能是正确的。当我使用适当的-environment特定的-键入例外“ System.Security.Cryptography.CryptographicException:填充无效,无法删除。” 解决了。所以是的,这可能会产生误导。
主要

如果通过“ passPhrase”正在谈论要加密/解密的确切值(不是使用错误密钥的问题),那么是的,这是我的问题。我的情况是原始加密值的长度超出了我的数据库表字段允许的长度,因此在没有意识到的情况下它被截断以适合。然后,在解密该截断的值时会引发此异常。
David Gunderson

2

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


1

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

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

1
对于此解决方案的有效性,我会向任何“命中注定”的胆小鬼提出质疑。
有用蜜蜂

+1是因为在您解密两次或解密未加密的内容时会引发此异常。因此,我将此答案读为“您确定数据实际上已加密吗?”。
Gerardo Grignoli

0

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

对我来说,此错误是在Dispose()方法期间发生的,该方法掩盖了与加密无关的先前错误。

一旦修复了其他组件,该异常便消失了。


3
与加密无关的先前错误是什么?
NStuke 2015年

0

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

我的解决方案是放置一个

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

就像我说的那样,我的填充错误是因为我正在使用记事本手动键入解密后的文本。可能是我的答案,可能会指导您找到解决方案。


0

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


0

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

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


0

我在尝试将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)

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

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


0

客户端报告了相同的错误。我个人无法对其进行复制。查看EncryptDecrypt方法的代码,都将Padding设置为PaddingMode.PKCS7解密看起来像这样,我看不到有关“ FlushFinalBlock ”的问题。有人可以说明一下吗?

public string Decrypt(string cipherText)
{
  if (string.IsNullOrEmpty(cipherText))
    return "";
  string result;
  Encoding byteEncoder = Encoding.Default;

  byte[] rijnKey = byteEncoder.GetBytes(Password);
  byte[] rijnIv = byteEncoder.GetBytes(InitialVector);
  RijndaelManaged rijn = new RijndaelManaged { Mode = CipherMode.ECB, Padding = PaddingMode.PKCS7 };

  using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(cipherText)))
  {
    using (ICryptoTransform decryptor = rijn.CreateDecryptor(rijnKey, rijnIv))
    {
      using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
      {
                    using (StreamReader swDecrypt = new StreamReader(csDecrypt))
                    {
                        result = swDecrypt.ReadToEnd();
                    }
                }
    }
  }
  rijn.Clear();      
  return result.Replace("\0", "");
}

0

我有同样的错误。在我的情况下,给定的密码大于16表示已加密,但是在解密时却出现此错误。加密:

string keyString = "CDFUYP@ssw0rd123";
            var key = Encoding.UTF8.GetBytes(keyString);            
            using (var aesAlg = Aes.Create())
            {
                using (var encryptor = aesAlg.CreateEncryptor(key, aesAlg.IV))
                {
                    using (var msEncrypt = new MemoryStream())
                    {
                        using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                        using (var swEncrypt = new StreamWriter(csEncrypt))
                        {
                            swEncrypt.Write(text);
                        }                          
                        var iv = aesAlg.IV;

                        var decryptedContent = msEncrypt.ToArray();

                        var result = new byte[iv.Length + decryptedContent.Length];

                        Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
                        Buffer.BlockCopy(decryptedContent, 0, result, iv.Length, decryptedContent.Length);

                        var encryptedString = Convert.ToBase64String(result);
                        var decryptedString = Decrypt(encryptedString);
                        if (decryptedString == null)
                        {
                            return null;
                        }
                        return encryptedString;

                    }
                }

解密:

 string keyString = "CDFUYP@ssw0rd123";
            var fullCipher = Convert.FromBase64String(cipherText);
            var iv = new byte[16];
            var cipher = new byte[16];
            Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length);
            Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, iv.Length);
            var key = Encoding.UTF8.GetBytes(keyString);

            using (var aesAlg = Aes.Create())
            {
                using (var decryptor = aesAlg.CreateDecryptor(key, iv))
                {
                    string result;
                    using (var msDecrypt = new MemoryStream(cipher))
                    {
                        using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                        {
                            using (var srDecrypt = new StreamReader(csDecrypt))
                            {
                                result = srDecrypt.ReadToEnd();
                            }
                        }
                    }

                    return result;
                }
            }

嗨@sundarraj,这是一个问题吗?
Tiago Martins Peres李大仁19/12/19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.