本文概述
如果用户的文件包含敏感信息, 则可以对其进行加密, 以便除了用户本身之外, 其他任何人都不能打开该文件。加密文件会使任何人在没有密码的情况下都难以访问和阅读它们。如果你是项目中的加密主题, 我们将在本文中向你展示如何轻松地使用AES算法对文件进行加密和解密。
注意
本文介绍了一种使用诸如加密和解密之类的简单方法轻松快速地对文件进行加密和解密的方法。它们是从Stack Overflow, Security Exchange和MSDN官方网站等不同来源重新收集信息的结果。
1.导入所需的类型
为了在你的项目上处理AES加密算法以加密和解密文件, 请导入以下2种必需的类型:
using System.Security.Cryptography;
using System.Runtime.InteropServices;
在类顶部对InteropServices的引用将使你以后可以在类中使用DllImport方法。
2.创建加密和解密方法
你需要将以下3种方法添加到你的类中(或在新类中创建它们, 然后将它们导入你的类中):
- GenerateRandomSalt:此方法创建随机盐。此功能是可自定义的, 你可以根据需要对其进行修改以创建自己的盐。
- FileEncrypt:此方法使用普通密码加密现有文件。
- FileDecrypt:此方法使用纯密码作为参数, 使用FileEncrypt方法解密以前加密的文件。
- ZeroMemory:此方法将用于从内存中删除密码, 从而提高加密的安全性。不必要, 但建议使用。
该方法将在第3步中使用和说明, 目前, 将这些方法复制并包含在你的项目中:
// Call this function to remove the key from memory after use for security
[DllImport("KERNEL32.DLL", EntryPoint = "RtlZeroMemory")]
public static extern bool ZeroMemory(IntPtr Destination, int Length);
/// <summary>
/// Creates a random salt that will be used to encrypt your file. This method is required on FileEncrypt.
/// </summary>
/// <returns></returns>
public static byte[] GenerateRandomSalt()
{
byte[] data = new byte[32];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
for (int i = 0; i < 10; i++)
{
// Fille the buffer with the generated data
rng.GetBytes(data);
}
}
return data;
}
/// <summary>
/// Encrypts a file from its path and a plain password.
/// </summary>
/// <param name="inputFile"></param>
/// <param name="password"></param>
private void FileEncrypt(string inputFile, string password)
{
//http://stackoverflow.com/questions/27645527/aes-encryption-on-large-files
//generate random salt
byte[] salt = GenerateRandomSalt();
//create output file name
FileStream fsCrypt = new FileStream(inputFile + ".aes", FileMode.Create);
//convert password string to byte arrray
byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password);
//Set Rijndael symmetric encryption algorithm
RijndaelManaged AES = new RijndaelManaged();
AES.KeySize = 256;
AES.BlockSize = 128;
AES.Padding = PaddingMode.PKCS7;
//http://stackoverflow.com/questions/2659214/why-do-i-need-to-use-the-rfc2898derivebytes-class-in-net-instead-of-directly
//"What it does is repeatedly hash the user password along with the salt." High iteration counts.
var key = new Rfc2898DeriveBytes(passwordBytes, salt, 50000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
//Cipher modes: http://security.stackexchange.com/questions/52665/which-is-the-best-cipher-mode-and-padding-mode-for-aes-encryption
AES.Mode = CipherMode.CFB;
// write salt to the begining of the output file, so in this case can be random every time
fsCrypt.Write(salt, 0, salt.Length);
CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateEncryptor(), CryptoStreamMode.Write);
FileStream fsIn = new FileStream(inputFile, FileMode.Open);
//create a buffer (1mb) so only this amount will allocate in the memory and not the whole file
byte[] buffer = new byte[1048576];
int read;
try
{
while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0)
{
Application.DoEvents(); // -> for responsive GUI, using Task will be better!
cs.Write(buffer, 0, read);
}
// Close up
fsIn.Close();
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
finally
{
cs.Close();
fsCrypt.Close();
}
}
/// <summary>
/// Decrypts an encrypted file with the FileEncrypt method through its path and the plain password.
/// </summary>
/// <param name="inputFile"></param>
/// <param name="outputFile"></param>
/// <param name="password"></param>
private void FileDecrypt(string inputFile, string outputFile, string password)
{
byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password);
byte[] salt = new byte[32];
FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
fsCrypt.Read(salt, 0, salt.Length);
RijndaelManaged AES = new RijndaelManaged();
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, salt, 50000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Padding = PaddingMode.PKCS7;
AES.Mode = CipherMode.CFB;
CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateDecryptor(), CryptoStreamMode.Read);
FileStream fsOut = new FileStream(outputFile, FileMode.Create);
int read;
byte[] buffer = new byte[1048576];
try
{
while ((read = cs.Read(buffer, 0, buffer.Length)) > 0)
{
Application.DoEvents();
fsOut.Write(buffer, 0, read);
}
}
catch (CryptographicException ex_CryptographicException)
{
Console.WriteLine("CryptographicException error: " + ex_CryptographicException.Message);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
try
{
cs.Close();
}
catch (Exception ex)
{
Console.WriteLine("Error by closing CryptoStream: " + ex.Message);
}
finally
{
fsOut.Close();
fsCrypt.Close();
}
}
它们不一定是完美的, 可以(并且需要)对其进行修改以处理更多异常, 以防它们出现以及你如何使用应用程序。
3.使用方法
从所需的方法中, 你显然只需要使用其中2个(FileEncrypt和FileDecrypt), 并且其中1个是可选的, 第四个(GenerateRandomSalt)由FileEncrypt方法在内部使用。
加密档案
使用FileEncrypt方法对文件加密, 该方法期望将要加密的文件的路径作为第一个参数, 并将第二个参数用作加密文件的密码。以后可以使用密码解密文件。为使一切正常, 建议你使用ZeroMemory方法从内存中删除密码。出于安全目的, 调用此函数以在使用后从内存中删除密钥:
string password = "ThePasswordToDecryptAndEncryptTheFile";
// For additional security Pin the password of your files
GCHandle gch = GCHandle.Alloc(password, GCHandleType.Pinned);
// Encrypt the file
FileEncrypt(@"C:\Users\username\Desktop\wordFileExample.doc", password);
// To increase the security of the encryption, delete the given password from the memory !
ZeroMemory(gch.AddrOfPinnedObject(), password.Length * 2);
gch.Free();
// You can verify it by displaying its value later on the console (the password won't appear)
Console.WriteLine("The given password is surely nothing: " + password);
FileEncrypt方法将在原始文件的同一目录中生成文件, 其扩展名为aes(例如wordFileExample.doc)。
解密文件
要解密文件, 我们将遵循相同的过程, 只是使用FileDecrypt。此方法将加密文件的路径作为第一个参数, 将解密文件应放置的路径作为第二个参数。作为第三个参数, 你需要提供最初用于加密文件的字符串:
string password = "ThePasswordToDecryptAndEncryptTheFile";
// For additional security Pin the password of your files
GCHandle gch = GCHandle.Alloc(password, GCHandleType.Pinned);
// Decrypt the file
FileDecrypt(@"C:\Users\sdkca\Desktop\example.doc.aes", @"C:\Users\sdkca\Desktop\example_decrypted.doc", password);
// To increase the security of the decryption, delete the used password from the memory !
ZeroMemory(gch.AddrOfPinnedObject(), password.Length * 2);
gch.Free();
// You can verify it by displaying its value later on the console (the password won't appear)
Console.WriteLine("The given password is surely nothing: " + password);
最后的笔记
- 加密/解密过程会消耗大量内存, 并且需要花费时间, 因此建议在另一个线程中运行这些任务, 以防止主UI冻结。
- 可以将扩展名aes更改为所需的扩展名。
编码愉快!
评论前必须登录!
注册