为什么我的解密方法会引发"Length of the data to decrypt is invalid"加密异常



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

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

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

加密端:

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

…而另一面


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

在我看来,最简单的方法是遵循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();
    }
    
  • 解密:

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

使用这个构造,Close(),FlushFinalBlock()等的调用通过隐式的Dispose()调用以正确的顺序触发(详细信息参见这里)。

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


还要记住以下内容:

  • 如果你以后unicode解码解密的数据(这是可能的),它是更有效的返回字符串而不是Decrypt()中的byte[](即return reader.ReadToEnd())。
  • 关于填充,PKCS7填充应该使用而不是零填充,因为pkcs# 7填充更可靠(相对于你在代码中的注释)。
  • 使用StreamReader#.ReadToEnd()可以防止与。net 6中破坏性更改相关的问题。

您需要在Encrypt方法中编码纯文本,并在.Write中编码结果而不是纯文本。即ICryptoTransform处理并返回加密后的输出字节数组的输入字节数组。

我建议去掉StreamWriter,直接写CryptoStream

这是一个建议的修复。

static byte[] Encrypt<T>(string plainText, string password, string salt)
where T : SymmetricAlgorithm, new()
{
var plainTextBytes = Encoding.Unicode.GetBytes(plainText);
var saltBytes = Encoding.Unicode.GetBytes(salt);
var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes);
using (var algorithm = new T())
{
var key = derivedBytes.GetBytes(algorithm.KeySize >> 3);
byte[] iv = new byte[algorithm.BlockSize >> 3];
RNGCryptoServiceProvider.Create().GetNonZeroBytes(iv);
using (var buffer = new MemoryStream())
using (var cryptoStream = new CryptoStream(buffer, 
algorithm.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
return iv.Concat(buffer.ToArray()).ToArray();

}
}
}
// Return byte array if you need that and let the caller decode it.
static string Decrypt<T>(byte[] encryptedData, string password, string salt)
where T : SymmetricAlgorithm, new()
{
var saltBytes = Encoding.Unicode.GetBytes(salt);
var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes);
using (var algorithm = new T())
{
var key = derivedBytes.GetBytes(algorithm.KeySize >> 3);
var iv = encryptedData.Take(algorithm.BlockSize >> 3).ToArray();
encryptedData = encryptedData.Skip(iv.Length).ToArray();
using (var buffer = new MemoryStream())
using (var stream = new CryptoStream(buffer, 
algorithm.CreateDecryptor(key, iv), CryptoStreamMode.Write))
{
stream.Write(encryptedData, 0, encryptedData.Length);
stream.FlushFinalBlock();
return Encoding.Unicode.GetString(buffer.ToArray());
}
}            
}

…在一些来电者中:

var filename = "test.encrypted";
var plainText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";
var password = "A better password than this";
var salt = "Sodium Chloride";
var encrypted = Encrypt<AesManaged>(plainText, password, salt);
File.WriteAllBytes(filename, encrypted);
var fileBytes = File.ReadAllBytes(filename);
var decrypted = Decrypt<AesManaged>(fileBytes, password, salt);
Console.WriteLine(decrypted == plainText);

请参阅此处的通用SymmetricCrypto<T>类。

似乎有一个编码/流处理错误。您正在编写密文,就好像它是Unicode。我建议将密文包装在MemoryStream中,然后使用输入CryptoStream,即一个用于读取,然后一个StreamReader用于读取文本。

现在你已经完全颠倒了流,从概念上讲,这是最有意义的。例如,假设您想直接从文件中获取密文。然后可以在读模式下使用FileStream。您还可以决定对明文执行其他操作。当然,如果您首先有一个输入流,您可以简单地先读取IV,然后使用CryptoStream继续读取。

最后,CryptoStream现在可以清楚地发现流何时结束,因此可以执行最终操作,如自动取消填充,解决您的问题。

相关内容

最新更新