4

The C# .NET Framework 4.5 code I'm working on is supposed to allow me to transfer text across an encrypted stream to another program. I've created two simple programs to demonstrate my problem. EncryptionTestA is the server, and is meant to run first. EncryptionTestB is the client and is meant to run second. Once EncryptionTestB connects, it transfers the text "hello world" to the other program by passing it through a CryptoStream. At least in theory.

What actually happens is nothing. I confirmed this by watching for data transfer with Wireshark on the internal interface. This code transfers absolutely no data in it's present form. The only way I was able to get it to send "hello world" was to close the StreamWriter on the client side. The problem with this is that it also closes the underlying TCP connection, which I don't want to do.

So, my question: how do I flush the StreamWriter/CryptoStream without closing the underlying TCP connection?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Collections;
using System.Threading;
using System.Security;
using System.Security.Cryptography;
using System.Net;
using System.Net.Sockets;

namespace EncryptionTestA
{
    class Program
    {
        static void Main(string[] args)
        {
            TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1892);
            listener.Start();
            TcpClient client = listener.AcceptTcpClient();
            NetworkStream ns = client.GetStream();

            Rijndael aes = RijndaelManaged.Create();
            byte[] key = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
            byte[] iv = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
            CryptoStream cs = new CryptoStream(ns, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read);

            StreamReader sr = new StreamReader(cs);

            String test = sr.ReadLine();

            Console.Read();

            sr.Close();
            cs.Close();
            ns.Close();
            client.Close();
            listener.Stop();
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Collections;
using System.Threading;
using System.Security;
using System.Security.Cryptography;
using System.Net;
using System.Net.Sockets;

namespace EncryptionTestB
{
    class Program
    {
        static void Main(string[] args)
        {
            TcpClient client = new TcpClient();
            client.Connect(IPAddress.Parse("127.0.0.1"), 1892);
            NetworkStream ns = client.GetStream();

            Rijndael aes = RijndaelManaged.Create();
            byte[] key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
            byte[] iv = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
            CryptoStream cs = new CryptoStream(ns, aes.CreateEncryptor(key, iv), CryptoStreamMode.Write);

            StreamWriter sw = new StreamWriter(cs);

            sw.WriteLine("hello world");
            sw.Flush();
            //sw.Close();

            Console.Read();

            sw.Close();
            cs.Close();
            ns.Close();
            client.Close();
        }
    }
}
pracsec
  • 320
  • 4
  • 8
  • 1
    Use AesManaged, not RijndaelManaged. Aes is the official AES version, Rijndael is the first proof-of-concept version of AES... – mcmonkey4eva Jun 24 '13 at 01:57
  • 1
    Also, if you don't get any better answer, you could lazy-job it and just copy network data to a MemoryStream and make the crypto around that. – mcmonkey4eva Jun 24 '13 at 01:58

4 Answers4

6

I believe the problem is that you're using a block cypher - it always works in blocks, so until you get to the final block where you close the stream (at which point you have a shorter block with padding of some description) nothing can be written to the stream while you've got a partial block.

I strongly suspect that if you try to encrypt something longer than "hello world" - try something of at least 16 characters, and maybe more - you'll find that you get some blocks before closing the stream, but unless you happen to hit a block boundary with the end of your data, you'll still be missing some at the end.

It's not clear what your eventual use case is: if you're trying to send multiple messages of some description on the same stream, I would encourage you to work out a scheme where you encrypt each message separately, and then put all of that data on the communication channel - with a length prefix so you know how big the encrypted message is on the receiving side.

Note that on the receiving side, I'd thoroughly recommend that you avoid using DataAvailable. Just because there isn't data available on the stream right now doesn't mean that you've got to the end of the message... that's why you want the length prefix.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • My eventual case is to send multiple messages on the same NetworkStream. My second post basically allows this, though I will have to make some modifications to pass the message length prefix. – pracsec Jun 29 '13 at 18:29
  • @user2416758: Yes - my post was basically explaining *why* this was necessary, and advising you on how to deal with it. – Jon Skeet Jun 29 '13 at 18:31
  • Would just like to note - cipher.FeedbackSize = 8; and cipher.Mode = CipherMode.CFB; – Joshua W Nov 16 '13 at 07:15
3

You may also use

byte[] mess = Encoding.UTF8.GetBytes("hello world");
cs.Write(mess, 0, mess.Length);
cs.FlushFinalBlock();  //as said in documentation, this method will write everything you have in final block, even if it isn't full

Hope it helps ;).

Max Rudko
  • 51
  • 7
0

Based off of mcmonkey4eva's suggestion, I implemented a solution using MemoryStreams which seems to work; however, I am not completely satisfied with this solution since I more or less don't address the original problem so much as I avoid it.

EncryptionTestA is the application listening for a conneciton, and EncryptionTestB is the application connecting. Once connected EncryptionTestB sends the message "Hello World!" encrypted with AES to EncryptionTestB.

NOTE: Sticking with the Rinjdael class allows for some backwards compatibility, in case the target system is not running a higher version of .NET Framework.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Collections;
using System.Threading;
using System.Security;
using System.Security.Cryptography;
using System.Net;
using System.Net.Sockets;

namespace EncryptionTestA
{
    class Program
    {
        static void Main(string[] args)
        {
            TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1892);
            listener.Start();
            TcpClient client = listener.AcceptTcpClient();
            NetworkStream ns = client.GetStream();

