我正在通过下面的简单示例学习如何加密网络流。如果没有加密,这个例子工作得很好,但现在我已经添加了 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);
}
}
我很确定我错过了一些非常简单的东西。第一个想法是加密弄乱了新行字符的发送,导致服务器在等待分隔符时挂起,但我无法发现问题。
任何帮助将不胜感激。
在你写了一行之后,很可能没有一个完整的加密数据块准备好发送。你不能直接对此做任何事情。你不能冲洗半个街区。
最好的做法是完全放弃这种方法,并使用现成的解决方案,如 WCF 或 HTTPS。你为什么要弄弄套接字?套接字和加密都非常坚硬。例如,您的加密货币是不安全的,因为攻击者可以在您没有发现的情况下更改消息。他可以翻转位。
下一个最好的办法是在发送消息后关闭连接。我不确定这将如何在这里工作。可能,CryptoStream.Flush
有效。
或者,使用计数器模式 (CTR(,使块大小为 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;
}
}
我也有同样的问题。就我而言,问题出在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?");
}