代码之家  ›  专栏  ›  技术社区  ›  Felastine

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

  •  3
  • Felastine  · 技术社区  · 10 年前

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

    下面是我的代码,它从一个流中读取,并对另一个流进行加密/解密:

    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中,然后将转换为字符数组的字节加载到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,一切正常。奇怪的是,对于控制台上打印的文本 前面的额外问号:

    Hello world.
    

    整个过程有什么问题?

    2 回复  |  直到 8 年前
        1
  •  6
  •   BoeseB    10 年前

    问号是 BOM UTF8的(字节顺序标记)为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;
        }
    
        2
  •  3
  •   CodesInChaos    10 年前

    一些问题:

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

      从名为 pw ,大概是“password”的缩写,表示相关的混淆。密码不是密钥。您应该使用密钥派生函数,最好是专门用于密码哈希的函数,如PBKDF2或scrypt。

    • 使用固定的IV忽略了IV的全部意义。您需要为每条消息使用一个新的随机值。这不是秘密,所以只需将其放在密文前面。

    • 如果没有MAC,你将面临主动攻击,包括填充预言。
    • 使用 Stream.CopyTo 以在两个流之间复制数据。