我已经在线查看了此异常对我的程序的意义,但似乎无法找到解决方案或其原因发生在我的特定程序中。我一直在使用我的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);
}
答案 0 :(得分:63)
Rijndael / AES是一个块密码。它以128位(16个字符)块加密数据。 Cryptographic padding用于确保消息的最后一个块始终是正确的大小。
您的解密方法预期无论其默认填充是什么,并且找不到它。正如@NetSquirrel所说,您需要为加密和解密显式设置填充。除非您有其他理由,否则请使用PKCS#7填充。
答案 1 :(得分:39)
确保用于加密和解密的密钥相同。填充方法即使没有明确设置,仍应允许正确的解密/加密(如果没有设置它们将是相同的)。但是,如果由于某种原因使用一组不同的密钥进行解密而不是用于加密,则 会出现此错误:
填充无效且无法删除
如果您使用某种算法来动态生成无效的密钥。加密和解密都需要相同。一种常见的方法是让调用者在加密方法类的构造函数中提供密钥,以防止加密/解密过程有任何创建这些项的过程。它侧重于手头的任务(加密和解密数据),并要求呼叫者提供iv
和key
。
答案 2 :(得分:23)
为了搜索人员的利益,可能需要检查被解密的输入。在我的情况下,发送用于解密的信息(错误地)作为空字符串进入。这导致了填充错误。
这可能与rossum的回答有关,但认为值得一提。
答案 3 :(得分:11)
战斗的一段时间,我终于解决了问题 (注意:我使用标准AES作为对称算法。这个答案可能不合适 适合所有人。)
RijndaelManaged
类替换为AESManaged
一个。 KeySize
,将它们保留为默认值
(这是非常重要的一步。我认为KeySize属性存在错误。)这是一个列表,您想要检查可能错过的参数:
答案 4 :(得分:9)
如果使用相同的密钥和初始化向量进行编码和解码,则此问题不是来自数据解码,而是来自数据编码。
在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
如果在写入要加密的数据之后没有在关闭之前调用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。
答案 5 :(得分:3)
我的问题是加密的passPhrase与解密的passPhrase没有匹配......所以它抛出了这个错误..有点误导。
答案 6 :(得分:1)
修复我的解决方案是我无意中将不同的密钥应用于加密和解密方法。
答案 7 :(得分:1)
当将代码从传统的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
样式编写时,我必须手动清除加密和解密方法的最后一块。
答案 8 :(得分:0)
如果您设置了错误的加密密钥并设置了填充模式,也会发生这种情况。
我在测试并发问题并弄乱了我的测试平台时看到了这一点。我在没有设置密钥的情况下为每个转换(加密/解密)创建了一个 AES 类的新实例,当我试图解密结果时,它被抛出。
答案 9 :(得分:0)
这将解决问题:
aes.Padding = PaddingMode.Zeros;
答案 10 :(得分: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;
}
}
答案 11 :(得分:0)
客户端向我报告了相同的错误。我个人无法对其进行复制。查看 Encrypt 和 Decrypt 方法的代码,都将 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", "");
}
答案 12 :(得分: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#等的人有用。
答案 13 :(得分:0)
我遇到此错误,并明确设置了块大小:aesManaged.BlockSize = 128;
一旦我删除了它,它就会起作用。
答案 14 :(得分:0)
我有同样的错误。在我的情况下,这是因为我已将加密数据存储在SQL数据库中。存储数据的表具有二进制(1000)数据类型。当从数据库中检索数据时,它将解密这1000个字节,而实际上有400个字节。因此从结果中删除尾随零(600)就解决了问题。
答案 15 :(得分:0)
我在尝试将未加密的文件路径传递给Decrypt方法时遇到此错误。解决方法是在尝试解密之前检查传递的文件是否先加密
if (Sec.IsFileEncrypted(e.File.FullName))
{
var stream = Sec.Decrypt(e.File.FullName);
}
else
{
// non-encrypted scenario
}
答案 16 :(得分:0)
当我手动编辑文件中的加密字符串(使用记事本)时,我遇到了这个填充错误,因为我想测试如果手动更改加密内容,解密功能将如何表现。
我的解决方案是放置
try
decryption stuff....
catch
inform decryption will not be carried out.
end try
就像我说的填充错误是因为我使用记事本手动输入解密文本。可能是我的回答可能会指导您解决问题。
答案 17 :(得分:0)
另一种情况,再次是为了人们搜索的好处。
对我来说,这个错误发生在Dispose()方法中,它掩盖了与加密无关的先前错误。
一旦另一个组件被修复,这个例外就消失了。