2

我正在学习如何使用下面的简单示例加密网络流。在没有加密的情况下,这个例子工作得很好,但是现在我添加了 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);
        }
    }

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

任何帮助,将不胜感激。

4

3 回答 3

1

正如其他人所提到的,在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;
  }
}
于 2015-03-23T14:26:56.487 回答
1

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

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

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

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

于 2015-03-20T15:15:23.163 回答
0

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

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

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

也可以使用它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?");
     }
于 2018-01-11T21:42:20.187 回答