CryptoStream не сбрасывается, как ожидалось

Предполагается, что код C# .NET Framework 4.5, над которым я работаю, позволяет мне передавать текст через зашифрованный поток в другую программу. Я создал две простые программы, чтобы продемонстрировать мою проблему. EncryptionTestA — это сервер, и он должен запускаться первым. EncryptionTestB является клиентом и должен запускаться вторым. После подключения EncryptionTestB передает текст «hello world» другой программе, передавая его через CryptoStream. По крайней мере, в теории.

Что на самом деле происходит, ничего. Я подтвердил это, наблюдая за передачей данных с помощью Wireshark на внутреннем интерфейсе. Этот код не передает абсолютно никаких данных в его нынешнем виде. Единственный способ заставить его отправить «привет, мир» - это закрыть StreamWriter на стороне клиента. Проблема в том, что он также закрывает базовое TCP-соединение, чего я не хочу делать.

Итак, мой вопрос: как мне сбросить StreamWriter/CryptoStream, не закрывая базовое TCP-соединение?

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

person user2400203    schedule 24.06.2013    source источник
comment
Используйте AesManaged, а не RijndaelManaged. Aes — официальная версия AES, Rijndael — первая экспериментальная версия AES...   -  person mcmonkey4eva    schedule 24.06.2013
comment
Кроме того, если вы не получите лучшего ответа, вы можете отложить его работу и просто скопировать сетевые данные в MemoryStream и создать криптографию вокруг этого.   -  person mcmonkey4eva    schedule 24.06.2013


Ответы (3)


Я считаю, что проблема в том, что вы используете блочный шифр — он всегда работает блоками, поэтому, пока вы не дойдете до последнего блока, где вы закрываете поток (в этот момент у вас есть более короткий блок с дополнение некоторого описания) ничего нельзя записать в поток, пока у вас есть неполный блок.

Я сильно подозреваю, что если вы попытаетесь зашифровать что-то более длинное, чем «hello world» — попробуйте что-нибудь длиной не менее 16 символов, а может и больше — вы обнаружите, что получаете несколько блоков перед закрытием потока, но если вы не столкнетесь с границей блока с концом ваших данных, вам все равно будет не хватать некоторых в конце.

Непонятно, каков ваш конечный вариант использования: если вы пытаетесь отправить несколько сообщений с некоторым описанием в одном и том же потоке, я бы посоветовал вам разработать схему, в которой вы шифруете каждое сообщение отдельно, а затем помещаете все эти данные на канале связи - с префиксом длины, чтобы вы знали, насколько велика зашифрованная информация на принимающей стороне.

Обратите внимание, что принимающей стороне я настоятельно рекомендую избегать использования DataAvailable. Тот факт, что в потоке сейчас нет доступных данных, не означает, что вы дошли до конца сообщения... вот почему вам нужен префикс длины.

person Jon Skeet    schedule 29.06.2013
comment
Мой возможный случай - отправить несколько сообщений в один и тот же NetworkStream. Мой второй пост в основном позволяет это, хотя мне придется внести некоторые изменения, чтобы передать префикс длины сообщения. - person user2400203; 29.06.2013
comment
@ user2416758: Да, мой пост в основном объяснял, почему это было необходимо, и советовал, как с этим справиться. - person Jon Skeet; 29.06.2013
comment
Сразу хочу отметить - cipher.FeedbackSize = 8; и cipher.Mode = CipherMode.CFB; - person Joshua W; 16.11.2013

Вы также можете использовать

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

Надеюсь, поможет ;).

person Max Rudko    schedule 18.09.2016

Основываясь на предложении mcmonkey4eva, я реализовал решение с использованием MemoryStreams, которое, похоже, работает; однако я не полностью удовлетворен этим решением, поскольку я более или менее не решаю исходную проблему, а избегаю ее.

EncryptionTestA — это приложение, прослушивающее подключение, а EncryptionTestB — приложение, которое подключается. После подключения EncryptionTestB отправляет сообщение «Hello World!» зашифровано с помощью AES в EncryptionTestB.

ПРИМЕЧАНИЕ. Использование класса Rinjdael обеспечивает некоторую обратную совместимость, если целевая система не использует более позднюю версию .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");
            }
        }
    }
}
person user2400203    schedule 29.06.2013