1. 项目概述:从需求到实现的DES文件加密工具

最近在整理一些本地文档,发现不少私密的项目日志和合同草稿就这么“裸奔”在硬盘里,心里总觉得不踏实。虽说现在云盘都自带加密,但把文件传上去总觉得隔了一层,尤其是涉及到一些未公开的创意或敏感数据时,还是希望能完全掌控在自己手里。于是,我就琢磨着自己写一个本地的文件加密工具。要求很简单:可靠、高效、能集成到日常的工作流里。DES(Data Encryption Standard)算法虽然现在不是最前沿的,但其结构清晰、实现成熟,对于保护非绝密级别的个人文件来说,完全够用,而且作为学习对称加密的经典案例再合适不过。这个项目就是用C#实现一个完整的DES文件加密解密程序,我会把核心思路、踩过的坑以及完整的源码都分享出来,目标是让你拿到代码就能跑起来,并且理解每一行背后的逻辑。

这个工具适合谁呢?如果你是C#的初学者,想通过一个完整的项目理解流操作、字节处理和加密算法的集成,这会是一个很好的练手项目。如果你是一名开发者,需要在自己的应用中快速集成一个轻量级的文件加密模块,这里的代码结构清晰,可以直接复用。即使你只是对加密原理感兴趣,通过这个具体的实现,也能直观地看到DES算法是如何一步步将明文变成密文,又如何还原回来的。整个过程,我们会从DES的原理聊起,然后搭建项目框架,逐步实现加密和解密的核心功能,最后处理文件读写和用户交互。我会重点解释那些容易混淆的概念,比如初始置换、Feistel网络、以及各种模式(如ECB、CBC)的选择,并分享我在调试字节对齐和填充模式时遇到的真实问题。

2. DES算法核心原理与C#实现选型

在动手写代码之前,我们必须先搞清楚DES到底在干什么。DES是一种对称分组加密算法,所谓“对称”,就是加密和解密用同一把钥匙;“分组”意味着它一次处理64位(8字节)的数据块。虽然它的56位密钥长度在今天看来已不足以抵御暴力破解,但其精巧的Feistel网络结构依然是密码学史上的经典。

2.1 DES算法的核心流程拆解

DES的加密过程可以概括为几个大的步骤:初始置换(IP)、16轮的Feistel轮函数、最终置换(IP⁻¹)。解密过程就是加密过程的逆序,这正是Feistel结构的精妙之处,加密和解密的逻辑几乎一致,只是子密钥的使用顺序相反。

1. 初始置换与最终置换 :这两个步骤是固定的位重排操作,通过查表完成。它们本身不提供密码学强度,主要目的是打乱原始数据的位序,为后续的轮操作做准备。在C#中,我们可以用位操作和预定义的置换表数组来实现。

2. Feistel轮函数(F函数) :这是DES的心脏。每一轮中,右半部分(32位)会经过扩展置换(E盒)变成48位,然后与当前轮的子密钥进行异或。接着,这48位数据被送入8个S盒(Substitution Box,替换盒),每个S盒将6位输入映射为4位输出,总共压缩回32位。最后,这32位再经过一个固定的置换(P盒)后,与左半部分进行异或,产生新的右半部分,而旧的右半部分则直接成为新的左半部分。S盒是DES算法中唯一的非线性部件,是整个算法安全性的关键。

3. 子密钥生成 :从用户输入的64位密钥(实际有效位56位,另有8位奇偶校验位)出发,经过置换选择(PC-1)得到56位有效密钥。然后,这56位被分成左右两部分,每轮进行循环左移(移位数根据轮数固定),再经过置换选择(PC-2)压缩成48位的轮子密钥。解密时,只需将这16个子密钥倒序使用即可。

注意 :在.NET环境中,我们通常不会从零开始实现DES的S盒、置换表等底层逻辑。 System.Security.Cryptography 命名空间提供了高度优化且经过严格验证的 DESCryptoServiceProvider 类。自己实现底层算法更多是出于学习目的,对于生产环境,强烈建议使用这些标准库,它们经过了更充分的安全审计和性能优化。

2.2 C#中的实现策略与 DESCryptoServiceProvider

