前言
无从想象------至少不伴随实感------此外的生活是什么样子。------《国境以南太阳以西》

本来想做互联网连接的,但是做到那个部分又不知道怎么做了。。。把前面的加密解密步骤发出来吧。
\;\\\;\\\;
目录
加密传输原理
RSA加密
服务器端与客户端连接后,指每一次连接后,都会生成RSA公钥和私钥。私钥只有服务器自己有,公钥通过无加密的字符串格式发送给客户端。
csharp
// 生成RSA密钥对
using (RSA rsa = RSA.Create())
{
// 获取公钥和私钥(XML格式)
publicKey = rsa.ToXmlString(false); // 公钥,服务器发给客户端,
privateKey = rsa.ToXmlString(true); // 私钥,服务器自己保存
}
// 将公钥字符串转换为字节数组
byte[] publicKeyBytes = Encoding.UTF8.GetBytes(publicKey);
//发送到客户端
stream.Write(publicKeyBytes, 0, publicKeyBytes.Length);
这样客户端就能拿到这个公钥并加密自己待发送的字符串或者文件块了。文件头是不会加密的,这是区分字符串和文件的。
\;\\\;\\\;
AES加密
文件如果有300M或者几个G呢,RSA不好用来加密大块数据,要用AES加密。
AES秘钥由客户端生成,在要发送文件前生成。秘钥分为Key和IV。
AES秘钥附带在第一个文件块的文件头当中,但是这个AES秘钥也不能公开,所以要用RSA公钥加密这个AES.Key和AES.IV。
服务器拿到第一个文件块后,从文件头中拿到加密后的AES秘钥并用RSA私钥解密。之后就用AES秘钥解密第一个文件块和之后接收到的文件块。
\;\\\;\\\;
加密传输
客户端框架