            Rijndael aes = RijndaelManaged.Create();
            byte[] key = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
            byte[] iv = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
            aes.Key = key;
            aes.IV = iv;

            byte[] message = Program.ReadMessage(ns, aes);
            String output = System.Text.Encoding.UTF8.GetString(message);
            Console.WriteLine(output);

            Console.Read();

            ns.Close();
            client.Close();
            listener.Stop();
        }

        static byte[] ReadMessage(NetworkStream stream, Rijndael aes)
        {
            if (stream.CanRead)
            {
                byte[] buffer = new byte[4096];

                MemoryStream ms = new MemoryStream(4096);
                CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write);
                do
                {
                    int length = stream.Read(buffer, 0, buffer.Length);
                    cs.Write(buffer, 0, length);
                } while (stream.DataAvailable);

                cs.Close();
                return ms.ToArray();
            }
            else
            {
                return new byte[0];
            }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Collections;
using System.Threading;
using System.Security;
using System.Security.Cryptography;
using System.Net;
using System.Net.Sockets;

namespace EncryptionTestB
{
    class Program
    {
        static void Main(string[] args)
        {
            TcpClient client = new TcpClient();
            client.Connect(IPAddress.Parse("127.0.0.1"), 1892);
            NetworkStream ns = client.GetStream();

            Rijndael aes = RijndaelManaged.Create();
            byte[] key = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
            byte[] iv = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
            aes.Key = key;
            aes.IV = iv;

            Program.SendMessage("hello world!\n", ns, aes);

            Console.Read();

            ns.Close();
            client.Close();
        }

        static void SendMessage(String message, NetworkStream stream, Rijndael aes)
        {
            byte[] messageBytes = System.Text.Encoding.UTF8.GetBytes(message);
            Program.SendMessage(messageBytes, stream, aes);
        }

        static void SendMessage(byte[] message, NetworkStream stream, Rijndael aes)
        {
            if (stream.CanWrite)
            {
                MemoryStream ms = new MemoryStream(4096);
                CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write);

                cs.Write(message, 0, message.Length);
                cs.Close();

                byte[] cipherText = ms.ToArray();
                stream.Write(cipherText, 0, cipherText.Length);
            }
            else
            {
                Console.WriteLine("Error:  Stream is not valid for writing.\n");
            }
        }
    }
}
pracsec
  • 320
  • 4
  • 8
0

Dispose the CryptoStream before accessing the underlying buffer (make sure to leaveOpen the underyling stream in this case):

public byte[] Encrypt(byte[] data, ICryptoTransform transform)
{
    using MemoryStream mem = new();
    using (CryptoStream cs = new(mem, transform, CryptoStreamMode.Write, leaveOpen: true))
    {
        cs.Write(data);
    }
    return mem.ToArray();
}

For a block-based cipher (which you are using), this has the same effect as calling CryptoStream.FlushFinalBlock.

Ray
  • 7,940
  • 7
  • 58
  • 90