对于这个文件加密工具,我们的目标是实用和稳健。因此,我将采用混合策略: 使用.NET Framework内置的 DESCryptoServiceProvider 类来执行核心的加密解密运算,但由我们自己来控制文件的读取、写入、以及加密模式、填充方式等关键参数。 这样做有几个好处:

  1. 安全可靠 :避免了自实现算法可能引入的细微错误和安全漏洞。
  2. 高效便捷 :直接调用底层CAPI,性能有保障。
  3. 聚焦重点 :我们可以把精力集中在文件流处理、用户交互和错误处理等应用层逻辑上,这正是本项目更有价值的部分。

我们将主要使用 DESCryptoServiceProvider 来生成加密器( ICryptoTransform )对象,并结合 CryptoStream 这个强大的类,将文件流“包裹”起来,实现边读边加密或边解密边写,非常节省内存。整个项目的架构将围绕以下几个核心类展开:

  • FileEncryptor :主逻辑类,包含加密和解密的核心方法。
  • 用户交互:一个简单的控制台界面,用于接收文件路径、密钥等输入。
  • 错误处理:妥善处理诸如“密钥错误”、“文件损坏”、“路径不存在”等异常情况。

3. 项目搭建与核心类设计

明确了实现策略后,我们开始搭建项目。打开Visual Studio,创建一个新的C#控制台应用项目,命名为“DesFileEncryptor”。我们将在这个项目中组织我们的代码。

3.1 核心类: FileEncryptor 的设计

我们将创建一个静态工具类 FileEncryptor ,它提供两个主要的静态方法: EncryptFile DecryptFile 。这样设计调用起来非常直观。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace DesFileEncryptor
{
    public static class FileEncryptor
    {
        /// <summary>
        /// 使用DES算法加密文件
        /// </summary>
        /// <param name="inputFile">待加密文件路径</param>
        /// <param name="outputFile">加密后输出文件路径</param>
        /// <param name="key">加密密钥(8字节)</param>
        /// <param name="iv">初始化向量(8字节)</param>
        public static void EncryptFile(string inputFile, string outputFile, byte[] key, byte[] iv)
        {
            // 参数校验
            if (string.IsNullOrEmpty(inputFile)) throw new ArgumentException("输入文件路径不能为空");
            if (string.IsNullOrEmpty(outputFile)) throw new ArgumentException("输出文件路径不能为空");
            if (key == null || key.Length != 8) throw new ArgumentException("密钥必须为8字节数组");
            if (iv == null || iv.Length != 8) throw new ArgumentException("初始化向量必须为8字节数组");
            if (!File.Exists(inputFile)) throw new FileNotFoundException($"找不到输入文件: {inputFile}");

            // 核心加密逻辑将在这里实现
        }

        /// <summary>
        /// 使用DES算法解密文件
        /// </summary>
        /// <param name="inputFile">待解密文件路径</param>
        /// <param name="outputFile">解密后输出文件路径</param>
        /// <param name="key">解密密钥(8字节)</param>
        /// <param name="iv">初始化向量(8字节)</param>
        public static void DecryptFile(string inputFile, string outputFile, byte[] key, byte[] iv)
        {
            // 参数校验(与加密类似)
            // 核心解密逻辑将在这里实现
        }
    }
}

这里有几个关键点需要解释:

  1. 密钥(Key)和初始化向量(IV) :DES要求密钥和IV都是8字节(64位)。IV在CBC等分组模式中用于增加随机性,即使加密相同的明文,使用不同的IV也会产生完全不同的密文,这很重要。在实际使用中,IV不需要保密,但必须唯一且不可预测。通常,我们可以随机生成一个IV,并将其保存在加密文件的开头。
  2. 参数校验 :这是保证程序健壮性的第一步。必须检查输入参数的有效性,比如文件是否存在、密钥长度是否正确。这能避免很多运行时异常。
  3. 异常处理 :在文件操作和加密操作中,很多地方都可能出错(如磁盘已满、权限不足、密钥错误导致解密失败)。我们需要用 try-catch 块妥善处理这些异常,并给出对用户友好的提示。

3.2 加密模式与填充模式的选择

