Search code examples
c#rijndaelmanaged

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


I want to encrypt some string through RijndaelManaged and get encrypted result. I want to do it with MemoryStream. But I get the empty string. I get the same problem if I use Rijndael class instead of RijndaelManaged. What I did wrong?

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;
}

Then I use this method:

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

Solution

  • You have two issues here:


    Problem 1: You try to read the result before it is ready. You need to close the StreamWriter first:

    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
        ...
    }
    

    But why do I need this? Didn't you see all those Flush statements in my code? Yes, but Rijndael is a block cypher. It can only encrypt a block once it has read the full block (or you have told it that this was the final partial block). Flush allows further data to be written to the stream, so the encryptor cannot be sure that the block is complete.

    You can solve this by explicitly telling the crypto stream that you are done sending input. The reference implementation does this by closing the StreamWriter (and, thus the CryptoStream) with a nested using statement. As soon as the CryptoStream is closed, it flushes the final block.

    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
        ...
    }
    

    Alternatively, as mentioned by Jimi in the comments, you can call FlushFinalBlock explicitly. In addition, you can skip the StreamWriter by explicitly converting your base string to a byte array:

    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
        ...
    }
    

    Or, as mentioned by V.Lorz in the comments, you can just dispose the CryptoStream to call FlushFinalBlock implicitly:

    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
        ...
    }
    

    Problem 2: You tried to read the result as a string. Encryption does not work on strings, it works on byte arrays. Thus, trying to read the result as an UTF-8 string will result in garbage.

    Instead, you could, for example, use a Base64 representation of the resulting byte array:

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

    Here are working fiddles of your code with all those fixes applied:

    1. With StreamWriter: https://dotnetfiddle.net/8kGI4N
    2. Without StreamWriter: https://dotnetfiddle.net/Nno0DF