2

I'm learning how to encrypt network streams with the simple example below. Without encryption this example worked fine, but now I've added CryptoStreams the server hangs on "var data = reader.ReadLine()" after the client has written it's message and flushed.

    static byte[] Key;
    static byte[] IV;

    static void Main(string[] args)
    {
        var svrTask = RunServer();
        RunClient();
        svrTask.Wait();
    }
    static async Task RunServer ()
    {
        var listener = new TcpListener(4567);
        var algo = new RijndaelManaged();
        listener.Start();
        var client = await listener.AcceptTcpClientAsync();

        using (var stream = client.GetStream())
        using (var inputStream = new CryptoStream(stream, algo.CreateDecryptor(Key, IV), CryptoStreamMode.Read))
        using (var outputStream = new CryptoStream(stream, algo.CreateEncryptor(Key, IV), CryptoStreamMode.Write))
        {
            var reader = new StreamReader(inputStream);
            var writer = new StreamWriter(outputStream);
            var data = reader.ReadLine(); // Task hangs here
            writer.WriteLine("Server Received: " + data);
            writer.Flush();
        }
        client.Close();
        listener.Stop();
    }
    static void RunClient ()
    {
        var algo = new RijndaelManaged();
        Key = algo.Key;
        IV = algo.IV;

        var client = new TcpClient("localhost", 4567);
        using (var stream = client.GetStream())
        using (var inputStream = new CryptoStream(stream, algo.CreateDecryptor(Key, IV), CryptoStreamMode.Read))
        using (var outputStream = new CryptoStream(stream, algo.CreateEncryptor(Key, IV), CryptoStreamMode.Write))
        {
            var writer = new StreamWriter(outputStream);
            Console.WriteLine("Client will send: ");
            var data = Console.ReadLine();

            writer.WriteLine(data);
            writer.Flush();

            var reader = new StreamReader(inputStream);
            var response = reader.ReadLine();
            Console.WriteLine("Client received: " + response);
        }
    }

I'm pretty sure I'm missing something very simple. First thought would be that the encryption is messing up the sending of the new line character, causing the server to hang as it waits for the delimiter, but I can't spot the issue.

Any help would be appreciated.

mistakenot
  • 554
  • 3
  • 14

3 Answers3

1

After you have written a line in all likelihood there is not a complete cryptographic block of data ready to be sent. You can't do anything about this directly. You can't flush half a block.

The best course of action is to abandon this approach completely and use a ready made solution such as WCF or HTTPS. Why are you messing around with sockets anyway? Both sockets as well as crypto are very hard. For example your crypto is unsafe in the sense that an attacker can change the message without you finding out. He can flip bits.

The next best thing would be to close the connection after you have sent your message. I'm unsure how this would work here. Probably, CryptoStream.Flush works.

Alternatively, use Counter Mode (CTR) so that the block size is 1 byte. This is not supported by anything built-in. You need some library.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Agreed, I'd never dream of using this for a production situation, but it's mentioned in the 70-483 syllabus I'm studying for so I need to at least be able to demonstrate some knowledge of it. – mistakenot Mar 24 '15 at 15:56
1

As others have mentioned, the CryptoStream wont fully flush the data until it is disposed because you are using a block cypher - it always works in blocks, and wont be fully flushed until you get to the final block on disposal of the stream. Note: simply closing or flushing will not help.

Here is a sample that is based on your example that works:

class Program
{
   static byte[] Key = new byte[] { 0x02, 0x02, 0x01, 0x03, 0x02, 0x32,
      0x51, 0x13, 0x02, 0x02, 0x01, 0x03, 0x02, 0x32, 0x51, 0x13, 
      0x02, 0x02, 0x01, 0x03, 0xA2, 0x33, 0x53, 0xF3, 0xE2, 0xB2,
      0xA1, 0x93, 0x32, 0x52, 0x53, 0x83 };
   static byte[] IV = new byte[] { 0x44, 0x82, 0xF1, 0x03, 0xA2, 0x3D,
      0x51, 0x13, 0x42, 0x02, 0x01, 0x03, 0x02, 0x32, 0x51, 0x13 };

   static SymmetricAlgorithm Algo = new RijndaelManaged();

   static void Main(string[] args)
   {
      Algo.KeySize = 256;
      Algo.Padding = PaddingMode.PKCS7;
      Algo.Key = Key;
      Algo.IV = IV;

      var svrTask = RunServer();
      RunClient();
      svrTask.Wait();
   }

  static async Task RunServer()
  {
     byte[] resp = new byte[2048];
     var listener = new TcpListener(4567);

     listener.Start();
     var client = await listener.AcceptTcpClientAsync();

     using (var stream = client.GetStream())
     {
        // should read while DataAvailable
        var bytes = stream.Read(resp, 0, resp.Length);
        Array.Resize<byte>(ref resp, bytes);

        string resp_str = DecryptArray(resp);
        var data = "Server Received: " + resp_str;
        Console.WriteLine(data);

        byte[] enc_resp = EncryptString(data);
        stream.Write(enc_resp, 0, enc_resp.Length);
     }
     client.Close();
     listener.Stop();
  }

