为什么我的解密方法抛出“要解密的数据长度无效”加密异常

Sco*_*ker 3 c# encryption encryption-symmetric .net-3.5

这是一个非常常见的异常,但显然我找到的解决方案都没有解决我的问题。

我有一个加密和一个解密方法;我加密一个字符串并将其写入文件,然后从文件中读取该字符串并解密(理论上)。事实上,我得到了一个

CryptographicException:要解密的数据长度无效

在该过程的解密方面。

这是Main()完成所有工作的方法:

public static void Main()
{
    var filename = "test.encrypted";
    var plainText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";

    string password = "A better password than this";
    string salt = "Sodium Chloride";

    var padding = PaddingMode.Zeros; // I have tried every padding mode to no avail

    var encrypted = Encrypt<AesManaged>(plainText, password, salt, padding);
    File.WriteAllBytes(filename, encrypted);

    var fileBytes = File.ReadAllBytes(filename);
    var decrypted = Decrypt<AesManaged>(fileBytes, password, salt, padding);

    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

加密端:

static byte[] Encrypt<T>(string plainText, string password, string salt, PaddingMode padding)
    where T : SymmetricAlgorithm, new()
{
    var saltBytes = Encoding.Unicode.GetBytes(salt);
    var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes);

    using (var algorithm = new T())
    {
        algorithm.Padding = padding;

        var key = derivedBytes.GetBytes(algorithm.KeySize >> 3);
        byte[] iv = new byte[algorithm.BlockSize >> 3];
        RNGCryptoServiceProvider.Create().GetNonZeroBytes(iv);

        var transform = algorithm.CreateEncryptor(key, iv);


        using (MemoryStream buffer = new MemoryStream())
        using (CryptoStream cryptoStream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))
        using (StreamWriter writer = new StreamWriter(cryptoStream, Encoding.Unicode))
        {
            writer.Write(plainText);
            writer.Flush();  
             
            // cryptoStream.FlushFinalBlock() is called after writer.Flush()
            // or as part of the Dispose().
            // Calling it here causes a "you can't call that twice" exception.

            // prepend IV to the data
            return iv.Concat(buffer.ToArray()).ToArray();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

...还有另一面


static byte[] Decrypt<T>(byte[] encryptedData, string password, string salt, PaddingMode padding)
      where T : SymmetricAlgorithm, new()
{
    var saltBytes = Encoding.Unicode.GetBytes(salt);

    var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes);

    using (var algorithm = new T())
    {
        algorithm.Padding = padding;

        var key = derivedBytes.GetBytes(algorithm.KeySize >> 3);

        //IV is at the beginning of the data
        var iv = encryptedData.Take(algorithm.BlockSize >> 3).ToArray();
        encryptedData = encryptedData.Skip(iv.Length).ToArray();

        var transform = algorithm.CreateDecryptor(key, iv);

        using (MemoryStream buffer = new MemoryStream())
        using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))
        using (StreamWriter writer = new StreamWriter(stream, Encoding.Unicode))
        {
            writer.Write(encryptedData);
            writer.Flush();
            return buffer.ToArray();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Top*_*aco 6

我认为最简单的方法是遵循 MS 模式,例如这里

  • 对于加密:

    using (MemoryStream buffer = new MemoryStream())
    using (CryptoStream cryptoStream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))
    {
        using (StreamWriter writer = new StreamWriter(cryptoStream, Encoding.Unicode))
        {
            writer.Write(plainText);
        }
        return iv.Concat(buffer.ToArray()).ToArray();
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 以及解密:

    using (MemoryStream buffer = new MemoryStream(encryptedData))
    using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Read))
    using (StreamReader reader = new StreamReader(stream, Encoding.Unicode))
    {
        return Encoding.Unicode.GetBytes(reader.ReadToEnd());
    }
    
    Run Code Online (Sandbox Code Playgroud)

使用此构造Close()FlushFinalBlock()等的调用是通过按Dispose()正确顺序的隐式调用触发的(有关详细信息,另请参阅此处)。

旧的实现则不然,导致加解密不完整。此外,密文在解密过程中被 Unicode(更准确地说是 UTF-16LE)编码损坏(这是发布异常的原因)。


另请记住以下几点:

  • 如果您稍后对解密的数据进行 Unicode 解码(这很可能),则返回字符串而不是 in byte[]Decrypt()return reader.ReadToEnd())会更有效。
  • 至于填充,应使用 PKCS7 填充而不是零填充,因为 PKCS#7 填充更可靠(相对于代码中的注释)。
  • 使用可以防止与 .NET 6 中的重大更改StreamReader#.ReadToEnd()相关的问题。