csharp
//Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
//网络包
using System.Net;
using System.Net.Sockets;
using Newtonsoft.Json;
using System.IO;
using Client.Tool;
namespace Client
{
public partial class Form1 : Form
{
//客户端的主要职责是发起连接请求(Connect),而不是监听。所以没有TcpListener
private TcpClient client; // 用于连接服务器的TcpClient
private NetworkStream stream; // 用于数据传输的网络流
private Thread clientThread; // 用于运行客户端的线程
private int interval = 3; //一次连接中,等待多少秒
private int MaxRetries = 20; //最大重试次数。总的等待时间 = 最大重试次数 X 每次等待多少秒
public Form1()
{
InitializeComponent();
//this.CenterToScreen();
//传递控件过去
Util.SetTextBoxWorld(textBox_world);
InitClient();
}
//初始化客户端
private void InitClient()
{
client = new TcpClient(); // 创建TcpClient
// 创建一个后台线程用于避免UI线程被阻塞
clientThread = new Thread(new ThreadStart(ConnectToServer));
clientThread.IsBackground = true;
clientThread.Start(); // 启动客户端线程
}
//客户端连接服务器
private void ConnectToServer()
{
Util.filename = Util.filepath = "";
int retryCount = 0; // 重试计数器
while (retryCount < MaxRetries)
{
try
{
// 尝试连接到服务器的IP地址和端口
int port = 13000; // 12345;
string serverAddr = "127.0.0.1"; //"192.168.21.60"
client.Connect(serverAddr, port);
//client.Connect("192.168.21.60", 12345);
stream = client.GetStream(); // 获取与服务器通信的网络流
// 更新UI,显示已连接到服务器
Util.OutClientMsg("已连接到服务器。");
// 启动一个新线程来接收服务器的消息
Thread receiveThread = new Thread(new ThreadStart(ReceiveFromServer));
receiveThread.IsBackground = true;
receiveThread.Start();
return; // 成功连接
}
catch (Exception ex)
{
// 更新UI,显示连接失败信息
Util.OutErr($"连接失败,{ex.Message}。正在尝试重新连接...");
retryCount++; // 增加重试计数
if (retryCount < MaxRetries)
{
// 等待一段时间后重试
Thread.Sleep(interval * 1000); // 等待指定的秒数
}
else
{
// 达到最大重试次数,显示错误信息
Util.OutErr("连接失败,已达到最大重试次数。");
}
}
}
}
//接收服务器消息
private void ReceiveFromServer()
{
try
{
//-----------------------------------------------------------------------------------
byte[] buffer = new byte[65536]; // 创建缓冲区64KB
//接收RSA公钥
Util.ReceiveRSA_PublicKey(stream);
while (true) // 持续读取服务器发送的数据
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)
break; // 连接被关闭
List<byte> data = new List<byte>();
data.AddRange(buffer.Take(bytesRead)); //读取bbytesRead个字节,存入data中
Util.Proc(stream,data);
}
//-----------------------------------------------------------------------------------
// 如果读取返回0,说明服务器已断开连接
Util.OutErr("服务器连接意外关闭");
}
catch (IOException ex)
{
// 捕获连接异常并更新UI显示异常信息
Util.OutErr("接收失败!" + ex.Message);
}
catch (Exception ex)
{
Util.OutErr("接收线程异常:" + ex.Message);
}
finally
{
// 确保网络流被关闭
stream?.Close();
client?.Close();
//关闭文件流
Util.CloseFileStream();
}
// 重新尝试连接
InitClient();
}
// 发送信息给服务器
private void btn_send_Click(object sender, EventArgs e)
{
if (Util.filename != "" && Util.filepath != "" && File.Exists(Util.filepath)) //首先要传递文件
{
try
{
Util.SendFile(stream);
// 更新UI,显示已发送的消息
Util.OutClientMsg("文件已发送:" + Util.filename);
}
catch (Exception ex)
{
// 如果发生异常,提示用户
Util.OutErr("发送文件失败!" + ex.Message);
}
Util.filename = Util.filepath = "";
}
else if (stream != null) // 检查网络流是否可用
{
try
{
string message = textBox_send.Text; // 获取待发送的消息
if (message == "")
return;
Util.SendStr(stream, message);
// 更新UI,显示已发送的消息
Util.OutClientMsg(message);
}
catch (Exception ex)
{
// 如果发生异常,提示用户
Util.OutErr("发送失败!" + ex.Message);
}
}
else
{
// 如果未连接到服务器,提示用户
//MessageBox.Show("客户端: 未连接到服务器!");
Util.OutErr("未连接到服务器!");
}
// 清空发送框 , 不管成功与否
textBox_send.Clear();
}
//客户端程序关闭
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// 关闭客户端时释放资源
if (client != null)
{
client.Close(); // 关闭TcpClient
}
if (stream != null)
{
stream.Close(); // 关闭网络流
}
}
//文件拖入
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length > 0)
{
Util.filepath = files[0].Trim();
Util.filename = Path.GetFileName(Util.filepath).Trim();
//输入框中只显示该文件名
textBox_send.Text = Util.filename;
}
else
{
Util.filename = "";
}
}
}
}
\;\\\;\\\;
服务器端框架

