C#Rijndael解密返回额外的问号字符

时间:2015-01-30 06:06:38

标签: c# encryption cryptography rijndaelmanaged

我正在课堂上组织一些非常基本的对称加密/解密代码以供将来使用。我能够成功地加密和解密...只有一个小问题。

这是我的代码,它从流中读入并解密到另一个流:

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()始终返回相同的值。

这是我的测试文件。

hello
world
this
is
a
word
list

当我尝试从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然后将bytes-converted-to-chars数组加载到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.

整个过程有什么问题?

2 个答案:

答案 0 :(得分:6)

问号是UTF8的BOM(字节顺序标记)是0xef 0xbb 0xbf 这些字节写在以UTF8编码的文件的开头。

因为FileStream将文件作为字节读取并且没有将其作为UTF8文本文件进行解释,所以BOM包含在您的加密中,因此如果您将其解密并将其保存到文件,它在TextEditor中看起来很好但是如果您将其转储到控制台BOM中还打印因为控制台不知道它是某种控制序列/标记

编辑: 以下是获取没有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;
    }

答案 1 :(得分:3)

一些问题:

  • 密钥和IV是固定长度的二进制序列,可以包含任意字节,因此UTF-8不能正确。

    从名为pw的变量中复制密钥,可能是“密码”的缩写,表示存在相关的混淆。密码不是密钥。您应该使用密钥派生函数,最好是专门用于密码散列的函数,如PBKDF2或scrypt。

  • 使用固定的IV错过了IV的全部要点。您需要为每条消息使用新的随机值。这不是秘密,所以只需将它放在密文之前。

  • 如果没有MAC,你就会受到主动攻击,包括填充神谕。
  • 使用Stream.CopyTo在两个流之间复制数据。