为什么在加密字符串时得到空结果,即使我刷新了输入流?
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());
以下是您的代码的工作小提琴,应用了所有这些修复:
- 使用 StreamWriter:https://dotnetfiddle.net/8kGI4N
- 没有 StreamWriter:https://dotnetfiddle.net/Nno0DF
我想通过 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());
以下是您的代码的工作小提琴,应用了所有这些修复:
- 使用 StreamWriter:https://dotnetfiddle.net/8kGI4N
- 没有 StreamWriter:https://dotnetfiddle.net/Nno0DF