为什么在加密字符串时得到空结果,即使我刷新了输入流?

Why do I get an empty result when encrypting a string, even though I flushed the input stream?

我想通过 RijndaelManaged 加密一些字符串并得到加密结果。我想用 MemoryStream 来做。但我得到空字符串。如果我使用 Rijndael class 而不是 RijndaelManaged,我会遇到同样的问题。我做错了什么?

static string EncodeString(string text, byte[] key, byte[] iv) {
    RijndaelManaged alg = new RijndaelManaged();
    var encryptor = alg.CreateEncryptor(key, iv);
    string encodedString = null;
    using (var s = new MemoryStream())
    using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
    using (var sw = new StreamWriter(cs)) {
        // encrypt the string
        sw.Write(text);
        sw.Flush();
        cs.Flush();
        s.Flush();
        Console.WriteLine($"Stream position: {s.Position}"); // Oops... It is 0 still. Why?
        // get encrypted string
        var sr = new StreamReader(s);
        s.Position = 0;
        encodedString = sr.ReadToEnd(); // I get empty string here
    }
    return encodedString;
}

那我就用这个方法:

    RijndaelManaged alg = new RijndaelManaged();
    alg.GenerateKey();
    alg.GenerateIV();
    var encodedString = EncodeString("Hello, Dev!", alg.Key, alg.IV); // I get empty string. Why?

这里有两个问题:


问题 1:您尝试在结果准备好之前读取它。您需要先关闭 StreamWriter:

using (var s = new MemoryStream())
using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
using (var sw = new StreamWriter(cs)) {
    // encrypt the string
    sw.Write(text);
    Console.WriteLine(s.ToArray().Length); // prints 0
    sw.Close();
    Console.WriteLine(s.ToArray().Length); // prints 16
    ...
}

但是我为什么需要这个?你没看到我的代码中所有那些 Flush 语句吗? 是的,但 Rijndael 是一个分组密码。它只能在读取完整块后加密块(或者您已经告诉它这是最后一个部分块)。 Flush 允许将更多数据写入流,因此加密器无法确定块是否完整。

您可以通过明确告诉加密流您已完成发送输入来解决此问题。 reference implementation 通过使用嵌套的 using 语句关闭 StreamWriter(以及 CryptoStream)来实现这一点。一旦 CryptoStream 关​​闭,它就会刷新最后一个块。

using (var s = new MemoryStream())
using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
{ 
    using (var sw = new StreamWriter(cs))
    {
        // encrypt the string
        sw.Write(text);
    }
    Console.WriteLine(s.ToArray().Length); // prints 16
    ...
}

或者,正如 Jimi 在评论中提到的,您可以显式调用 FlushFinalBlock。此外,您可以通过将基本字符串显式转换为字节数组来跳过 StreamWriter:

using (var s = new MemoryStream())
using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
{
    cs.Write(Encoding.UTF8.GetBytes(text));
    cs.FlushFinalBlock();
    Console.WriteLine(s.ToArray().Length); // prints 16
    ...
}

或者,正如 V.Lorz 在评论中提到的,您可以只配置 CryptoStream 以隐式调用 FlushFinalBlock:

using (var s = new MemoryStream())
{
    using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
    {
        cs.Write(Encoding.UTF8.GetBytes(text));
    }
    Console.WriteLine(s.ToArray().Length); // prints 16
    ...
}

问题 2:您试图以字符串形式读取结果。 加密不适用于字符串,它适用于字节数组。因此,尝试将结果读取为 UTF-8 字符串将导致垃圾。

相反,您可以使用结果字节数组的 Base64 表示形式:

return Convert.ToBase64String(s.ToArray());

以下是您的代码的工作小提琴,应用了所有这些修复:

  1. 使用 StreamWriter:https://dotnetfiddle.net/8kGI4N
  2. 没有 StreamWriter:https://dotnetfiddle.net/Nno0DF