csharp
//Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
//网络包
using System.Net;
using System.Net.Sockets;
using Newtonsoft.Json;
using System.IO;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Server.Tool;
using System.Security.Cryptography.X509Certificates;
namespace Server
{
public partial class Form1 : Form
{
private TcpListener server; // 用于监听客户端连接的TcpListener
private TcpClient client; // 用于与客户端通信的TcpClient
private NetworkStream stream; // 用于数据传输的网络流
private Thread serverThread; // 用于运行服务器的线程
public Form1()
{
InitializeComponent();
//this.CenterToScreen();
//传递控件过去
Util.SetTextBoxWorld(textBox_world);
InitServer();
}
//初始化服务器
private void InitServer()
{
// 创建TcpListener,监听所有IP地址 0.0.0.0:8000端口
//server = new TcpListener(IPAddress.Any, 8000); //在控制台输入netstat -ano | findstr "8000" 后,有反馈说明这个口已经被占用
//只监视本地ip,监视其端口
int port = 13000; //12345;
string localAddr = "127.0.0.1"; //"192.168.21.60"
server = new TcpListener(IPAddress.Parse(localAddr), port); //没反馈说明这个口没被占用
// 创建一个后台线程用于启动服务器
serverThread = new Thread(new ThreadStart(StartServer));
serverThread.IsBackground = true;
serverThread.Start(); // 启动服务器线程
}
//启动服务器
private void StartServer()
{
Util.filename = Util.filepath = "";
try
{
server.Start();
Util.OutServerMsg("启动,等待客户端连接...");
}
catch (SocketException ex)
{
Util.OutErr("启动失败," + ex.Message + ",错误代码:" + ex.ErrorCode);
return;
}
while (true) // 服务器持续运行
{
try
{
client = server.AcceptTcpClient();
stream = client.GetStream(); // 获取与客户端通信的网络流
Util.OutServerMsg("客户端已连接。");
//-----------------------------------------------------------------------------
byte[] buffer = new byte[65536]; // 创建缓冲区64KB
//第一次发送RSA公钥
Util.SendRSA_PublicKey(stream);
// 持续读取客户端发送的数据
while (true)
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)
break; // 如果读取返回0,说明客户端已断开连接
List<byte> data = new List<byte>();
data.AddRange(buffer.Take(bytesRead));
Util.Proc(stream,data);
}
//-----------------------------------------------------------------------------
// 如果读取返回0,说明客户端已断开连接
Util.OutErr("客户端断开连接");
}
catch (Exception ex)
{
// 捕获异常并更新UI显示异常信息
Util.OutErr("接收失败!" + ex.Message);
}
finally
{
// 确保网络流被关闭
stream?.Close();
client?.Close();
//关闭文件流
Util.CloseFileStream();
}
// 重置client和stream以便接受新的连接
client = null;
stream = null;
}
}
//发送信息给客户端
private void btn_send_Click(object sender, EventArgs e)
{
if (Util.filename != "" && Util.filepath != "" && File.Exists(Util.filepath)) //首先要传递文件
{
try
{
Util.SendFile(stream);
// 更新UI,显示已发送的消息
Util.OutServerMsg("文件已发送:" + Util.filename);
}
catch (Exception ex)
{
// 如果发生异常,提示用户
Util.OutErr("发送文件失败!" + ex.Message);
}
//回到正常的消息状态!
Util.filename = Util.filepath = "";
}
else if (stream != null) // 检查网络流是否可用
{
try
{
string message = textBox_send.Text; // 获取待发送的消息
if (message == "")
return;
Util.SendStr(stream, message);
// 更新UI,显示已发送的消息
Util.OutServerMsg(message);
}
catch (Exception ex)
{
// 如果发生异常,提示用户
Util.OutErr("发送失败!" + ex.Message);
}
}
else
{
// 如果没有客户端连接,提示用户
Util.OutErr("客户端未连接到服务器!");
}
// 清空发送框 , 不管成功与否
textBox_send.Clear();
}
//服务器程序关闭
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// 关闭服务器时释放资源
if (server != null)
{
server.Stop(); // 停止TcpListener
}
if (client != null)
{
client.Close(); // 关闭TcpClient
}
if (stream != null)
{
stream.Close(); // 关闭网络流
}
}
//文件拖入
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length > 0)
{
Util.filepath = files[0].Trim();
Util.filename = Path.GetFileName(Util.filepath).Trim();
//输入框中只显示该文件名
textBox_send.Text = Util.filename;
}
else
{
Util.filename = "";
}
}
}
}
\;\\\;\\\;
字符串
字符串直接发过去不知道怎么接收,按字符串格式接收那文件怎么办?所以字符串和文件都是以Byte[]格式发送。
- 消息: @STR| + strLength| + 经过RSA公钥加密的内容
csharp
//Util.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Client.Tool
{
partial class Util
{
//文件发送
public static string filepath;
public static string filename;
//文件接收
public static FileStream curFileStream = null;
public static string curFileName = null;
public static List<byte> accumulatedData = new List<byte>();
public static long expectedFileSize = -1;
public static long totalReceived = 0;
//传递控件回来
private static RichTextBox textBox_world;
//RSA公钥
public static string publicKey = null;
//AES秘钥+初始化向量IV
public static byte[] AES_Key = null;
public static byte[] AES_IV = null;
//设置控件
public static void SetTextBoxWorld(RichTextBox world)
{
textBox_world = world;
}
//客户端和服务器消息输出函数
public static void OutMsg(string prefix, string prefixColor, string message, string messageColor)
{
if (textBox_world.InvokeRequired)
{
textBox_world.Invoke(new Action(() => OutMsg(prefix, prefixColor, message, messageColor)));
}
else
{
// 保存当前的文本长度,用于后续选择文本
int startIndex = textBox_world.TextLength;
// 追加前缀、冒号、消息内容和换行符
textBox_world.AppendText(prefix + ": " + message + "\n");
// 选中前缀并设置样式
textBox_world.Select(startIndex, prefix.Length + 2); // 包括冒号和空格
textBox_world.SelectionColor = Color.FromName(prefixColor);
textBox_world.SelectionFont = new Font(textBox_world.Font, FontStyle.Italic);
// 选中消息内容并设置样式
textBox_world.Select(startIndex + prefix.Length + 2, message.Length);
textBox_world.SelectionColor = Color.FromName(messageColor);
textBox_world.SelectionFont = new Font(textBox_world.Font, FontStyle.Regular); // 重置为常规字体样式
//滚动到最下面
textBox_world.SelectionStart = textBox_world.TextLength;
textBox_world.ScrollToCaret();
// 取消选择
textBox_world.SelectionStart = textBox_world.TextLength;
textBox_world.SelectionLength = 0;
}
}
//服务器消息输出函数
public static void OutServerMsg(string message)
{
OutMsg("服务器", "Blue", message, "Black");
}
//客户端消息输出函数
public static void OutClientMsg(string message)
{
OutMsg("客户端", "Green", message, "Black");
}
//输出错误信息函数
public static void OutErr(string message)
{
OutMsg("客户端", "Green", message, "Red");
}
public static void SendFile(NetworkStream stream)
{
...
}
public static void SendStr(NetworkStream stream, string message)
{
byte[] msg = ConvertStr2ByteArr(message);
//加密实际的字符串
byte[] data = EncryptStr(msg);
//测试
OutClientMsg("公钥加密后字符串: " + GetTestStr(data));
string strHead = "@STR|"; // 固定头
string prefix = $"{strHead}{data.Length}|"; // 头部 + 数据长度 + 分隔符
byte[] prefixBytes = ConvertStr2ByteArr(prefix); //保证前缀中之前没有Byte[],不然经过两次转换数据会损坏
//测试
OutServerMsg("文件头: " + prefix);
byte[] combinedData = new byte[prefixBytes.Length + data.Length];
Buffer.BlockCopy(prefixBytes, 0, combinedData, 0, prefixBytes.Length);
Buffer.BlockCopy(data, 0, combinedData, prefixBytes.Length, data.Length);
stream.Write(combinedData, 0, combinedData.Length);
stream.Flush();
}
//处理数据
public static void Proc(NetworkStream stream, List<byte> data)
{
//if (expectedFileSize == -1)
//{
// 尝试解析消息头
string msgPart = Encoding.UTF8.GetString(data.ToArray());
if (msgPart.StartsWith("@STR|"))
{
ProcStr(msgPart, data); //获取到的就是消息头+消息
}
else if (msgPart.StartsWith("@FILE|"))
{
ProcFileHead(msgPart, data); //第一次获取到的只是文件头
WriteInFile(stream);
}
//}
//else
//{
// // 如果正在接收文件,则直接写入文件
//}
}
//处理文件头
// @FILE + 文件名 + 文件大小
public static void ProcFileHead(string msgPart, List<byte> data)
{
...
}
//打开文件流
public static void OpenFileStream()
{
...
}
//写入文件
public static void WriteInFile(NetworkStream stream)
{
...
}
//关闭文件流
public static void CloseFileStream()
{
...
}
//处理字符串
public static void ProcStr(string msgPart, List<byte> data)
{
int posEndHdr = msgPart.IndexOf('|', 5); // 跳过@STR|
if (posEndHdr > -1 && int.TryParse(msgPart.Substring(5, posEndHdr - 5), out int msgLen))
{
int totalMsgLen = posEndHdr + 1 + msgLen;
if (msgPart.Length >= totalMsgLen)
{
string content = msgPart.Substring(posEndHdr + 1, msgLen);
OutServerMsg(content);
// 移除已解析的文件头部分
data.RemoveRange(0, totalMsgLen);
return;
}
}
}
}
}
\;\\\;\\\;
文件
文件是按最大64KB byte[]的块来发送的,除了文件大小外,还需要携带AES秘钥。也就是每次发送一个文件,第一个文件包开头都需要一个新的AES秘钥。同时这个秘钥为了不能泄露,也需要经过RSA公钥加密。
文件头: @FILE| + filename| + fileSize| +
AES_Key.Length| + 经过RSA公钥加密的AES_Key| +
AES_IV.Length| + AES_IV|
文件块1: 长度
文件块1: 经过AES加密的内容
文件块2: 长度
文件块2: 经过AES加密的内容
文件块3: 长度
文件块3: 经过AES加密的内容
csharp
//Util.cs
public static void SendFile(NetworkStream stream)
{
const int bufferSize = 65536; // 设置缓冲区大小64KB
using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[bufferSize];
long fileSize = fs.Length;
long totalSent = 0;
OutClientMsg($"开始发送文件: {filename} ...");
//生成AES秘钥,每次发送文件的AES秘钥都是新的
GenerateAESKeyIV();
//使用RSA公钥加密AES
byte[] rsa_aes_crypted = EncryptStr(AES_Key);
//测试
OutClientMsg($"经RSA加密后AES秘钥为: {GetTestStr(rsa_aes_crypted)}");
// 发送文件头部信息
string fileHead = "@FILE|"; // 文件头标识
// @FILE + 文件名 + 文件大小 + AES秘钥长度 + 被RSA加密后的AES秘钥 + IV向量长度 + IV向量
byte[] filePrefix = ConvertStr2ByteArr($"{fileHead}{filename}|{fileSize}|");
byte[] bar = ConvertStr2ByteArr("|");
byte[] aesKeyLength = ConvertInt2ByteArr(rsa_aes_crypted.Length); //AES_Key长度是16/24/32
byte[] aesIvLength = ConvertInt2ByteArr(AES_IV.Length); //AES_IV长度是16
byte[] prefixBytes = JointByteArr(filePrefix,
aesKeyLength, bar,
rsa_aes_crypted, bar,
aesIvLength, bar,
AES_IV, bar); //连接各个数组,避免多次转换损坏数据
//测试
OutClientMsg($"组成文件头前缀: {ConvertByteArr2Str(filePrefix)}");
//发送整个文件头
stream.Write(prefixBytes, 0, prefixBytes.Length);
stream.Flush();
//分块发送内容,每块大小buffer为64KB
int bytesRead;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
byte[] dataToSend = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, dataToSend, 0, bytesRead);
//使用AES秘钥加密数据块
byte[] encrypted_data = EncryptData(dataToSend);
//先发送数据块大小
byte[] l = ConvertInt2ByteArr(encrypted_data.Length);
stream.Write(l, 0, l.Length);
stream.Flush();
//再发送数据块
stream.Write(encrypted_data, 0, encrypted_data.Length);
stream.Flush();
totalSent += bytesRead;
int pct = (int)(totalSent * 100 / fileSize);
OutClientMsg($"发送中... 总数: {fileSize} 字节, 剩余: {fileSize - totalSent} 字节, 已发送: {totalSent} 字节 {pct}%");
}
}
}
//处理文件头
// @FILE + 文件名 + 文件大小
public static void ProcFileHead(string msgPart, List<byte> data)
{
int posNameEnd = msgPart.IndexOf('|', 6); // 跳过@FILE|
int posSizeEnd = msgPart.IndexOf('|', posNameEnd + 1);
if (posSizeEnd > -1 && Int64.TryParse(msgPart.Substring(posNameEnd + 1, posSizeEnd - posNameEnd - 1), out long fileSize))
{
curFileName = msgPart.Substring(6, posNameEnd - 6);
expectedFileSize = fileSize;
OutClientMsg($"开始接收文件: {curFileName}, 文件大小: {fileSize} 字节");
// 移除已解析的文件头部分
data.RemoveRange(0, posSizeEnd + 1);
}
}
//打开文件流
public static void OpenFileStream()
{
curFileStream = new FileStream(curFileName, FileMode.Create);
totalReceived = 0;
}
写入文件
//public static void WriteInFile_old(List<byte> data)
//{
// while (data.Count > 0 && totalReceived < expectedFileSize)
// {
// int bytesToWrite = (int)Math.Min(data.Count, expectedFileSize - totalReceived);
// byte[] chunk = data.Take(bytesToWrite).ToArray();
// curFileStream.Write(chunk, 0, chunk.Length);
// curFileStream.Flush();
// totalReceived += bytesToWrite;
// data.RemoveRange(0, bytesToWrite);
// int pct = (int)(totalReceived * 100 / expectedFileSize);
// OutClientMsg($"接收中... 总数: {expectedFileSize} 字节, 剩余: {expectedFileSize - totalReceived} 字节, 已接收: {totalReceived} 字节 {pct}%");
// }
// if (totalReceived >= expectedFileSize)
// {
// CloseFileStream();
// expectedFileSize = -1; // 准备接收下一个文件或消息
// OutClientMsg($"文件接收完成: {curFileName}");
// }
//}
//写入文件
public static void WriteInFile(NetworkStream stream)
{
// 打开文件流
OpenFileStream();
// 确保已初始化了curFileStream和其他必要的变量
if (curFileStream == null || expectedFileSize < 0)
{
throw new InvalidOperationException("未正确初始化文件接收参数");
}
const int bufferSize = 65536; // 设置缓冲区大小64KB
byte[] buffer = new byte[bufferSize];
while (totalReceived < expectedFileSize)
{
try
{
int bytesToRead = Math.Min(bufferSize, (int)expectedFileSize - (int)totalReceived);
int bytesRead = 0;
int totalRead = 0;
// 根据需要读取的数据量进行读取
while (totalRead < bytesToRead)
{
bytesRead = stream.Read(buffer, totalRead, bytesToRead - totalRead);
if (bytesRead == 0)
throw new Exception("连接关闭前未能完成读取");
totalRead += bytesRead;
}
// 将读取的数据写入文件流
curFileStream.Write(buffer, 0, totalRead);
curFileStream.Flush(); // 刷新文件流以确保所有数据都被写入磁盘
// 更新已接收的数据总量
totalReceived += totalRead;
// 计算并输出接收进度百分比
int pct = (int)(totalReceived * 100 / expectedFileSize);
OutServerMsg($"接收中... 总数: {expectedFileSize} 字节, 剩余: {expectedFileSize - totalReceived} 字节, 已接收: {totalReceived} 字节 {pct}%");
}
catch (Exception ex)
{
OutServerMsg($"接收过程中发生错误: {ex.Message}");
break;
}
}
// 如果已接收的数据量达到或超过预期文件大小,则关闭文件流
if (totalReceived >= expectedFileSize)
{
CloseFileStream(); // 关闭当前文件流
// 重置预期文件大小以便准备接收下一个文件或消息
expectedFileSize = -1;
// 输出文件接收完成的消息
OutServerMsg($"文件接收完成: {curFileName}");
}
}
//关闭文件流
public static void CloseFileStream()
{
if (curFileStream != null)
{
curFileStream.Close();
curFileStream = null;
}
}
\;\\\;\\\;
核心文件
核心在于不要采用base64编码的字符串,这样加密解密的大小就不对了,这种格式会让原本的字符串大小膨胀!
csharp
//Rsa.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net.Sockets;
namespace Client.Tool
{
partial class Util
{
//接收RSA公钥
public static void ReceiveRSA_PublicKey(NetworkStream stream)
{
byte[] buffer = new byte[1024]; // 创建缓冲区1KB
using (MemoryStream ms = new MemoryStream())
{
int bytesRead;
//循环,获取完整的公钥
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, bytesRead);
// 检查是否接收到完整的公钥(例如,通过检查XML的结束标记)
if (Encoding.UTF8.GetString(ms.ToArray()).Contains("</RSAKeyValue>"))
{
break;
}
}
ms.Position = 0; // 重置流位置
publicKey = Encoding.UTF8.GetString(ms.ToArray());
// 测试输出
OutClientMsg($"公钥: {publicKey} \n长度: {publicKey.Length}");
}
}
// RSA加密方法(客户端使用) //为了避免长度不合适的错误,把数据分成固定长度的快来加密解密
public static byte[] EncryptStr(byte[] data)
{
using (RSA rsa = RSA.Create())
{
rsa.FromXmlString(publicKey);
// 获取RSA密钥大小(以字节为单位)
int keySize = rsa.KeySize / 8;
int maxDataSize = keySize - 11; // PKCS1 padding requires at least 11 bytes
// 创建一个MemoryStream来保存所有的加密数据块
using (MemoryStream ms = new MemoryStream())
{
for (int i = 0; i < data.Length; i += maxDataSize)
{
int currentChunkSize = Math.Min(maxDataSize, data.Length - i);
byte[] chunk = new byte[currentChunkSize];
Array.Copy(data, i, chunk, 0, currentChunkSize);
// 如果当前块小于最大数据大小,则需要填充到最大数据大小以便于加密
if (currentChunkSize < maxDataSize)
{
byte[] paddedChunk = new byte[maxDataSize];
Array.Copy(chunk, paddedChunk, currentChunkSize);
chunk = paddedChunk;
}
//加密
byte[] encryptedChunk = rsa.Encrypt(chunk, RSAEncryptionPadding.Pkcs1);
// 将加密块长度转换为大端序
byte[] lengthBytes = BitConverter.GetBytes(encryptedChunk.Length);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(lengthBytes); // 转换为大端序
}
// 将加密后的数据块写入MemoryStream
ms.Write(lengthBytes, 0, sizeof(int)); // 先写入长度
ms.Write(encryptedChunk, 0, encryptedChunk.Length); // 再写入数据
}
return ms.ToArray();
}
}
}
/*
消息: @STR| + strLength| + 经过RSA公钥加密的内容
*/
/*
如果需要加密大块数据,建议结合对称加密算法(如 AES):
客户端加密逻辑
1,生成对称密钥:生成一个随机的AES密钥。
2,使用 AES 密钥加密数据。
3,使用服务器的RSA公钥加密AES密钥。
4,将加密后的数据和加密的对称密钥一起发送给服务器。
服务器解密逻辑
1,使用RSA私钥解密AES密钥。
2,使用AES密钥解密数据
*/
//生成AES秘钥
public static void GenerateAESKeyIV()
{
// 生成随机的AES密钥
using (Aes aes = Aes.Create())
{
aes.KeySize = 256;
aes.GenerateKey(); //随机生成Key
aes.GenerateIV(); //随机生成IV
AES_Key = aes.Key;
AES_IV = aes.IV;
// 测试输出
OutClientMsg($"生成AES秘钥: {GetTestStr(AES_Key)}, 长度: {AES_Key.Length}");
OutClientMsg($"生成AES向量: {GetTestStr(AES_IV)}, 长度: {AES_IV.Length}");
}
}
//AES加密大块数据(客户端)
public static byte[] EncryptData(byte[] data)
{
using (Aes aes = Aes.Create())
{
aes.Key = AES_Key; //使用提供的AES密钥
aes.IV = AES_IV; //使用提供的IV
// 创建加密器
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
// 使用MemoryStream和CryptoStream进行加密
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
}
// 获取加密后的数据
return ms.ToArray();
}
}
}
/*
文件头: @FILE| + filename| + fileSize| + AES_Key.Length| + 经过RSA公钥加密的AES_Key| + AES_IV.Length| + AES_IV|
文件块1: 长度
文件块1: 经过AES加密的内容
文件块2: 长度
文件块2: 经过AES加密的内容
文件块3: 长度
文件块3: 经过AES加密的内容
*/
//byte[]转成一个字符串
public static string ConvertByteArr2Str(byte[] data)
{
return Encoding.UTF8.GetString(data);
}
//byte[]转成可实现的字符串
public static string GetTestStr(byte[] data)
{
return Convert.ToBase64String(data);
}
//字符串转byte[]
public static byte[] ConvertStr2ByteArr(string str)
{
return Encoding.UTF8.GetBytes(str);
}
//多个字节数组合并
public static byte[] JointByteArr(params byte[][] arrays)
{
// 计算合并后的总长度
int totalLength = arrays.Sum(arr => arr.Length);
// 创建一个新的字节数组用于存储合并结果
byte[] result = new byte[totalLength];
// 当前写入位置
int currentPos = 0;
foreach (byte[] array in arrays)
{
// 将当前数组复制到结果数组的相应位置
Buffer.BlockCopy(array, 0, result, currentPos, array.Length);
currentPos += array.Length;
}
return result;
}
//将int转换成byte[]
public static byte[] ConvertInt2ByteArr(int data)
{
return BitConverter.GetBytes(data);
}
//将byte[]转换成int
public static int ConvertByteArr2Int(byte[] data)
{
return BitConverter.ToInt32(data);
}
}
}
除了服务器端框架外,服务器的Util.cs和Rsa.cs没给了,也差不多,关键是服务器接受客户端字符串或文件并解密,服务器发送不会加密。