应用场景

在很多情况下,我们希望对一些比较私密的文件进行加密,当自己需要查看的时候,再解密出来。因此,今天与大家分享一款自己以前用WPF开发的文件加密解密软件。

准备工作

开发环境:VisualStudio2019;项目类型:WPF应用程序;加密类型:AES加密。

效果预览

程序初始界面:

AES256文件加密 aes文件加密解密工具_c#


这里以选择文件夹加密举例,如图,选择需要加密的文件夹后界面:

AES256文件加密 aes文件加密解密工具_FileStream_02


输入密钥,进行加密:

AES256文件加密 aes文件加密解密工具_Text_03


此时,我们打开加密后的文件夹:

AES256文件加密 aes文件加密解密工具_Text_04


AES256文件加密 aes文件加密解密工具_wpf_05


我们把"dog(加密文件).ico"文件用记事本打开看一下:

AES256文件加密 aes文件加密解密工具_FileStream_06


可以看到文件已经被加密了,现在回到我们的程序,选择刚才加密过的文件夹:

AES256文件加密 aes文件加密解密工具_FileStream_07


输入密钥,开始解密:

AES256文件加密 aes文件加密解密工具_FileStream_08


这时,再去访问我们解密后的文件夹,可以看到,很明显,文件已经全部解密完毕。

AES256文件加密 aes文件加密解密工具_FileStream_09


AES256文件加密 aes文件加密解密工具_AES256文件加密_10

代码开始

界面布局这里就不多说了,读者可以根据笔者提供的预览界面自行参考布局。
首先,我们新建几个全局变量:

private Thread thdEncryptData = null;  //加密进程
        private Thread thdDecodeData = null;  //解密进程
        private List<string> lstFileNames = null;  //所有文件名List
        private string strChooseFolderName = "";  //选择的文件夹路径名

选择文件或文件夹

这里就是把选择文件或文件夹后所有相应的文件名添加到lstFileNames里,同时显示到界面上。

private void btn_OpenFiles_Click(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();
            ofd.Title = "选择需要加密或解密的文件";
            ofd.Filter = "所有文件(*.*)|*.*";

            ofd.Multiselect = true;

            if (ofd.ShowDialog() == true)
            {
                if (lstFileNames != null)
                {
                    lstFileNames.Clear();
                    lb_FilesName.Items.Clear();        
                }
                lstFileNames = new List<string>();

                foreach (string fileName in ofd.FileNames)
                {
                    lstFileNames.Add(fileName);
                    lb_FilesName.Items.Add(fileName);
                }
            }

            txb_FolderName.Text = "";
            strChooseFolderName = "";
        }

        private void btn_OpenFolder_Click(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.Description = "选择需要加密或解密的文件所在的文件夹";
            fbd.ShowNewFolderButton = false;
            //fbd.RootFolder = Environment.SpecialFolder.Desktop;

            fbd.ShowDialog();
            if (fbd.SelectedPath == string.Empty)
            {
                return;
            }

            if (lstFileNames != null)
            {
                lstFileNames.Clear();
                lb_FilesName.Items.Clear();      
            }
            lstFileNames = new List<string>();

            GetDirectory(fbd.SelectedPath);

            txb_FolderName.Text = "所选文件夹路径:" + fbd.SelectedPath;
            strChooseFolderName = fbd.SelectedPath;
        }

选择文件夹的话,因为选择的文件夹还有可能有子文件夹,所以需要递归遍历到所有文件。

/// <summary>
        /// 获得指定路径下所有子目录名
        /// </summary>
        /// <param name="path">文件夹路径</param>
        public void GetDirectory(string path)
        {
            GetFileName(path);
            DirectoryInfo directoryInfo = new DirectoryInfo(path);
            foreach (DirectoryInfo di in directoryInfo.GetDirectories())
            {
                GetDirectory(di.FullName);
            }
        }

        /// <summary>
        /// 获得指定路径下所有文件名
        /// </summary>
        /// <param name="path">文件夹路径</param>
        public void GetFileName(string path)
        {
            DirectoryInfo di = new DirectoryInfo(path);
            foreach (FileInfo fi in di.GetFiles())
            {
                lstFileNames.Add(fi.FullName);
                lb_FilesName.Items.Add(fi.FullName);
            }
        }

加密开始

开始加密时,考虑到实际应用,这里我们分情况处理加密文件还是加密文件夹。

