创建加密应用程序
此演练提供用于对内容进行加密和解密的代码示例。这些代码示例是专为 Windows 窗体应用程序设计的。此应用程序并不演示实际方案(如使用智能卡),而是演示加密和解密的基础。
此演练使用下列加密准则:
-
使用 RijndaelManaged 类(一种对称算法)并利用它自动生成的 Key 和 IV 对数据进行加密和解密。
-
使用 RSACryptoServiceProvider(一种不对称算法)对 RijndaelManaged 加密的数据的密钥进行加密和解密。不对称算法最适用于少量数据,如密钥。
注意 如果您的目的是保护计算机上的数据,而不是与其他人交换加密内容,可考虑使用 ProtectedData 或ProtectedMemory 类。
下表对本主题中的加密任务进行了总结。
任务 | 说明 |
---|---|
创建 Windows 窗体应用程序 |
列出运行应用程序所需的控件。 |
声明全局对象 |
声明字符串路径变量、CspParameters 和RSACryptoServiceProvider 以获得 Form 类的全局上下文。 |
创建不对称密钥 |
创建一个不对称的公钥/私钥值对,并为其指定一个密钥容器名称。 |
加密文件 |
显示一个对话框以选择要加密的文件并对该文件进行加密。 |
解密文件 |
显示一个对话框以选择要解密的已加密文件并对该文件进行解密。 |
获取私钥 |
获取使用密钥容器名的完全密钥对。 |
导出公钥 |
将只带有公共参数的密钥保存到一个 XML 文件中。 |
导入公钥 |
将密钥从一个 XML 文件加载到密钥容器中。 |
测试应用程序 |
列出用于测试此应用程序的过程。 |
此应用程序需要引用下面的命名空间:
-
System.IO
-
System.Security.Cryptography
大多数代码示例都被设计为按钮控件的事件处理程序。下表列出了示例应用程序所需的控件以及这些控件为与代码示例匹配所需的名称:
控件 | 名称 | 文本属性(根据需要) |
---|---|---|
buttonEncryptFile |
加密文件 |
|
Button |
buttonDecryptFile |
解密文件 |
Button |
buttonCreateAsmKeys |
创建密钥 |
Button |
buttonExportPublicKey |
导出公钥 |
Button |
buttonImportPublicKey |
导入公钥 |
Button |
buttonGetPrivateKey |
获取私钥 |
label1 |
||
openFileDialog1 |
||
OpenFileDialog |
openFileDialog2 |
双击 Visual Studio 设计器中的按钮以创建按钮的事件处理程序。
将下列代码示例添加到窗体的构造函数中。编辑表示您的环境和首选项的字符串变量。
// Declare CspParmeters and RsaCryptoServiceProvider // objects with global scope of your Form class. CspParameters cspp = new CspParameters(); RSACryptoServiceProvider rsa; // Path variables for source, encryption, and // decryption folders. Must end with a backslash. const string EncrFolder = @"c:\Encrypt\"; const string DecrFolder = @"c:\Decrypt\"; const string SrcFolder = @"c:\docs\"; // Public key file const string PubKeyFile = @"c:\encrypt\rsaPublicKey.txt"; // Key container name for // private/public key value pair. const string keyName = "Key01";
此任务将创建用于对 RijndaelManaged 密钥(用于加密内容)进行加密和解密的不对称密钥,并在标签控件上显示密钥容器名称。
将下列代码示例添加为“创建密钥”按钮的单击事件处理程序:buttonCreateAsmKeys_Click。
private void buttonCreateAsmKeys_Click(object sender, System.EventArgs e) { // Stores a key pair in the key container. cspp.KeyContainerName = keyName; rsa = new RSACryptoServiceProvider(cspp); rsa.PersistKeyInCsp = true; if (rsa.PublicOnly == true) label1.Text = "Key: " + cspp.KeyContainerName + " - Public Only"; else label1.Text = "Key: " + cspp.KeyContainerName + " - Full Key Pair"; }
此任务涉及两个方法:“加密文件”按钮的事件处理程序方法 buttonEncryptFile_Click 以及 EncryptFile 方法。第一个方法将显示一个对话框以便选择某个文件并将相应的文件名传递给第二个方法,后者将执行加密操作。
将加密的内容、密钥和 IV 全部保存到一个称为加密包的 FileStream 中。
EncryptFile 方法执行下列操作:
-
创建 RijndaelManaged 对称算法以对内容进行加密。
-
创建一个 RSACryptoServiceProvider 实例以对 RijndaelManaged 密钥进行加密。
-
使用 CryptoStream 将源文件的 FileStream 读取到加密文件的目标 FileStream 中并进行加密(以字节块为单位)。
-
确定加密密钥和 IV 的长度并创建其长度值的字节数组。
-
将密钥、IV 及其长度值写入到加密包中。
加密包的格式如下:
-
密钥长度,字节 0 - 3
-
IV 长度,字节 4 - 7
-
加密密钥
-
IV
-
密码文本
通过获得密钥和 IV 的长度,可以确定加密包的所有部分的起始点和长度以便对文件进行加密。
将下列代码示例添加为“加密文件”按钮的单击事件处理程序:buttonEncryptFile_Click。
private void buttonEncryptFile_Click(object sender, System.EventArgs e) { if (rsa == null) MessageBox.Show("Key not set."); else { // Display a dialog box to select a file to encrypt. openFileDialog1.InitialDirectory = SrcFolder; if (openFileDialog1.ShowDialog() == DialogResult.OK) { string fName = openFileDialog1.FileName; if (fName != null) { FileInfo fInfo = new FileInfo(fName); // Pass the file name without the path. string name = fInfo.Name; EncryptFile(name); } } } }
向窗体中添加以下 EncryptFile 方法。
private void EncryptFile(string inFile) { // Create instance of Rijndael for // symetric encryption of the data. RijndaelManaged rjndl = new RijndaelManaged(); rjndl.KeySize = 256; rjndl.BlockSize = 256; rjndl.Mode = CipherMode.CBC; ICryptoTransform transform = rjndl.CreateEncryptor(); // Use RSACryptoServiceProvider to // enrypt the Rijndael key. byte[] keyEncrypted = rsa.Encrypt(rjndl.Key, false); // Create byte arrays to contain // the length values of the key and IV. byte[] LenK = new byte[4]; byte[] LenIV = new byte[4]; int lKey = keyEncrypted.Length; LenK = BitConverter.GetBytes(lKey); int lIV = rjndl.IV.Length; LenIV = BitConverter.GetBytes(lIV); // Write the following to the FileStream // for the encrypted file (outFs): // - length of the key // - length of the IV // - ecrypted key // - the IV // - the encrypted cipher content // Change the file's extension to ".enc" string outFile = EncrFolder + inFile.Substring(0, inFile.LastIndexOf(".")) + ".enc"; using (FileStream outFs = new FileStream(outFile, FileMode.Create)) { outFs.Write(LenK, 0, 4); outFs.Write(LenIV, 0, 4); outFs.Write(keyEncrypted, 0, lKey); outFs.Write(rjndl.IV, 0, lIV); // Now write the cipher text using // a CryptoStream for encrypting. using (CryptoStream outStreamEncrypted = new CryptoStream(outFs, transform, CryptoStreamMode.Write)) { // By encrypting a chunk at // a time, you can save memory // and accommodate large files. int count = 0; int offset = 0; // blockSizeBytes can be any arbitrary size. int blockSizeBytes = rjndl.BlockSize / 8; byte[] data = new byte[blockSizeBytes]; int bytesRead = 0; using (FileStream inFs = new FileStream(inFile, FileMode.Open)) { do { count = inFs.Read(data, 0, blockSizeBytes); offset += count; outStreamEncrypted.Write(data, 0, count); bytesRead += blockSizeBytes; } while (count > 0); inFs.Close(); } outStreamEncrypted.FlushFinalBlock(); outStreamEncrypted.Close(); } outFs.Close(); } }
此任务涉及两个方法:“解密文件”按钮的事件处理程序方法 buttonEncryptFile_Click 以及 DecryptFile 方法。第一个方法将显示一个对话框以便选择某个文件并将相应的文件名传递给第二个方法,后者将执行解密操作。
Decrypt 方法执行下列操作:
-
创建 RijndaelManaged 对称算法以对内容进行解密。
-
将加密包的 FileStream 的前 8 个字节读取到字节数组中,以获取加密密钥和 IV 的长度。
-
从加密包中将密钥和 IV 提取到字节数组中。
-
创建一个 RSACryptoServiceProvider 实例以对 RijndaelManaged 密钥进行解密。
-
使用 CryptoStream 以将 FileStream 加密包的密码文本部分读取到解密文件的 FileStream 中并进行解密(以字节块为单位)。完成此操作后,解密即告完成。
将下列代码示例添加为“解密文件”按钮的单击事件处理程序。
private void buttonDecryptFile_Click(object sender, EventArgs e) { if (rsa == null) MessageBox.Show("Key not set."); else { // Display a dialog box to select the encrypted file. openFileDialog2.InitialDirectory = EncrFolder; if (openFileDialog2.ShowDialog() == DialogResult.OK) { string fName = openFileDialog2.FileName; if (fName != null) { FileInfo fi = new FileInfo(fName); string name = fi.Name; DecryptFile(name); } } } }
向窗体中添加以下 DecryptFile 方法。
private void DecryptFile(string inFile) { // Create instance of Rijndael for // symetric decryption of the data. RijndaelManaged rjndl = new RijndaelManaged(); rjndl.KeySize = 256; rjndl.BlockSize = 256; rjndl.Mode = CipherMode.CBC; rjndl.Padding = PaddingMode.None; // Create byte arrays to get the length of // the encrypted key and IV. // These values were stored as 4 bytes each // at the beginning of the encrypted package. byte[] LenK = new byte[4]; byte[] LenIV = new byte[4]; // Consruct the file name for the decrypted file. string outFile = DecrFolder + inFile.Substring(0, inFile.LastIndexOf(".")) + ".txt"; // Use FileStream objects to read the encrypted // file (inFs) and save the decrypted file (outFs). using (FileStream inFs = new FileStream(EncrFolder + inFile, FileMode.Open)) { inFs.Seek(0, SeekOrigin.Begin); inFs.Seek(0, SeekOrigin.Begin); inFs.Read(LenK, 0, 3); inFs.Seek(4, SeekOrigin.Begin); inFs.Read(LenIV, 0, 3); // Convert the lengths to integer values. int lenK = BitConverter.ToInt32(LenK, 0); int lenIV = BitConverter.ToInt32(LenIV, 0); // Determine the start postition of // the ciphter text (startC) // and its length(lenC). int startC = lenK + lenIV + 8; int lenC = (int)inFs.Length - startC; // Create the byte arrays for // the encrypted Rijndael key, // the IV, and the cipher text. byte[] KeyEncrypted = new byte[lenK]; byte[] IV = new byte[lenIV]; // Extract the key and IV // starting from index 8 // after the length values. inFs.Seek(8, SeekOrigin.Begin); inFs.Read(KeyEncrypted, 0, lenK); inFs.Seek(8 + lenK, SeekOrigin.Begin); inFs.Read(IV, 0, lenIV); // Use RSACryptoServiceProvider // to decrypt the Rijndael key. byte[] KeyDecrypted = rsa.Decrypt(KeyEncrypted, false); // Decrypt the key. ICryptoTransform transform = rjndl.CreateDecryptor(KeyDecrypted, IV); // Decrypt the cipher text from // from the FileSteam of the encrypted // file (inFs) into the FileStream // for the decrypted file (outFs). using (FileStream outFs = new FileStream(outFile, FileMode.Create)) { int count = 0; int offset = 0; // blockSizeBytes can be any arbitrary size. int blockSizeBytes = rjndl.BlockSize / 8; byte[] data = new byte[blockSizeBytes]; // By decrypting a chunk a time, // you can save memory and // accommodate large files. // Start at the beginning // of the cipher text. inFs.Seek(startC, SeekOrigin.Begin); using (CryptoStream outStreamDecrypted = new CryptoStream(outFs, transform, CryptoStreamMode.Write)) { do { count = inFs.Read(data, 0, blockSizeBytes); offset += count; outStreamDecrypted.Write(data, 0, count); } while (count > 0); outStreamDecrypted.FlushFinalBlock(); outStreamDecrypted.Close(); } outFs.Close(); } inFs.Close(); } }
此任务将加载“导出公钥”按钮创建的密钥(仅带有公共参数)并将其设置为密钥容器名称。
此任务模拟这样一种情况:小明将加载小红提供的仅带有公共参数的密钥,以便他能够对发送给小红的文件进行加密。
将下列代码示例添加为“导入公钥”按钮的单击事件处理程序:buttonImportPublicKey_Click。
void buttonImportPublicKey_Click(object sender, System.EventArgs e) { StreamReader sr = new StreamReader(PubKeyFile); cspp.KeyContainerName = keyName; rsa = new RSACryptoServiceProvider(cspp); string keytxt = sr.ReadToEnd(); rsa.FromXmlString(keytxt); rsa.PersistKeyInCsp = true; if (rsa.PublicOnly == true) label1.Text = "Key: " + cspp.KeyContainerName + " - Public Only"; else label1.Text = "Key: " + cspp.KeyContainerName + " - Full Key Pair"; sr.Close(); }
此任务将密钥容器名称设置为使用“创建密钥”按钮创建的密钥的名称。密钥容器将包含带有私有参数的完全密钥对。
此任务模拟这样一种情况:小红使用自己的私钥对小明加密的文件进行解密。
将下列代码示例添加为“获取私钥”按钮的单击事件处理程序:buttonGetPrivateKey_Click。
private void buttonGetPrivateKey_Click(object sender, EventArgs e) { cspp.KeyContainerName = keyName; rsa = new RSACryptoServiceProvider(cspp); rsa.PersistKeyInCsp = true; if (rsa.PublicOnly == true) label1.Text = "Key: " + cspp.KeyContainerName + " - Public Only"; else label1.Text = "Key: " + cspp.KeyContainerName + " - Full Key Pair"; }
生成应用程序之后,执行下列测试方案。
创建密钥、加密和解密
-
单击“创建密钥”按钮。标签将显示密钥名称,并显示此密钥是一个完全密钥对。
-
单击“导出公钥”按钮。请注意,导出公钥参数不会更改当前密钥。
-
单击“加密文件”按钮并选择一个文件。
-
单击“解密文件”按钮并选择刚刚加密的文件。
-
检查刚刚解密的文件。
-
关闭并重新启动应用程序以在下一个方案中对检索保留的密钥容器进行测试。
使用公钥进行加密
-
单击“导入公钥”按钮。标签将显示密钥名称,并显示此密钥仅是公用的。
-
单击“加密文件”按钮并选择一个文件。
-
单击“解密文件”按钮并选择刚刚加密的文件。此操作将失败,因为您必须具有私钥才能进行解密。
此方案演示了仅使用公钥对发送给其他人的文件进行加密的情况。通常,此人将仅为您提供公钥,而保留用于解密的私钥。
使用私钥进行解密
-
单击“获取私钥”按钮。标签将显示密钥名称,并显示此密钥是否为一个完全密钥对。
-
单击“解密文件”按钮并选择刚刚加密的文件。此操作将成功,因为您具有用于解密的完全密钥对。