C# Rijndael解密returns多出问号字符
C# Rijndael decryption returns extra question mark character
我正在 class 中组织一些非常基本的对称 encryption/decryption 代码以备将来使用。我能够成功加密和解密...只是有一个小问题。
这是我的代码,它从一个流中读入并 en/decrypt 到另一个流:
public void Encrypt(Stream input, Stream output) {
byte[] key = Encoding.UTF8.GetBytes(_pw);
byte[] iv = Encoding.UTF8.GetBytes(GenerateInitVector());
RijndaelManaged rm = new RijndaelManaged();
CryptoStream cs = new CryptoStream(
output,
rm.CreateEncryptor(key, iv),
CryptoStreamMode.Write);
int data;
while ((data = input.ReadByte()) != -1)
cs.WriteByte((byte) data);
cs.Close();
}
public void Decrypt(Stream input, Stream output) {
byte[] key = Encoding.UTF8.GetBytes(_pw);
byte[] iv = Encoding.UTF8.GetBytes(GenerateInitVector());
RijndaelManaged rm = new RijndaelManaged();
CryptoStream cs = new CryptoStream(
input,
rm.CreateDecryptor(key, iv),
CryptoStreamMode.Read);
int data;
while ((data = cs.ReadByte()) != -1)
output.WriteByte((byte) data);
cs.Close();
}
供您参考,方法 GenerateInitVector() 总是 returns 相同的值。
这是我的测试文件。
hello
world
this
is
a
word
list
当我尝试 en/decrypt 从 FileStream 到 FileStream 时,一切正常,使用这些方法:
public void Encrypt(string inputPath, string outputPath) {
FileStream input = new FileStream(inputPath, FileMode.Open);
FileStream output = new FileStream(outputPath, FileMode.Create);
Encrypt(input, output);
output.Close();
input.Close();
}
public void Decrypt(string inputPath, string outputPath) {
FileStream input = new FileStream(inputPath, FileMode.Open);
FileStream output = new FileStream(outputPath, FileMode.Create);
Decrypt(input, output);
output.Close();
input.Close();
}
当我尝试将文件解密到 MemoryStream 中,然后将字节转换为字符的数组加载到 StringBuilder 中,最后将其作为字符串打印到控制台时,我进入了控制台:
?hello
world
this
is
a
word
list
我所有的文字前面都多了一个问号。这是我的解密方法:
public StringBuilder Decrypt(string inputPath) {
FileStream input = new FileStream(inputPath, FileMode.Open);
byte[] buffer = new byte[4096];
MemoryStream output = new MemoryStream(buffer);
Decrypt(input, output);
StringBuilder sb = new StringBuilder(4096);
sb.Append(Encoding.UTF8.GetChars(buffer, 0, (int) output.Position));
input.Close();
output.Close();
return sb;
}
我在这里读过一些关于 BOM 和 C# 的类似内容字符:
Result of RSA encryption/decryption has 3 question marks
因此我再次确认我使用的是UTF-8编码。尽管如此,我还是看到了这个额外的问号。为了完整起见,我还写了这个方法来将StringBuilder中的内容加密到文件中:
public void Encrypt(string outputPath, StringBuilder builder) {
char[] buffer = new char[builder.Length];
builder.CopyTo(0, buffer, 0, builder.Length);
byte[] buf = Encoding.UTF8.GetBytes(buffer);
MemoryStream input = new MemoryStream(buf);
FileStream output = new FileStream(outputPath, FileMode.Create);
Encrypt(input, output);
output.Close();
input.Close();
}
然后我通过做交叉检查:
var builder = new StringBuilder();
builder.Append("Hello world.");
Encrypt("test.txt.enc", builder);
Decrypt("test.txt.enc", "test.txt");
builder = Decrypt("test.txt.enc");
Console.WriteLine(builder.ToString());
对于文件 test.txt,一切正常。奇怪的是,对于打印在控制台上的文本,我在前面得到 NO 额外的问号:
Hello world.
整个过程有什么问题?
问号是UTF8的BOM(Byte Order Mark)是0xef 0xbb 0xbf
这些字节写在以 UTF8 编码的文件的开头。
因为 FileStream 以字节形式读取文件并且不将其解释为 UTF8 文本文件,所以 BOM 包含在您的加密中,因此如果您解密它并将其保存到文件中,它在 TextEditor 中看起来一切正常,但如果您将其转储到控制台还打印了 BOM,因为控制台不知道它是某种控件 sequence/marker
编辑:
这是获取没有BOM的字符串的解决方案。
public static string Decrypt(string inputPath)
{
FileStream input = new FileStream(inputPath, FileMode.Open);
MemoryStream output = new MemoryStream();
Decrypt(input, output);
StreamReader reader = new StreamReader(output, new UTF8Encoding()); //Read with encoding
output.Seek(0, SeekOrigin.Begin); //Set stream Position to beginning
string result = reader.ReadToEnd(); //read to string
reader.Close();
input.Close();
output.Close();
return result;
}
一些问题:
Key和IV是固定长度的二进制序列,可以包含任意字节,所以UTF-8不可能是对的
从名为 pw
的变量复制密钥,大概是 'password' 的缩写,表示相关的混淆。密码不是密钥。您应该使用密钥派生函数,最好是专门用于密码散列的函数,例如 PBKDF2 或 scrypt。
使用固定的 IV 错过了 IV 的全部要点。您需要为每条消息使用一个新的随机值。不是密文,直接放在密文前面就可以了
- 如果没有 MAC,您将容易受到主动攻击,包括填充预言机。
- 使用
Stream.CopyTo
在两个流之间复制数据。
我正在 class 中组织一些非常基本的对称 encryption/decryption 代码以备将来使用。我能够成功加密和解密...只是有一个小问题。
这是我的代码,它从一个流中读入并 en/decrypt 到另一个流:
public void Encrypt(Stream input, Stream output) {
byte[] key = Encoding.UTF8.GetBytes(_pw);
byte[] iv = Encoding.UTF8.GetBytes(GenerateInitVector());
RijndaelManaged rm = new RijndaelManaged();
CryptoStream cs = new CryptoStream(
output,
rm.CreateEncryptor(key, iv),
CryptoStreamMode.Write);
int data;
while ((data = input.ReadByte()) != -1)
cs.WriteByte((byte) data);
cs.Close();
}
public void Decrypt(Stream input, Stream output) {
byte[] key = Encoding.UTF8.GetBytes(_pw);
byte[] iv = Encoding.UTF8.GetBytes(GenerateInitVector());
RijndaelManaged rm = new RijndaelManaged();
CryptoStream cs = new CryptoStream(
input,
rm.CreateDecryptor(key, iv),
CryptoStreamMode.Read);
int data;
while ((data = cs.ReadByte()) != -1)
output.WriteByte((byte) data);
cs.Close();
}
供您参考,方法 GenerateInitVector() 总是 returns 相同的值。
这是我的测试文件。
hello
world
this
is
a
word
list
当我尝试 en/decrypt 从 FileStream 到 FileStream 时,一切正常,使用这些方法:
public void Encrypt(string inputPath, string outputPath) {
FileStream input = new FileStream(inputPath, FileMode.Open);
FileStream output = new FileStream(outputPath, FileMode.Create);
Encrypt(input, output);
output.Close();
input.Close();
}
public void Decrypt(string inputPath, string outputPath) {
FileStream input = new FileStream(inputPath, FileMode.Open);
FileStream output = new FileStream(outputPath, FileMode.Create);
Decrypt(input, output);
output.Close();
input.Close();
}
当我尝试将文件解密到 MemoryStream 中,然后将字节转换为字符的数组加载到 StringBuilder 中,最后将其作为字符串打印到控制台时,我进入了控制台:
?hello
world
this
is
a
word
list
我所有的文字前面都多了一个问号。这是我的解密方法:
public StringBuilder Decrypt(string inputPath) {
FileStream input = new FileStream(inputPath, FileMode.Open);
byte[] buffer = new byte[4096];
MemoryStream output = new MemoryStream(buffer);
Decrypt(input, output);
StringBuilder sb = new StringBuilder(4096);
sb.Append(Encoding.UTF8.GetChars(buffer, 0, (int) output.Position));
input.Close();
output.Close();
return sb;
}
我在这里读过一些关于 BOM 和 C# 的类似内容字符:
Result of RSA encryption/decryption has 3 question marks
因此我再次确认我使用的是UTF-8编码。尽管如此,我还是看到了这个额外的问号。为了完整起见,我还写了这个方法来将StringBuilder中的内容加密到文件中:
public void Encrypt(string outputPath, StringBuilder builder) {
char[] buffer = new char[builder.Length];
builder.CopyTo(0, buffer, 0, builder.Length);
byte[] buf = Encoding.UTF8.GetBytes(buffer);
MemoryStream input = new MemoryStream(buf);
FileStream output = new FileStream(outputPath, FileMode.Create);
Encrypt(input, output);
output.Close();
input.Close();
}
然后我通过做交叉检查:
var builder = new StringBuilder();
builder.Append("Hello world.");
Encrypt("test.txt.enc", builder);
Decrypt("test.txt.enc", "test.txt");
builder = Decrypt("test.txt.enc");
Console.WriteLine(builder.ToString());
对于文件 test.txt,一切正常。奇怪的是,对于打印在控制台上的文本,我在前面得到 NO 额外的问号:
Hello world.
整个过程有什么问题?
问号是UTF8的BOM(Byte Order Mark)是0xef 0xbb 0xbf 这些字节写在以 UTF8 编码的文件的开头。
因为 FileStream 以字节形式读取文件并且不将其解释为 UTF8 文本文件,所以 BOM 包含在您的加密中,因此如果您解密它并将其保存到文件中,它在 TextEditor 中看起来一切正常,但如果您将其转储到控制台还打印了 BOM,因为控制台不知道它是某种控件 sequence/marker
编辑: 这是获取没有BOM的字符串的解决方案。
public static string Decrypt(string inputPath)
{
FileStream input = new FileStream(inputPath, FileMode.Open);
MemoryStream output = new MemoryStream();
Decrypt(input, output);
StreamReader reader = new StreamReader(output, new UTF8Encoding()); //Read with encoding
output.Seek(0, SeekOrigin.Begin); //Set stream Position to beginning
string result = reader.ReadToEnd(); //read to string
reader.Close();
input.Close();
output.Close();
return result;
}
一些问题:
Key和IV是固定长度的二进制序列,可以包含任意字节,所以UTF-8不可能是对的
从名为
pw
的变量复制密钥,大概是 'password' 的缩写,表示相关的混淆。密码不是密钥。您应该使用密钥派生函数,最好是专门用于密码散列的函数,例如 PBKDF2 或 scrypt。使用固定的 IV 错过了 IV 的全部要点。您需要为每条消息使用一个新的随机值。不是密文,直接放在密文前面就可以了
- 如果没有 MAC,您将容易受到主动攻击,包括填充预言机。
- 使用
Stream.CopyTo
在两个流之间复制数据。