Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CryptoStream does not flush like expected

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();
        }
    }
}
like image 294
user2400203 Avatar asked Mar 23 '23 10:03

user2400203


1 Answers

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.

like image 80
Jon Skeet Avatar answered Mar 31 '23 17:03

Jon Skeet