填充无效,无法删除?

Bro*_*ove 107 c# cryptography

我已经在线查看了这个异常对我的程序的意义,但似乎无法找到解决方案或者为什么它会发生在我的特定程序中.我一直在使用我的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);

}
Run Code Online (Sandbox Code Playgroud)

ros*_*sum 65

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

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

  • 我意识到这是一个老线程.但是,对于那些访问者,请确保在加密数据时刷新最后一个块. (17认同)
  • 谢谢,我发现它是rj.Padding = PaddingMode.none; :) (7认同)
  • @AhmadHajjar没有填充有安全隐患,请不要使用它. (7认同)
  • 如何明确设置填充? (6认同)
  • @Markus 看来当流关闭时会自动调用flushfinalblock。从“using (CryptoStream csEncrypt...”构造中显式调用它会导致“FlushFinalBlock 方法被调用两次”异常(即使是第一次显式调用)。 (4认同)
  • 嗨,我明确设置了填充,但不起作用。我不知道我做错了什么步骤。请帮忙。`alg.Padding = PaddingMode.PKCS7;` (2认同)
  • 两侧的填充需要相同。您应该明确设置填充两次,一次用于加密,一次用于解密。填充错误还有其他可能的原因。我们需要查看您的代码和确切的错误消息来诊断问题。 (2认同)

atc*_*way 44

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

填充无效,无法删除

如果您使用某种算法来动态生成无效的密钥.加密和解密都需要相同.一种常见的方法是让调用者在加密方法类的构造函数中提供密钥,以防止加密/解密过程有任何创建这些项的过程.它侧重于手头的任务(加密和解密数据),并要求调用者提供ivkey提供.

  • 我建议在日常使用中,这可能是人们遇到此错误的最可能原因。特别是如果您不弄乱填充设置。 (3认同)

Hoc*_*eyJ 25

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

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

  • 我的问题是,在我尝试解密之前,要解密的字符串正在转换为小写.我对填充和密码以及所有这些东西都很着迷,但事实证明这只是糟糕的输入.有时你只需退后一步! (2认同)

fig*_*olu 13

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

在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.

  • 这解决了我的问题。我正确地处理了cryptoStream,但是正如您所说,处理调用“为时已晚”。如上所述,这导致了“无效填充”错误。通过添加cryptoStream.FlushFinalBlock(),无效的填充错误得到解决。谢谢! (3认同)
  • 为什么说是错的?`Stream.Close()` 的代码调用了 `this.Dispose(true)`。`CryptoStream.Dispose(bool)` 的代码是: `if (disposing) { if (!this._finalBlockTransformed) { this.FlushFinalBlock(); } this._stream.Close(); }` (2认同)
  • 但请注意,如果使用 StreamWriter,则需要在对 cryptoStream 调用 FlushFinalBlock() 之前对 StreamWriter 调用 Flush(),至少这是我的经验。 (2认同)

Joh*_*nny 12

战斗的时候,我终于解决了问题.
(注意:我使用标准AES作为对称算法.这个答案可能并不适合所有人.)

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

这是一个列表,您想要检查可能错过的参数:

  • 密钥
    (字节数组,长度必须是16,24,32字节中的一个,用于不同的密钥大小.)
  • IV
    (字节数组,16字节)
  • CipherMode
    (CBC,CFB,CTS,ECB,OFB之一)
  • PaddingMode
    (ANSIX923之一,ISO10126,无,PKCS7,零)

  • 没有明确设置`KeySize`立即为我修复它.哦.NET的怪癖:-( (3认同)

小智 8

这将解决问题:

aes.Padding = PaddingMode.Zeros;
Run Code Online (Sandbox Code Playgroud)

  • 嗨亚当。您能否简要解释一下这段代码的作用以及为什么它比该问题已有的许多答案更可取?亲切的问候。 (6认同)

eck*_*rpa 7

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


Ron*_*kay 5

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


Som*_*ing 5

我在尝试将 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
}
Run Code Online (Sandbox Code Playgroud)

... 和 ...

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
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,用 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;
}
Run Code Online (Sandbox Code Playgroud)

如何解释填充问题?在加密之前,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
}
Run Code Online (Sandbox Code Playgroud)

重要的部分是:

enhanced := make([]byte, numberNecessaryBlocks*aes.BlockSize)
copy(enhanced, secretBytesDecrypted)
Run Code Online (Sandbox Code Playgroud)

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

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


MCa*_*tle 5

我在使用声明样式将代码从传统using块重构到新的 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());
}
Run Code Online (Sandbox Code Playgroud)

新的、缩进较少的样式:

//...
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());
Run Code Online (Sandbox Code Playgroud)

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

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