前言
无从想象——至少不伴随实感——此外的生活是什么样子。——《国境以南太阳以西》
本来想做互联网连接的,但是做到那个部分又不知道怎么做了。。。把前面的加密解密步骤发出来吧。
\;\\\;\\\;
目录
- 前言
- 加密传输原理
- RSA加密
- AES加密
- 加密传输
- 客户端框架
- 服务器端框架
- 字符串
- 文件
- 核心文件
加密传输原理
RSA加密
服务器端与客户端连接后,指每一次连接后,都会生成RSA公钥和私钥。私钥只有服务器自己有,公钥通过无加密的字符串格式发送给客户端。
// 生成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秘钥解密第一个文件块和之后接收到的文件块。
\;\\\;\\\;
加密传输
客户端框架
//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),而不是监听。所以没有TcpListenerprivate TcpClient client; // 用于连接服务器的TcpClientprivate 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 = "";}}}
}
\;\\\;\\\;
服务器端框架
//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; // 用于监听客户端连接的TcpListenerprivate TcpClient client; // 用于与客户端通信的TcpClientprivate 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公钥加密的内容
//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秘钥+初始化向量IVpublic 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加密的内容
//Util.cspublic static void SendFile(NetworkStream stream){const int bufferSize = 65536; // 设置缓冲区大小64KBusing (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公钥加密AESbyte[] 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/32byte[] aesIvLength = ConvertInt2ByteArr(AES_IV.Length); //AES_IV长度是16byte[] 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为64KBint 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; // 设置缓冲区大小64KBbyte[] 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编码的字符串,这样加密解密的大小就不对了,这种格式会让原本的字符串大小膨胀!
//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]; // 创建缓冲区1KBusing (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); // 转换为大端序}// 将加密后的数据块写入MemoryStreamms.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(); //随机生成Keyaes.GenerateIV(); //随机生成IVAES_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[]转换成intpublic static int ConvertByteArr2Int(byte[] data){return BitConverter.ToInt32(data);}}
}
除了服务器端框架外,服务器的Util.cs和Rsa.cs没给了,也差不多,关键是服务器接受客户端字符串或文件并解密,服务器发送不会加密。