在实例化 DESCryptoServiceProvider 时,我们需要指定两个关键属性: Mode (加密模式)和 Padding (填充模式)。

  • 加密模式(CipherMode) :我们选择 CipherMode.CBC (密码分组链接模式)。这是最常用的模式之一。在CBC模式下,每个明文块在加密前,会先与前一个密文块进行异或操作(第一个块与IV异或)。这样,即使明文中有重复的块,加密后的密文也不会重复,安全性比ECB(电子密码本)模式高得多。ECB模式是简单的块对应加密,重复的明文会产生重复的密文,容易暴露模式, 绝对不要用于加密有意义的数据
  • 填充模式(PaddingMode) :我们选择 PaddingMode.PKCS7 (在.NET中也叫 PKCS7 )。当数据的最后一块不足8字节时,PKCS7填充会填充上缺少的字节数。例如,如果最后缺3字节,就填充三个 0x03 。解密时,会根据最后一个字节的值移除填充。这是最通用和安全的填充方式之一。

实操心得 :选择CBC模式和PKCS7填充是绝大多数场景下的最佳实践。ECB模式因为其安全性问题,几乎只存在于教科书和测试中。如果你在别人的代码里看到ECB,那是一个巨大的危险信号。

4. 核心加密与解密流程的实现

现在,我们来填充 EncryptFile DecryptFile 方法的具体实现。这是整个项目的核心。

4.1 加密流程分步实现

加密的目标是:读取原始文件,使用DES CBC模式加密,并将IV和密文一起写入新文件。一个常见的做法是将IV写在加密文件的开头,这样解密时可以先读取IV。

