加密文件传输程序末尾数据乱码问题求助(AES+DH)
问题分析与解决方案
你的文件传输程序末尾乱码的核心原因在于加密/解密逻辑的多个关键错误,而非单纯的分块读取问题。以下是具体问题和修复方案:
关键问题拆解
- CBC模式上下文维护错误:CBC加密要求每个后续块的IV必须是前一个块的密文,你现在所有分块都复用初始IV,导致加密逻辑完全错误,解密后数据必然混乱。
- 加密数据发送不完整:AES加密后的块长度是16字节(块大小128位)的整数倍,但你只发送了原明文读取的
bytesRead长度,接收端拿到的加密数据不完整,解密失败。 - 零填充的局限性:
PaddingMode.Zeros无法区分明文末尾的零字节和填充的零字节,手动去填充的逻辑会误删合法数据,且PKCS7填充是更安全、可靠的标准方案。 - 网络流读取不完整:
NetworkStream.Read不能保证一次读取到指定长度的字节,你当前的单次读取逻辑在网络不稳定时会拿到不完整的公钥、IV或文件数据。
解决方案(推荐流式加密,简洁可靠)
不需要手动分块,直接通过CryptoStream将文件流与网络流对接,让.NET自动处理分块、CBC上下文和填充,既保证正确性又简化代码。
发送端修改
private void SendClick(object sender, RoutedEventArgs e) { // ...(公钥交换、密钥派生逻辑保持不变) // 修改AES配置为PKCS7填充(默认就是此模式,可省略显式设置) aes = Aes.Create(); iv = aes.IV; aes.Padding = PaddingMode.PKCS7; aes.BlockSize = blockSize; aes.Key = senderHashKey; aes.Mode = CipherMode.CBC; tcpClient.GetStream().Write(iv, 0, iv.Length); SendFileInfo(tcpClient.GetStream(), fileName, fileType); // 改用流式加密,自动处理分块和CBC上下文 using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (CryptoStream cryptoStream = new CryptoStream(tcpClient.GetStream(), aes.CreateEncryptor(), CryptoStreamMode.Write)) { fileStream.CopyTo(cryptoStream); cryptoStream.FlushFinalBlock(); // 确保最后一块的填充被正确处理 } tcpClient.Close(); AppendStatus("File sent successfully."); }
接收端修改
private void ReceiveClick(object sender, RoutedEventArgs e) { // ...(公钥交换、密钥派生逻辑保持不变) aes = Aes.Create(); aes.IV = receivedIV; aes.Padding = PaddingMode.PKCS7; aes.BlockSize = blockSize; aes.Mode = CipherMode.CBC; aes.Key = receiverHashKey; FileInfo fileInfo = ReceiveFileInfo(tcpClient.GetStream()); // 改用流式解密,自动处理分块和填充 using (FileStream fileStream = new FileStream(fileInfo.FileName, FileMode.Create, FileAccess.Write)) using (CryptoStream cryptoStream = new CryptoStream(tcpClient.GetStream(), aes.CreateDecryptor(), CryptoStreamMode.Read)) { cryptoStream.CopyTo(fileStream); } tcpListener.Stop(); }
若需保持1MB手动分块的方案
如果必须手动控制1MB分块,需维护CBC上下文并正确处理加密块的发送/接收:
发送端分块逻辑
private void SendClick(object sender, RoutedEventArgs e) { // ...(公钥交换、密钥派生逻辑不变) aes.Padding = PaddingMode.PKCS7; byte[] currentIV = iv; // 初始IV const int chunkSize = 1024 * 1024; // 1MB分块 using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { byte[] buffer = new byte[chunkSize]; int bytesRead; while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0) { using (var encryptor = aes.CreateEncryptor(senderHashKey, currentIV)) using (MemoryStream ms = new MemoryStream()) using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) { cs.Write(buffer, 0, bytesRead); cs.FlushFinalBlock(); byte[] encryptedChunk = ms.ToArray(); // 先发送块长度(避免粘包) byte[] chunkLength = BitConverter.GetBytes(encryptedChunk.Length); tcpClient.GetStream().Write(chunkLength, 0, chunkLength.Length); // 发送加密块数据 tcpClient.GetStream().Write(encryptedChunk, 0, encryptedChunk.Length); // 更新IV为当前块的密文(CBC下一个块的IV是当前块的密文) Array.Copy(encryptedChunk, encryptedChunk.Length - 16, currentIV, 0, 16); } } // 发送结束标记(长度为0的块) tcpClient.GetStream().Write(BitConverter.GetBytes(0), 0, sizeof(int)); } tcpClient.Close(); }
接收端分块逻辑
private void ReceiveClick(object sender, RoutedEventArgs e) { // ...(公钥交换、密钥派生逻辑不变) aes.Padding = PaddingMode.PKCS7; byte[] currentIV = receivedIV; using (FileStream fileStream = new FileStream(fileInfo.FileName, FileMode.Create, FileAccess.Write)) { byte[] lengthBuffer = new byte[sizeof(int)]; while (true) { // 循环读取块长度(确保拿到完整数据) int readLen = 0; while (readLen < sizeof(int)) { int bytes = tcpClient.GetStream().Read(lengthBuffer, readLen, sizeof(int) - readLen); if (bytes == 0) break; readLen += bytes; } if (readLen != sizeof(int)) break; int chunkLength = BitConverter.ToInt32(lengthBuffer, 0); if (chunkLength == 0) break; // 结束标记 // 循环读取完整的加密块 byte[] encryptedChunk = new byte[chunkLength]; int totalRead = 0; while (totalRead < chunkLength) { int bytes = tcpClient.GetStream().Read(encryptedChunk, totalRead, chunkLength - totalRead); if (bytes == 0) break; totalRead += bytes; } if (totalRead != chunkLength) break; // 解密当前块 using (var decryptor = aes.CreateDecryptor(receiverHashKey, currentIV)) using (MemoryStream ms = new MemoryStream()) using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write)) { cs.Write(encryptedChunk, 0, encryptedChunk.Length); cs.FlushFinalBlock(); byte[] decryptedData = ms.ToArray(); fileStream.Write(decryptedData, 0, decryptedData.Length); // 更新IV为当前块的密文 Array.Copy(encryptedChunk, encryptedChunk.Length - 16, currentIV, 0, 16); } } } tcpListener.Stop(); }
额外修复:网络流读取完整性
你当前的公钥、IV读取逻辑存在单次读取不完整的风险,需修改为循环读取:
// 示例:修复接收公钥的逻辑 byte[] senderPublicKeyBytes = new byte[receiverKey.PublicKey.ToByteArray().Length]; int totalRead = 0; while (totalRead < senderPublicKeyBytes.Length) { int bytesRead = tcpClient.GetStream().Read(senderPublicKeyBytes, totalRead, senderPublicKeyBytes.Length - totalRead); if (bytesRead == 0) throw new IOException("连接提前断开"); totalRead += bytesRead; }
内容的提问来源于stack exchange,提问作者PrzgiBaklo
相关产品推荐
相关产品推荐