  static void RunClient()
  {
     byte[] resp = new byte[2048];

     using (var client = new TcpClient("localhost", 4567))
     using (var stream = client.GetStream())
     {
        Console.WriteLine("Client will send: ");
        var data = Console.ReadLine();

        byte[] enc_msg = EncryptString(data);

        stream.Write(enc_msg, 0, enc_msg.Length);

        // should read while DataAvailable
        var bytes = stream.Read(resp, 0, resp.Length);
        Array.Resize<byte>(ref resp, bytes);

        string response = DecryptArray(resp);
        Console.WriteLine("Client received: " + response);
     }
  }

  static byte[] GetBytes(string str)
  {
     byte[] bytes = new byte[str.Length * sizeof(char)];
     System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
     return bytes;
  }

  static string GetString(byte[] bytes)
  {
     char[] chars = new char[bytes.Length / sizeof(char)];
     System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
     return new string(chars);
  }

  private static byte[] EncryptString(string stringValue)
  {
     return TransformData(GetBytes(stringValue), true);
  }

  private static string DecryptArray(byte[] arrayValue)
  {
     return GetString(TransformData(arrayValue, false));
  }

  private static byte[] TransformData(byte[] dataToTransform, bool enc)
  {
     byte[] result = new byte[0];
     if (dataToTransform != null && dataToTransform.Length > 0)
     {
        try
        {
           using (var transform = (enc) ? Algo.CreateEncryptor() :
             Algo.CreateDecryptor())
           {
              result = transform.TransformFinalBlock(
                 dataToTransform, 0, dataToTransform.Length);
           }
        }
        catch (Exception) { /* Log this */ }
     }
     return result;
  }
}
SwDevMan81
  • 48,814
  • 22
  • 151
  • 184
0

I had the same problem too. In my case the problem was in CryptoStream.Read()(I know it because I only read data in C#). Because it is a growing stream, it does not know the last-block of encrypted data. Somehow it calls the ICryptoTransform.TransformFinalBlock() and sets the HasFlushedFinalBlock flag eventually ending the read. In my case, the file was growing, and the reader had a feeling that file has ended fooling the CryptoStream to call ICryptoTransform.TransformFinalBlock().

I solved this by writing a CircularMemoryStream where I can dump decrypted data from the file 16 bytes(block-size) at a time. Then I plugged the CircularMemoryStream to the StreamReader to get the lines. It worked.

To make it easier I wrote a ChunkCryptoStream wrapper on CircularMemoryStream that feeds(multiple of 16 bytes) the decrypted circular buffer whenever stream-reader calls Read().

It is possible to do it using MemoryStream as well, but the internal buffer will keep growing.

     var decryptor = algo.CreateDecryptor(....);
     int BLOCK_SIZE_IN_BYTES = algo.BlockSize / 8;
     int READ_SIZE = BLOCK_SIZE_IN_BYTES*16; // Some multiple of BLOCK_SIZE_IN_BYTES
     MemoryStream decryptedStream = new MemoryStream(READ_SIZE*4); // allocate a buffer
     StreamReader clientReader = new StreamReader(decryptedStream);

     byte[] buff = new byte[READ_SIZE];
     while (true)
     {
        int len = 0;
        while ((len = clientInputStream.Read(buff, 0, READ_SIZE)) > 0)
        {
           int leftover = len % BLOCK_SIZE_IN_BYTES;
           len = len - leftover; // get full blocks

           // if it is big enough then we can decrypt it
           if (len > 0)
           {
              decryptor.TransformBlock(buff, 0, len, buff, 0);
              decryptedStream.Write(buff, 0, len);
              decryptedStream.Seek(-len, SeekOrigin.End);
              Console.WriteLine("Written " + len + " bytes to memory buffer");

              do
              {
                 var line = clientReader.ReadLine();
                 if (null != line)
                 {
                    line = line.Replace('\x1', ' '); // replace padding bits (for me padding is 1 bit and so the value is 1, if you use base64 encoding then use the no-padding option)
                    Console.WriteLine(String.Join(" ", line.Select(x => String.Format("{0:X}", Convert.ToInt32(x)))));
                    line = line.Trim();
                    Console.WriteLine(line);
                    Console.WriteLine("end line " + lineno);
                    lineno++;
                 }
              } while ((!content.EndOfStream));
           } //if
           if (leftover > 0)
           {
              clientInputStream.Seek(-leftover, SeekOrigin.Current);
              break;
           }
        }
        var cmd = Console.ReadLine("Want more Line?");
     }
KRoy
  • 1,290
  • 14
  • 10