public static void EncryptFile(string inputFile, string outputFile, byte[] key, byte[] iv)
{
    // ... 参数校验代码同上 ...

    using (FileStream inputFileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
    using (FileStream outputFileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
    {
        // 1. 将IV写入输出文件的开头(16进制表示,方便查看,实际存储二进制即可)
        // 这里我们直接写入二进制IV。解密时需要先读取这8个字节。
        outputFileStream.Write(iv, 0, iv.Length);

        // 2. 创建DES加密器
        using (DESCryptoServiceProvider des = new DESCryptoServiceProvider())
        {
            des.Key = key;
            des.IV = iv;
            des.Mode = CipherMode.CBC; // 设置加密模式
            des.Padding = PaddingMode.PKCS7; // 设置填充模式

            // 3. 创建加密转换器
            using (ICryptoTransform encryptor = des.CreateEncryptor())
            // 4. 创建CryptoStream,链接到输出流
            using (CryptoStream cryptoStream = new CryptoStream(outputFileStream, encryptor, CryptoStreamMode.Write))
            {
                // 5. 从输入文件读取数据,并写入CryptoStream(自动加密)
                byte[] buffer = new byte[4096]; // 4KB缓冲区
                int bytesRead;
                while ((bytesRead = inputFileStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    cryptoStream.Write(buffer, 0, bytesRead);
                }
                // CryptoStream在Dispose时会自动处理最后的填充和写入。
            }
        }
    }
    Console.WriteLine($"文件加密完成:{outputFile}");
}

关键点解析

  1. IV的存储 outputFileStream.Write(iv, 0, iv.Length); 这行代码将IV以明文形式写入加密文件的开头。这是标准做法,因为IV不需要保密。解密时,我们需要先读取这8个字节。
  2. CryptoStream 的魔力: CryptoStream 是一个装饰器流。我们将原始的 outputFileStream 传给它,并指定 CryptoStreamMode.Write 。当我们向 cryptoStream 写入数据时,它会自动调用 encryptor 进行加密,然后将加密后的数据写入底层的 outputFileStream 。这样,我们用一个循环就完成了“读取-加密-写入”的流水线操作,代码非常简洁,且内存友好(使用固定大小的缓冲区)。
  3. 缓冲区大小 :这里设置了4096字节(4KB)的缓冲区。这是一个经验值,在大多数情况下能在I/O效率和内存占用之间取得良好平衡。你可以根据实际文件大小调整,比如处理超大文件时可以考虑增大到64KB或更大。

4.2 解密流程分步实现

解密是加密的逆过程:从加密文件中读取IV,然后使用相同的密钥和读取的IV来解密后续的密文数据。

public static void DecryptFile(string inputFile, string outputFile, byte[] key, byte[] ivFromFile)
{
    // ... 参数校验(注意inputFile现在是加密文件)...

    using (FileStream inputFileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
    using (FileStream outputFileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
    {
        // 1. 从加密文件的开头读取IV(前8字节)
        // 注意:这里我们假设IV存储在文件开头。如果加密时没存,这里就需要外部传入。
        byte[] fileIv = new byte[8];
        int bytesRead = inputFileStream.Read(fileIv, 0, 8);
        if (bytesRead != 8)
        {
            throw new InvalidDataException("加密文件格式错误,无法读取完整的IV。");
        }

        // 2. 创建DES解密器,使用传入的密钥和从文件读取的IV
        using (DESCryptoServiceProvider des = new DESCryptoServiceProvider())
        {
            des.Key = key;
            des.IV = fileIv; // 使用从文件读取的IV
            des.Mode = CipherMode.CBC;
            des.Padding = PaddingMode.PKCS7;

            // 3. 创建解密转换器
            using (ICryptoTransform decryptor = des.CreateDecryptor())
            // 4. 创建CryptoStream,链接到输入流(注意模式是Read)
            using (CryptoStream cryptoStream = new CryptoStream(inputFileStream, decryptor, CryptoStreamMode.Read))
            {
                // 5. 从CryptoStream读取数据(自动解密),并写入输出文件
                byte[] buffer = new byte[4096];
                int decryptedBytesRead;
                while ((decryptedBytesRead = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    outputFileStream.Write(buffer, 0, decryptedBytesRead);
                }
            }
        }
    }
    Console.WriteLine($"文件解密完成:{outputFile}");
}

关键点解析

  1. IV的读取 inputFileStream.Read(fileIv, 0, 8); 这行代码至关重要。它从加密文件的前8个字节读取IV。这要求加密和解密双方对IV的存储位置有明确的约定。我们这里的约定就是“IV存储在文件开头”。
  2. CryptoStream 模式:解密时,我们将 CryptoStream 的模式设置为 CryptoStreamMode.Read ,并将其链接到包含密文的 inputFileStream 。当我们从 cryptoStream 读取数据时,它会自动从底层流读取密文块,解密后返回给我们明文数据。我们再将这些明文数据写入 outputFileStream
  3. 错误处理 :解密过程更容易出错。如果密钥错误、IV错误或者加密文件在传输存储过程中损坏, cryptoStream.Read 操作可能会在内部抛出 CryptographicException ,提示“填充无效,无法被移除”。我们需要在调用处用 try-catch 捕获这个异常,并给用户明确的提示(如“提供的密码或密钥不正确”)。

5. 密钥管理与用户交互封装

有了核心的加密解密方法,我们还需要一个安全、方便的方式来让用户输入密钥,并管理IV。直接让用户输入8字节的数组很不友好。通常,我们会让用户输入一个密码(字符串),然后通过一个密钥派生函数(如PBKDF2)来生成固定长度的密钥和IV。

5.1 使用Rfc2898DeriveBytes派生密钥

在.NET中,我们可以使用 Rfc2898DeriveBytes 类(它实现了PBKDF2算法)来从密码和盐(Salt)派生出密钥。盐是一个随机生成的字节序列,用于防止彩虹表攻击,即使两个用户密码相同,使用不同的盐也会派生出不同的密钥。

public static (byte[] key, byte[] iv) GenerateKeyAndIVFromPassword(string password, byte[] salt)
{
    if (string.IsNullOrEmpty(password)) throw new ArgumentException("密码不能为空");
    if (salt == null || salt.Length < 8) throw new ArgumentException("盐值至少需要8字节");

    using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256))
    {
        byte[] key = deriveBytes.GetBytes(8); // DES密钥需要8字节
        byte[] iv = deriveBytes.GetBytes(8);  // DES IV需要8字节
        return (key, iv);
    }
}

参数解释

  • password :用户输入的密码字符串。
  • salt :盐值。 每次加密都应该使用一个新的随机盐 ,并和IV一样,将其保存在加密文件中(通常放在IV后面)。解密时需要提供相同的密码和盐。
  • 10000 :迭代次数。这个值越大,暴力破解的难度就越大,但派生过程也越慢。10000是一个当前公认的安全基准值。
  • HashAlgorithmName.SHA256 :使用的哈希算法。
  • GetBytes(8) :分别派生8字节的密钥和IV。

5.2 完整的加密解密流程整合

现在,我们整合密钥派生和文件操作,提供一个更用户友好的入口方法。假设我们的加密文件格式为: [Salt (16字节)][IV (8字节)][密文数据]

public static void EncryptFileWithPassword(string inputFile, string outputFile, string password)
{
    // 1. 生成随机盐和IV(为了简化,这里用随机数生成器。实际中,IV也应由密码和盐派生,但分开存储更清晰)
    byte[] salt = new byte[16];
    byte[] iv = new byte[8];
    using (var rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(salt);
        rng.GetBytes(iv);
    }

    // 2. 从密码和盐派生密钥
    (byte[] key, _) = GenerateKeyAndIVFromPassword(password, salt); // 我们只需要密钥,IV使用随机生成的

    // 3. 写入盐和IV到输出文件
    using (FileStream outputFileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
    {
        outputFileStream.Write(salt, 0, salt.Length);
        outputFileStream.Write(iv, 0, iv.Length);
    }

    // 4. 调用核心加密方法,加密剩余的数据
    EncryptFile(inputFile, outputFile, key, iv, appendMode: true); // 需要修改EncryptFile以支持追加模式
}

public static void DecryptFileWithPassword(string inputFile, string outputFile, string password)
{
    using (FileStream inputFileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
    {
        // 1. 从文件头读取盐和IV
        byte[] salt = new byte[16];
        byte[] iv = new byte[8];
        if (inputFileStream.Read(salt, 0, 16) != 16 || inputFileStream.Read(iv, 0, 8) != 8)
        {
            throw new InvalidDataException("加密文件格式错误。");
        }

        // 2. 从密码和盐派生密钥
        (byte[] key, _) = GenerateKeyAndIVFromPassword(password, salt);

        // 3. 调用核心解密方法,解密剩余的数据(从第24字节开始)
        // 这里需要一个新的方法,或者修改DecryptFile,使其能从指定的流位置开始解密
        DecryptFileFromStream(inputFileStream, outputFile, key, iv);
    }
}

注意事项 :上面的代码是一个概念展示。为了使其工作,你需要修改或重载最初的 EncryptFile DecryptFile 方法,使其能处理“追加写入盐/IV”和“从流中间开始解密”的逻辑。一个更清晰的做法是创建新的方法,或者使用 Seek 方法移动文件流的位置。

5.3 控制台用户界面

最后,我们创建一个简单的控制台程序来驱动这一切。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("=== DES文件加密解密工具 ===");
        Console.WriteLine("1. 加密文件");
        Console.WriteLine("2. 解密文件");
        Console.Write("请选择操作 (1 或 2): ");
        string choice = Console.ReadLine();

        Console.Write("请输入输入文件路径: ");
        string inputPath = Console.ReadLine();
        Console.Write("请输入输出文件路径: ");
        string outputPath = Console.ReadLine();
        Console.Write("请输入密码: ");
        string password = ReadPassword(); // 一个隐藏密码输入的函数

        try
        {
            if (choice == "1")
            {
                FileEncryptor.EncryptFileWithPassword(inputPath, outputPath, password);
                Console.WriteLine("加密成功!");
            }
            else if (choice == "2")
            {
                FileEncryptor.DecryptFileWithPassword(inputPath, outputPath, password);
                Console.WriteLine("解密成功!");
            }
            else
            {
                Console.WriteLine("无效选择。");
            }
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine($"错误:文件未找到 - {ex.Message}");
        }
        catch (CryptographicException ex)
        {
            // 最常见的解密错误:密钥或密码错误
            Console.WriteLine($"错误:解密失败。请检查密码是否正确。详细信息:{ex.Message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"发生未知错误:{ex.Message}");
        }

        Console.WriteLine("按任意键退出...");
        Console.ReadKey();
    }

    // 一个简单的实现,用于在控制台隐藏密码输入(显示为*)
    private static string ReadPassword()
    {
        string password = "";
        ConsoleKeyInfo key;
        do
        {
            key = Console.ReadKey(true);
            if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
            {
                password += key.KeyChar;
                Console.Write("*");
            }
            else if (key.Key == ConsoleKey.Backspace && password.Length > 0)
            {
                password = password.Substring(0, (password.Length - 1));
                Console.Write("\b \b");
            }
        } while (key.Key != ConsoleKey.Enter);
        Console.WriteLine();
        return password;
    }
}

6. 常见问题、调试技巧与安全考量

在实际编写和运行这个程序时,你几乎一定会遇到一些问题。下面是我在开发过程中遇到的一些典型问题及解决方法。

6.1 常见错误与排查表

错误现象 可能原因 解决方案
解密时抛出 CryptographicException: Padding is invalid and cannot be removed. 1. 密码/密钥错误 (最常见)。
2. IV不匹配 :加密和解密使用的IV不同。
3. 加密文件损坏 :文件在存储或传输中字节丢失或更改。
4. 加密模式或填充模式不匹配 :加密用CBC,解密用了ECB。
1. 仔细核对密码。
2. 确认IV的存储和读取逻辑一致。如果IV是从文件读取的,检查读取的起始位置和字节数是否正确。
3. 对比加密前后文件的哈希值(如MD5),确认文件完整性。
4. 确保 CipherMode PaddingMode 在加密解密时设置完全相同。
解密后的文件大小不正确(通常是变大了) 填充模式问题 。例如,加密时用了 PKCS7 ,解密时用了 None ,导致填充字节被当作数据写入。 确保 PaddingMode 设置一致。DES CBC + PKCS7是黄金组合。
加密大文件时程序内存占用过高或崩溃 代码中一次性将整个文件读入内存(例如用了 File.ReadAllBytes )。 必须使用流(Stream)和缓冲区进行分块处理 ,就像我们示例代码中那样。这是处理文件I/O和加密解密的正确姿势。
在非Windows系统上运行出错 DESCryptoServiceProvider 依赖于Windows的CryptoAPI。 在.NET Core / .NET 5+ 跨平台项目中,使用 DES.Create() 工厂方法,它返回一个平台无关的实现(在Windows上可能仍是 DESCryptoServiceProvider ,在Linux/macOS上是其他实现)。
密钥长度错误异常 传递给 DES.Key 的字节数组长度不是8。 检查密钥生成逻辑。如果从密码派生,确保派生出的密钥字节数是8。

6.2 调试技巧与心得

  1. 从最小单元测试开始 :不要一开始就处理大文件。先写一个测试,用固定的密钥和IV加密一个短字符串(比如“Hello, DES!”),然后解密,看是否能还原。这能快速验证你的核心加密解密逻辑是否正确。
  2. 善用十六进制查看器 :当IV或文件格式出问题时,用一个十六进制编辑器(如Visual Studio的二进制编辑器,或HxD)打开加密文件,直接查看前24个字节(盐+IV),确认它们是否和你程序里生成/读取的值一致。这是最直接的调试手段。
  3. 关注流的位置 :在混合读写操作时(如先写IV再写加密数据),务必清楚 FileStream Position 在哪里。在写入IV后,流的位置已经移动了。如果后续的加密逻辑错误地重置了位置,就会覆盖IV。使用 using 语句和正确的流构造方式可以避免很多此类问题。
  4. 异常信息是关键 CryptographicException 的信息往往能指明方向。“Padding is invalid”几乎总是意味着密钥/IV/模式错误导致解密出的最后一个块数据无法正确移除填充。

6.3 安全增强建议与局限性

我们这个工具是一个教学和轻量级使用的示例。对于真正的敏感数据,需要考虑更多:

  1. DES已过时 :DES的56位密钥强度在现代计算能力面前已经不够安全。对于新项目, 至少应使用AES(Advanced Encryption Standard) ,它支持128、192、256位密钥,安全性高得多。在C#中,只需将 DESCryptoServiceProvider 替换为 AesCryptoServiceProvider Aes.Create() ,并调整密钥和IV的长度(AES的块大小是16字节),大部分逻辑可以复用。
  2. 密码派生 :我们使用了PBKDF2,这是正确的。迭代次数(10000)可以根据需要增加(如10万次),以提升暴力破解难度,但会轻微影响性能。
  3. 完整性校验 :当前的方案只提供机密性,不提供完整性。攻击者可能篡改密文文件,导致解密出乱码(但系统可能不报错,除非填充错误)。对于需要完整性的场景,可以考虑在加密后计算一个HMAC(基于哈希的消息认证码),并将其存储在文件末尾,解密前先验证HMAC。
  4. 密钥管理 :密码仍然需要用户记忆。对于更复杂的系统,需要考虑密钥库、硬件安全模块等专业方案。

最后,把完整的项目源码整合一下。你需要创建一个C#控制台应用,包含 FileEncryptor 静态类和 Program 主程序。由于篇幅限制,这里无法贴出所有经过完整整合和错误处理的最终代码,但以上部分已经提供了所有核心模块和逻辑。你可以根据上述步骤和代码块,将它们组合成一个完整的、可编译运行的项目。记住,编程最好的学习方式就是动手,遇到问题就对照第六部分的排查表,或者用调试器一步步跟踪数据流,你一定会对这个过程有更深的理解。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