【问题标题】:Encrypted NetworkStream hangs on ReadLine()加密的 NetworkStream 挂在 ReadLine()
【发布时间】:2018-01-12 07:23:52
【问题描述】:

我正在通过下面的简单示例学习如何加密网络流。在没有加密的情况下,这个例子工作得很好,但现在我添加了 CryptoStreams,在客户端写入消息并刷新后,服务器会挂在“var data = reader.ReadLine()”上。

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

我很确定我错过了一些非常简单的东西。首先想到的是加密会扰乱新行字符的发送,导致服务器在等待分隔符时挂起,但我无法发现问题。

任何帮助将不胜感激。

【问题讨论】:

    标签: c# encryption tcp networkstream cryptostream


    【解决方案1】:

    在您写完一行之后,很可能没有准备好发送的完整加密数据块。你不能直接对此做任何事情。你不能冲掉半个街区。

    最好的做法是完全放弃这种方法并使用现成的解决方案,例如 WCF 或 HTTPS。你为什么要搞乱套接字呢?套接字和加密都非常困难。例如,您的加密是不安全的,因为攻击者可以在您不知道的情况下更改消息。他可以翻转位。

    下一个最好的办法是在您发送消息后关闭连接。我不确定这将如何在这里工作。 CryptoStream.Flush 可能有效。

    或者,使用计数器模式 (CTR),使块大小为 1 个字节。这不受任何内置的支持。你需要一些图书馆。

    【讨论】:

    • 同意,我从没想过将它用于生产环境,但在我正在学习的 70-483 教学大纲中提到了它,所以我至少需要能够展示一些知识它。
    【解决方案2】:

    正如其他人所提到的,CryptoStream 在处理数据之前不会完全刷新数据,因为您使用的是块密码 - 它始终以块的形式工作,并且在您处理完最后一个块之前不会完全刷新流。 注意:简单地关闭或冲洗没有帮助。

    这是一个基于您的示例的示例:

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

    【讨论】:

    • 了解,不知道在处置之前它不会做任何事情。谢谢。
    【解决方案3】:

    我也有同样的问题。就我而言,问题出在CryptoStream.Read()(我知道是因为我只用 C# 读取数据)。因为它是一个不断增长的流,它不知道最后一个加密数据块。它以某种方式调用ICryptoTransform.TransformFinalBlock() 并设置HasFlushedFinalBlock 标志最终结束读取。在我的例子中,文件在增长,读者感觉文件已经结束了欺骗CryptoStream 调用ICryptoTransform.TransformFinalBlock()

    我通过编写CircularMemoryStream 解决了这个问题,我可以一次从文件中转储解密数据 16 个字节(块大小)。然后我将CircularMemoryStream 插入StreamReader 以获取线路。它奏效了。

    为了方便起见,我在CircularMemoryStream 上编写了一个ChunkCryptoStream 包装器,每当流读取器调用Read() 时,它都会提供(16 字节的倍数)解密的循环缓冲区。

    使用MemoryStream 也可以做到这一点,但内部缓冲区会不断增长。

         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?");
         }
    

    【讨论】:

      猜你喜欢
      • 2021-03-23
      • 2019-10-21
      • 1970-01-01
      • 1970-01-01
      • 2011-05-19
      • 1970-01-01
      • 1970-01-01
      • 2011-08-24
      • 2011-12-15
      相关资源
      最近更新 更多