You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

加密文件传输程序末尾数据乱码问题求助(AES+DH)

问题分析与解决方案

你的文件传输程序末尾乱码的核心原因在于加密/解密逻辑的多个关键错误,而非单纯的分块读取问题。以下是具体问题和修复方案:


关键问题拆解

  1. CBC模式上下文维护错误:CBC加密要求每个后续块的IV必须是前一个块的密文,你现在所有分块都复用初始IV,导致加密逻辑完全错误,解密后数据必然混乱。
  2. 加密数据发送不完整:AES加密后的块长度是16字节(块大小128位)的整数倍,但你只发送了原明文读取的bytesRead长度,接收端拿到的加密数据不完整,解密失败。
  3. 零填充的局限性:PaddingMode.Zeros无法区分明文末尾的零字节和填充的零字节,手动去填充的逻辑会误删合法数据,且PKCS7填充是更安全、可靠的标准方案。
  4. 网络流读取不完整: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 22:59:50