private void btn_Encrypt_Click(object sender, RoutedEventArgs e)
        {
            if(lstFileNames == null)
            {
                System.Windows.MessageBox.Show("请先选择需要加密的文件!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            if (lstFileNames.Count == 0)
            {
                System.Windows.MessageBox.Show("请先选择需要加密的文件!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            if(txt_Key.Text == "")
            {
                System.Windows.MessageBox.Show("请先输入加密密钥!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if(strChooseFolderName != "")
            {
                string generateFilePath = strChooseFolderName;
                string key = SupplementPassword(txt_Key.Text.ToString());
                EncryptFiles(key, generateFilePath);
            }
            else
            {
                SaveFileDialog sfd = new SaveFileDialog();
                sfd.Title = "请选择加密后文件的保存路径";
                sfd.FileName = "Encryption files";
                //sfd.Filter = "加密文件(*.ept)|*.ept";
                sfd.RestoreDirectory = true;

                if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    string generateFileName = sfd.FileName.ToString();
                    string generateFilePath = generateFileName.Substring(0, generateFileName.LastIndexOf("\\")) + "\\";
                    string key = SupplementPassword(txt_Key.Text.ToString());
                    EncryptFiles(key, generateFilePath);
                }
            }
        }

加密文件

加密文件时,为了不影响主UI进程(防止界面卡死),我们新开一个进程进行加密。

public void EncryptFiles(string key, string path)
        {
            EncryptInfo encryptInfo = new EncryptInfo()
            {
                Key = key,
                Path = path
            };
            thdEncryptData = new Thread(new ParameterizedThreadStart(ThreadEncryptData));
            thdEncryptData.IsBackground = true;
            thdEncryptData.SetApartmentState(ApartmentState.STA);
            thdEncryptData.Start(encryptInfo);
        }

		private void ThreadEncryptData(Object obj)
        {
            EncryptInfo encryptInfo = obj as EncryptInfo;
            double count = Math.Round(100 / Convert.ToDouble(lstFileNames.Count), 2);

            Dispatcher.Invoke(new Action(() =>
            {
                lbl_Progress.Content = "开始加密。。。";
            }));

            if (strChooseFolderName != "")
            {
                for (int i = 0; i < lstFileNames.Count; i++)
                {
                    string fileName = lstFileNames[i].Replace(strChooseFolderName, "");
                    string[] cfile1 = fileName.Split('\\');
                    string[] cfile2 = cfile1[cfile1.Length - 1].Split('.');
                    string nfile = fileName.Substring(0, fileName.LastIndexOf("\\"));
                    string cfile = cfile2[0] + "(加密文件)";
                    for(int j = 1; j < cfile2.Length; j++)
                    {
                        cfile += "." + cfile2[j];
                    }
                    string cfileName = encryptInfo.Path + "\\" + nfile + "\\" + cfile;

                    FileStream fsRead = new FileStream(lstFileNames[i], FileMode.Open);
                    byte[] dataRead = new byte[fsRead.Length];
                    fsRead.Read(dataRead, 0, (int)fsRead.Length);
                    byte[] resultData = Encrypt(dataRead, encryptInfo.Key);
                    fsRead.Close();

                    FileStream fsCreate = new FileStream(cfileName, FileMode.Create);
                    fsCreate.Write(resultData, 0, resultData.Length);
                    fsCreate.Close();

                    File.Delete(lstFileNames[i]);

                    Dispatcher.Invoke(new Action(() =>
                    {
                        pb_Progress.Value = (i + 1) * count;
                    }));
                }
            }
            else
            {
                for (int i = 0; i < lstFileNames.Count; i++)
                {
                    string fileName = lstFileNames[i];
                    string[] cfile1 = fileName.Split('\\');
                    string[] cfile2 = cfile1[cfile1.Length - 1].Split('.');
                    string cfileName = encryptInfo.Path + cfile2[0] + "(加密文件)." + cfile2[1];

                    FileStream fsRead = new FileStream(fileName, FileMode.Open);
                    byte[] dataRead = new byte[fsRead.Length];
                    fsRead.Read(dataRead, 0, (int)fsRead.Length);
                    byte[] resultData = Encrypt(dataRead, encryptInfo.Key);
                    fsRead.Close();

                    FileStream fsCreate = new FileStream(cfileName, FileMode.Create);
                    fsCreate.Write(resultData, 0, resultData.Length);
                    fsCreate.Close();

                    Dispatcher.Invoke(new Action(() =>
                    {
                        pb_Progress.Value = (i + 1) * count;
                    }));
                }
            }

            Dispatcher.Invoke(new Action(() =>
            {
                pb_Progress.Value = 100;
                lbl_Progress.Content = "";
                System.Windows.MessageBox.Show("所有文件已加密完毕。", "信息", MessageBoxButton.OK, MessageBoxImage.Information);
            }));
        }

这里我把加密的进度以进度条的方式呈现在了界面上,多说一句,如果在C#的其他进程里想要访问UI元素,那么需要采用Dispather.Invoke的方法。

解密

解密与加密实现类似,这里只贴出关键代码:

/// <summary>
        /// 解密
        /// </summary>
        /// <param name="array">要解密的byte[]数组</param>
        /// <param name="key">密钥</param>
        /// <returns>解密后的byte数组</returns>
        public byte[] Decrypt(byte[] array, string key)
        {
            byte[] keyArray = UTF8Encoding.UTF8.GetBytes(key);

            RijndaelManaged rDel = new RijndaelManaged();
            rDel.Key = keyArray;
            rDel.Mode = CipherMode.ECB;
            rDel.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = rDel.CreateDecryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(array, 0, array.Length);

            return resultArray;
        }

细节

有的朋友可能知道,采用AES加密的话,如果输入的密钥不是32位,那么加密可能无法继续进行。所以这里我们把用户输入的密钥再进行一层处理,即上面曾引用到的SupplementPassword方法。

public string SupplementPassword(string key)
        {
            string sKey = key;
            if (key.Length > 32)
            {
                key = key.Substring(0, 32);
            }
            else if (key.Length < 32)
            {
                for (int i = 0; i < 32 - key.Length; i++)
                {
                    sKey += "#";
                }
            }
            return sKey;
        }

到此,文件加密解密的软件我们就做完了。如果有朋友需要源码