应用场景
在很多情况下,我们希望对一些比较私密的文件进行加密,当自己需要查看的时候,再解密出来。因此,今天与大家分享一款自己以前用WPF开发的文件加密解密软件。
准备工作
开发环境:VisualStudio2019;项目类型:WPF应用程序;加密类型:AES加密。
效果预览
程序初始界面:
这里以选择文件夹加密举例,如图,选择需要加密的文件夹后界面:
输入密钥,进行加密:
此时,我们打开加密后的文件夹:
我们把"dog(加密文件).ico"文件用记事本打开看一下:
可以看到文件已经被加密了,现在回到我们的程序,选择刚才加密过的文件夹:
输入密钥,开始解密:
这时,再去访问我们解密后的文件夹,可以看到,很明显,文件已经全部解密完毕。
代码开始
界面布局这里就不多说了,读者可以根据笔者提供的预览界面自行参考布局。
首先,我们新建几个全局变量:
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;
}
到此,文件加密解密的软件我们就做完了。如果有朋友需要源码