填充无效且不能删除?

我已经在网上寻找这个异常对我的程序意味着什么,但似乎找不到一个解决方案或原因为什么它发生在我的具体程序。我一直在使用提供给我的 msdn 的示例,使用 Rijndael 算法对 XmlDocument 进行加密和解密。加密工作正常,但是当我试图解密时,我得到了下面的例外:

填充无效,无法删除

有人能告诉我怎样才能解决这个问题吗?下面的代码是我获取密钥和其他数据的地方。如果 crypMode 为 false,它将调用 deccrypt 方法,这是发生异常的地方:

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


}
292344 次浏览

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

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

确保用于 加密解密的键是 一样。即使没有显式设置填充方法,也应该允许正确的解密/加密(如果没有设置,它们将是相同的)。但是,如果出于某种原因,您使用的解密密钥与加密密钥不同,那么 威尔会得到以下错误:

填充无效,无法删除

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

为了便于人们搜索,可能值得检查正在解密的输入。在我的例子中,为解密而发送的信息是(错误地)以空字符串的形式输入的。这导致了填充错误。

这可能与 Rossum 公司的回答有关,但值得一提。

另一个场景,也是为了人们搜索的好处。

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

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

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

对我来说,解决的办法是

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

就像我说的,我的填充错误是因为我用记事本手动输入解密文本。也许我的答案会引导你找到解决方法。

打了几次架,我终于解决了这个问题。
(注意: 我使用标准 AES 作为对称算法。这个答案可能不适合 对每个人来说。)

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

下面是一个列表,你可以检查一下你可能遗漏了哪些论点:

  • 钥匙
    (字节数组,对于不同的密钥大小,长度必须正好是16、24、32字节之一。)

  • (字节数组,16字节)
  • 密码模式
    (CBC,CFB,CTS,ECB,OFB 之一)
  • PaddingMode
    (ANSIX923,ISO10126,无,PKCS7,零之一)

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

在 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 方法的调用将引发 CryptograpicException 异常(消息: “填充无效,无法删除”)。

这可能适用于所有衍生自 Symmetic松散算法(Aes,DES,RC2,Rijndael,TripleDES)的加密算法,尽管我刚刚证实了这一点,对于层管理和作为输出流的 MemoryStream。

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

您不需要以编程方式执行任何填充,也不需要选择另一个填充属性值。填充是 FlushFinalBlock 方法作业。

.........

凯文的补充说明:

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

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

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

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

修复我的问题的解决方案是,我无意中对加密和解密方法应用了不同的密钥。

我在尝试向 Decrypt 方法传递未加密的文件路径时遇到了这个错误。解决方案是在尝试解密之前首先检查传递的文件是否已加密

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

我也犯了同样的错误。在我的例子中,这是因为我将加密的数据存储在一个 SQL 数据库中。存储数据的表具有二进制(1000)数据类型。当从数据库中检索数据时,它将解密这1000个字节,而实际上有400个字节。所以从结果中去掉后面的零(600)就解决了这个问题。

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

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

我在尝试将 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 # 等等的人有所帮助。

在将传统的 using块重构为 使用声明样式的新 C # 8.0时,我发现这是一个回归 bug,当变量在方法结束时脱离作用域时,使用声明样式的新 C # 8.0块就结束了。

老式:

//...
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样式编写加密和解密方法时,我必须手动刷新最后一个块。

这将解决问题:

aes.Padding = PaddingMode.Zeros;

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

我在测试并发性问题时看到了这一点,并搞砸了我的测试平台。我为每个转换(加密/解密)创建了一个 AES 类的新实例,但没有设置密钥,在试图解密结果时抛出了这个实例。