数据的加密与解密
文件的加密与解密
第 章
加密与解密技术
第19章 加密与解密技术
829
19.1 数据的加密与解密
实例571
异或算法对数字进行加密与解密
光盘位置:光盘\MR\19\571
中级
趣味指数:
实 例说明
在实现本实例之前先来简要了解一下加密的概念,加密是指通过
某种特殊的方法,更改已有信息的内容,使得未授权的用户即使得到
了加密信息,如果没有正确解密的方法,也无法得到信息的内容。谈
到加密的话题,一些读者一定非常感兴趣,而且会联想到复杂的加密
算法,本实例主要使用异或“^”运算符简单地实现了对数字加密的
功能。实例运行效果如图19.1 所示。
关 键技术
本实例实现时主要使用了“异或”运算符对数字进行“异或”运
算,以达到简单加密数字的目的,下面对其进行详细讲解。
“异或”运算符“^”用于比较两个二进制数的相应位。在执行按位“异或”运算时,如果两个二进制数的
相应位都为1 或两个二进制数的相应位都为0,则返回0;如果两个二进制数的相应位其中一个为1 一个为0,
则返回1。
现在来了解一下使用“异或”加密或解密的执行过程,数值23 转换为二进制为10111,加密数字的数值15
转换为二进制为1111。对比两个二进制的值,从右向左按位对比,如果两个二进制数的相应位都为1 或两个二
进制数的相应位都为0,则返回0;如果两个二进制数的相应位中一个为1 一个为0,则返回1,最后得到的结
果为二进制值11000,该值转换为十进制为24,所以得到的加密结果为24。而解密过程也很简单,只是将加密
结果24与加密数字15 进行“异或”运算,将24 转换为二进制值11000,将15 转换为二进制值1111,进行“异
或”运算后,得到结果为23,这样又还原了加密的数据。
说明:本实例只是简单地使用了“异或”运算符计算两个整型数值以达到加密的目的,所以本实例只可以
对整型数值进行加密运算,并不适合其他数据的加密。
设 计过程
(1)打开Visual Studio 2008 开发环境,新建一个Windows窗体应用程序,并将其命名为Encrypt。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加两个GroupBox 容器控件,其中,
在第一个GroupBox 中放入3 个TextBox 控件和一个Button 按钮,分别用于输入数字、输入加密数字、显示加
密后的数字和计算加密信息;在第二个GroupBox 中放入一个TextBox 控件和一个Button 按钮,分别用于显示
解密后的信息和计算解密信息。
(3)程序主要代码如下:
private void btn_Encrypt_Click(object sender, EventArgs e)
{
int P_int_Num, P_int_Key; //定义两个值类型变量
if (int.TryParse(txt_Num.Text, out P_int_Num) //判断输入是否是数值
&& int.TryParse(txt_Key.Text, out P_int_Key))
{
txt_Encrypt.Text = (P_int_Num ^ P_int_Key).ToString(); //加密数值
}
else
图19.1 异或算法对数字进行加密与解密
C#开发实战1200 例(第II卷)
830
{
MessageBox.Show("请输入数值", "出现错误!"); //提示输入信息不正确
}
}
private void btn_Revert_Click(object sender, EventArgs e)
{
int P_int_Key, P_int_Encrypt; //定义两个值类型变量
if (int.TryParse(txt_Encrypt.Text, out P_int_Key) //判断输入是否是数值
&& int.TryParse(txt_Key.Text, out P_int_Encrypt))
{
txt_Revert.Text = (P_int_Encrypt ^ P_int_Key).ToString(); //解密数值
}
else
{
MessageBox.Show("请输入数值", "出现错误!"); //提示输入信息不正确
}
}
秘 笈心法
心法领悟571:简述“异或”运算符。
本实例使用了“异或”运算符,但是在使用“异或”运算符之前,有必要了解“异或”运算符所做的“异
或”运算的机制,“异或”运算符“^”用于比较两个二进制数的相应位。在执行按位“异或”运算时,如果两
个二进制数的相应位都为1 或两个二进制数的相应位都为0,则返回0;如果两个二进制数的相应位中一个为1
一个为0,则返回1。
实例572
使用MD5算法加密数据
光盘位置:光盘\MR\19\572
中级
趣味指数:
实 例说明
MD5(Message-Digest Algorithm 5)是一种被广泛使用的“消息-摘要
算法”。“消息-摘要算法”实际上就是一个单项散列函数,数据块通过单
向散列函数得到一个固定长度的散列值,数据块的签名就是计算数据块的散
列值,MD5 算法的散列值为128 位。本实例演示如何使用MD5 算法对用户
输入的密码进行加密,实例运行效果如图19.2 所示。
关 键技术
本实例在实现时主要用到了MD5类的ComputeHash 方法,下面对其进行详细讲解。
MD5 类表示MD5 哈希算法的所有实现均从中继承的抽象类,该类位于System.Security.Cryptography 命名
空间下,其ComputeHash 方法有3种重载形式,分别介绍如下。
计算指定字节数组的哈希值,语法格式如下:
public byte[] ComputeHash(byte[] buffer)
参数说明
buffer:要计算其哈希代码的输入。
返回值:计算所得的哈希代码。
计算指定Stream 对象的哈希值,语法格式如下:
public byte[] ComputeHash(Stream inputStream)
参数说明
inputStream:要计算其哈希代码的输入。
返回值:计算所得的哈希代码。
图19.2 使用MD5 算法加密数据
第19章 加密与解密技术
831
计算指定字节数组的指定区域的哈希值,语法格式如下:
public byte[] ComputeHash(byte[] buffer,int offset,int count)
ComputeHash 方法中的参数及说明如表19.1 所示。
表19.1 ComputeHash方法中的参数及说明
参 数 说 明
buffer 要计算其哈希代码的输入
offset 字节数组中的偏移量,从该位置开始使用数据
count 数组中用作数据的字节数
返回值 计算所得的哈希代码
说明:本实例用到了ComputeHash 方法的第一种重载形式。
设 计过程
(1)打开Visual Studio 2008 开发环境,新建一个Windows窗体应用程序,并将其命名为MD5Arithmetic。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加两个TextBox 控件,分别用来输入
要加密的数据和显示加密后的字符串;添加一个Button 控件,用来使用MD5算法对输入的数据进行加密。
(3)程序主要代码如下:
public string Encrypt(string strPwd)
{
MD5 md5 = new MD5CryptoServiceProvider(); //创建MD5 对象
byte[] data = System.Text.Encoding.Default.GetBytes(strPwd); //将字符编码为一个字节序列
byte[] md5data = md5.ComputeHash(data); //计算data字节数组的哈希值
md5.Clear(); //清空MD5 对象
string str = ""; //定义一个变量,用来记录加密后的密码
for (int i = 0; i < md5data.Length - 1; i++) //遍历字节数组
{
str += md5data[i].ToString("x").PadLeft(2, '0'); //对遍历到的字节进行加密
}
return str; //返回得到的加密字符串
}
private void button1_Click(object sender, EventArgs e)
{
string P_str_Code = textBox1.Text; //记录要加密的密码
textBox2.Text = Encrypt(P_str_Code); //显示加密后的字符串
}
秘 笈心法
心法领悟572:如何判断是否为数字?
开发程序时,经常需要判断输入的字符串是否为数字,如判断输入的电话号码、货币金额和邮编等。在程
序中判断是否为数字的方法有很多种,可以使用正则表达式、int.Parse 方法和double.Parse 方法等。下面的代码
通过double.Parse 方法判断textBox1 文本框中的输入是否为数字。
double.Parse(textBox1.Text);
实例573
使用ROT13算法加密解密数据
光盘位置:光盘\MR\19\573
中级
趣味指数:
实 例说明
文件加密可以避免造成重要信息的泄漏,复杂的加密算法可以将信息加密得非常繁杂,但是对于一般的应
用,没有必要作类似于PGP、RSA 或DES 等复杂的加密算法。本实例介绍如何使用ROT13 算法加密和解密数
C#开发实战1200 例(第II卷)
832
据。实例运行效果如图19.3 所示。
图19.3 使用ROT13算法加密解密数据
关 键技术
本实例实现时,主要是用Convert 类的ToChar 方法来获取单个字符的Unicode 编码,然后将字母的前13
个和后13 个对调,从而实现加密的功能。下面对Convert类的ToChar 方法进行详细讲解。
ToChar 方法返回指定的Unicode字符值,并且不执行任何实际的转换,其语法格式如下:
public static char ToChar (char value)
参数说明
value:一个Unicode 字符。
设 计过程
(1)打开Visual Studio 2008 开发环境,新建一个Windows窗体应用程序,并将其命名为ROT13Encrypt。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加两个TextBox 控件,分别用来显示
原始数据和解密后的数据;添加两个Button 控件,分别用来实现利用ROT13算法加密和解密数据的功能。
(3)程序主要代码如下:
public string ROT13Encode(string InputText)
{
char tem_Character; //存储临时字符
int UnicodeChar; //存储临时字符的字节值
string EncodedText = ""; //存储加密或解密后的字符串
for (int i = 0; i < InputText.Length; i++) //遍历字符串中的所有字符,只能加密字符串,无法加密汉字
{
tem_Character = System.Convert.ToChar(InputText.Substring(i, 1)); //获取字符串中指定的字符
UnicodeChar = (int)tem_Character; //获取当前字符的Unicode 编码
if (UnicodeChar >= 97 && UnicodeChar <= 109) //对字符进行加密
{
UnicodeChar = UnicodeChar + 13;
}
else if (UnicodeChar >= 110 && UnicodeChar <= 122) //对字符进行解密
{
UnicodeChar = UnicodeChar - 13;
}
else if (UnicodeChar >= 65 && UnicodeChar <= 77) //对字符进行加密
{
UnicodeChar = UnicodeChar + 13;
}
else if (UnicodeChar >= 78 && UnicodeChar <= 90) //对字符进行解密
{
UnicodeChar = UnicodeChar - 13;
}
EncodedText = EncodedText + (char)UnicodeChar; //得到加密或解密字符串
}
return EncodedText; //返回加密或解密后的字符串
}
秘 笈心法
心法领悟573:如何在字符串中查找指定字符?
在字符串中查找指定字符时,可以先将字符串显示在richTextBox 控件中,然后利用richTextBox 类的Find
方法在该控件中查找指定字符。在字符串中查找指定字符的代码如下:
第19章 加密与解密技术
833
M_int_index = richTextBox1.Find(textBox1.Text.Trim(), M_int_index, RichTextBoxFinds.MatchCase);
if (M_int_index == -1)
{
MessageBox.Show("没有要查找的字符串", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
M_int_index = 0;
}
else
M_int_index = M_int_index + textBox1.Text.Trim().Length;
richTextBox1.Focus();
实例574
使用恺撒密码算法加密密码
光盘位置:光盘\MR\19\574
中级
趣味指数:
实 例说明
恺撒密码据传是古罗马恺撒大帝用来保护重要军情的加密系统,它
是一种置换密码,通过将字母顺序推后起到加密作用。例如,将字母顺
序推后3 位,字母A 将被推作为字母D,字母B 将被推作字母E。本实
例使用C#实现了恺撒加密的算法,实例运行效果如图19.4 所示。
关 键技术
本实例实现时主要用到了string 类的ToCharArray 方法和Convert 类的ToChar 方法,下面分别对它们进行
详细介绍。
(1)string类的ToCharArray 方法
string类的ToCharArray 方法用来将字符串中的字符复制到Unicode 字符数组,该方法有两种重载形式,本
实例中用到的它的重载形式如下:
public char[] ToCharArray()
参数说明
返回值:元素为此字符串的各字符的Unicode 字符数组。如果此字符串是空字符串,则返回的数组为空且
长度为零。
(2)Convert 类的ToChar 方法
Convert 类的ToChar 方法用来将指定的值转换为Unicode 字符,该方法为可重载方法,本实例中用到的它
的重载形式如下:
public static char ToChar(int value)
参数说明
value:32 位有符号整数。
返回值:等效于value 的值的Unicode 字符。
设 计过程
(1)打开Visual Studio 2008开发环境,新建一个Windows窗体应用程序,并将其命名为CaesarArithmetic。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加两个TextBox 控件,分别用来输入
要加密的数据和显示加密后的字符串;添加一个Button 控件,用来使用恺撒密码算法对输入的数据进行加密。
(3)程序主要代码如下:
public int AscII(string str) //获取字符的ASCII 码
{
byte[] array = new byte[1]; //创建字节数组
array = System.Text.Encoding.ASCII.GetBytes(str); //为字节数组赋值
int asciicode = (short)(array[0]); //获取字节数组的第一项
return asciicode; //返回字节数组的第一项
}
图19.4 使用恺撒密码算法加密密码
C#开发实战1200 例(第II卷)
834
public string Caesar(string str) //凯撒加密算法的实现
{
char[] c = str.ToCharArray(); //创建字符数组
string strCaesar = ""; //定义一个变量,用来存储加密后的字符串
for (int i = 0; i < str.Length; i++) //遍历字符串中的每一个字符串
{
string ins = c[i].ToString(); //记录遍历到的字符
string outs = ""; //定义一个变量,用来记录加密后的字符串
bool isChar = "0123456789abcdefghijklmnopqrstuvwxyz".Contains(ins.ToLower()); //判断指定的字符串中是否包含遍历到的字符
bool isToUpperChar = isChar && (ins.ToUpper() == ins); //判断遍历到的字符是否是大写
ins = ins.ToLower(); //将遍历到的字符转换为小写
if (isChar) //判断指定的字符串中是否包含遍历到的字符
{
int offset = (AscII(ins) + 5 - AscII("a")) % (AscII("z") - AscII("a") + 1); //获取字符的ASCII 码
outs = Convert.ToChar(offset + AscII("a")).ToString(); //转换为字符并记录
if (isToUpperChar) //判断是否大写
{
outs = outs.ToUpper(); //全部转换为大写
}
}
else
{
outs = ins; //记录遍历的字符
}
strCaesar += outs; //添加到加密字符串中
}
return strCaesar; //返回加密后的字符串
}
秘 笈心法
心法领悟574:如何将新字符串添加到已有字符串中?
将新字符串添加到已有字符串中时,可以先声明一个StringBuilder类对象,以指定已有字符串的长度可变,
然后利用该对象的Append方法在字符串中添加指定字符串。将新字符串添加到已有字符串的代码如下:
StringBuilder strbuilder = new StringBuilder(textBox1.Text.Trim());
strbuilder.Append(textBox2.Text.Trim());
textBox3.Text = strbuilder.ToString();
实例575
对数据报进行加密保障通信安全
光盘位置:光盘\MR\19\575
高级
趣味指数:
实 例说明
网络传输数据时,有时候传输信息容易被不法分子截获而
用作其他用途。这样,如果传输的数据中包含有重要秘密,将
会造成非常严重的后果。为了防止这种情况的发生,可以对网
络中传输的数据进行加密,用户接收到数据后再进行解密查看,
这样可以更好地保障网络通信安全。运行本实例,首先设置端
口号,然后在窗体左下方的文本框中输入聊天信息,单击“发
送”按钮,向局域网中发送聊天信息,同时在右侧的“数据传
输信息”栏中显示数据报的发送、接收及丢失情况。实例运行
效果如图19.5 所示。
关 键技术
本实例获取数据报信息时主要用到IPGlobalProperties和UdpStatistics类,而在对数据报加密时用到DESCrypto
图19.5 对数据报进行加密保障通信安全
第19章 加密与解密技术
835
ServiceProvider 和CryptoStream 类,其中DESCryptoServiceProvider 继承于DES 类。下面对本实例中用到的关
键技术进行详细讲解。
(1)IPGlobalProperties 类
IPGlobalProperties 类提供有关本地计算机的网络连接的信息,本实例中用到它的GetIPGlobalProperties 和
GetUdpIPv4Statistics 方法,下面分别进行介绍。
GetIPGlobalProperties 为静态方法,主要用来获取一个对象,该对象提供有关本地计算机的网络连接和通信
统计数据的信息,其语法格式如下:
public static IPGlobalProperties GetIPGlobalProperties()
参数说明
返回值:IPGlobalProperties 对象,该对象包含有关本地计算机的信息。
GetUdpIPv4Statistics 方法主要用来提供本地计算机的用户数据报协议/Internet 协议版本4 (UDP/IPv4)统
计数据,其语法格式如下:
public abstract UdpStatistics GetUdpIPv4Statistics()
参数说明
返回值:UdpStatistics 对象,提供本地计算机的UDP/IPv4通信统计数据。
例如,本实例中创建IPGlobalProperties 对象,及调用其GetUdpIPv4Statistics 方法创建UdpStatistics 对象的
代码如下:
IPGlobalProperties NetInfo = IPGlobalProperties.GetIPGlobalProperties();
UdpStatistics myUdpStat = null;
myUdpStat = NetInfo.GetUdpIPv4Statistics();
(2)UdpStatistics类
UdpStatistics 类提供用户数据报协议(UDP)统计数据,本实例中主要用到其DatagramsSent 属性、
DatagramsReceived属性和IncomingDatagramsDiscarded 属性,其中,DatagramsSent 属性用来获取已发送的用户
数据报协议(UDP)数据报的数量,DatagramsReceived 属性用来获取已接收的用户数据报协议(UDP)数据报
的数量,IncomingDatagramsDiscarded 属性用来获取已收到但因端口错误而丢弃的用户数据报协议(UDP)数据
报的数量。
例如,本实例中初始化已发送、已接收和丢失数据报的实现代码如下:
SendNum1 = Int32.Parse(myUdpStat.DatagramsSent.ToString()); //记录发送的数据报
ReceiveNum1 = Int32.Parse(myUdpStat.DatagramsReceived.ToString()); //记录接收的数据报
DisNum1 = Int32.Parse(myUdpStat.IncomingDatagramsDiscarded.ToString()); //记录丢失的数据报
说明:IPGlobalProperties 类和UdpStatistics 类位于System.Net.NetworkInformation 命名空间下。
(3)DES 类
DES 类表示所有DES 实现都必须从中派生的数据加密标准(DES)算法的基类,其CreateEncryptor 方法和
CreateDecryptor 方法分别用来加密和解密。
CreateEncryptor 方法使用指定的Key属性和初始化向量(IV)创建对称加密器对象,其语法格式如下:
public abstract ICryptoTransform CreateEncryptor(byte[] rgbKey,byte[] rgbIV)
参数说明
rgbKey:用于对称算法的密钥。
rgbIV:用于对称算法的初始化向量。
返回值:对称加密器对象。
CreateDecryptor 方法使用指定的Key属性和初始化向量(IV)创建对称解密器对象,其语法格式如下:
public abstract ICryptoTransform CreateDecryptor(byte[] rgbKey,byte[] rgbIV)
参数说明
rgbKey:用于对称算法的密钥。
rgbIV:用于对称算法的初始化向量。
返回值:对称解密器对象。
C#开发实战1200 例(第II卷)
836
(4)CryptoStream 类
CryptoStream 类定义将数据流链接到加密转换的流,其构造函数的语法格式如下:
public CryptoStream(Stream stream,ICryptoTransform transform,CryptoStreamMode mode)
参数说明
stream:对其执行加密转换的流。
transform:要对流执行的加密转换。
mode:CryptoStreamMode 枚举值之一,CryptoStreamMode 枚举值及说明如表19.2 所示。
表19.2 CryptoStreamMode枚举值及说明
枚 举 值 说 明
Read 对加密流的读访问
Write 对加密流的写访问
另外,在向加密或解密流中写入数据时用到CryptoStream 类的Write 方法,该方法将一个字节序列写入当
前CryptoStream,并将流中的当前位置提升写入的字节数,其语法格式如下:
public override void Write(byte[] buffer,int offset,int count)
参数说明
buffer:字节数组,此方法将count 个字节从buffer 复制到当前流。
offset:buffer 中的字节偏移量,从此偏移量开始将字节复制到当前流。
count:要写入当前流的字节数。
说明:DES 类和CryptoStream 类位于System.Security.Cryptography 命名空间下。
设 计过程
(1)打开Visual Studio 2008开发环境,新建一个Windows窗体应用程序,并将其命名为EncryptDataReport。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加两个RichTextBox 控件,分别用来
输入聊天信息和显示聊天信息;添加4 个TextBox 控件,分别用来输入端口号和显示已发送数据报、已接收数
据报、丢失数据报;添加4 个Button 控件,分别用来执行设置端口号、发送聊天信息、清空聊天信息和关闭应
用程序操作。
(3)程序主要代码如下。
Frm_Main 窗体的后台代码中,首先创建程序所需要的.NET 对象及公共变量,代码如下:
#region 定义全局对象及变量
private IPEndPoint Server; //服务器端
private IPEndPoint Client; //客户端
private Socket mySocket; //套接字
private EndPoint ClientIP; //IP地址
byte[] buffer, data; //接收缓存
bool blFlag = true; //标识是否第一次发送信息
bool ISPort = false; //判断端口打开
int SendNum1, ReceiveNum1, DisNum1; //记录窗体加载时的已发送\已接收\丢失的数据报
int SendNum2, ReceiveNum2, DisNum2; //记录当前已发送\已接收\丢失的数据报
int SendNum3, ReceiveNum3, DisNum3; //缓存已发送\已接收\丢失的数据报
int port; //端口号
#endregion
Frm_Main 窗体加载时,初始化已发送、已接收和丢失的数据报,并使用全局变量记录,实现代码如下:
//初始化已发送、已接收和丢失的数据报
private void Form1_Load(object sender, EventArgs e)
{
if (blFlag == true)
{
IPGlobalProperties NetInfo = IPGlobalProperties.GetIPGlobalProperties(); //创建一个IPGlobalProperties 对象
UdpStatistics myUdpStat = null; //声明UdpStatistics 对象
myUdpStat = NetInfo.GetUdpIPv4Statistics(); //创建UdpStatistics 对象
第19章 加密与解密技术
837
SendNum1 = Int32.Parse(myUdpStat.DatagramsSent.ToString()); //记录发送的数据报
ReceiveNum1 = Int32.Parse(myUdpStat.DatagramsReceived.ToString()); //记录接收的数据报
DisNum1 = Int32.Parse(myUdpStat.IncomingDatagramsDiscarded.ToString()); //记录丢失的数据报
}
}
单击“设置”按钮,使用指定的端口号连接服务器端与客户端,并开始接收消息。“设置”按钮的Click
事件的代码如下:
private void button4_Click(object sender, EventArgs e) //设置端口号
{
try
{
port = Convert.ToInt32(textBox4.Text); //记录端口号
CheckForIllegalCrossThreadCalls = false; //指定线程中可以调用窗体的控件对象
buffer = new byte[1024];
data = new byte[1024];
Server = new IPEndPoint(IPAddress.Any, port); //创建服务器端
Client = new IPEndPoint(IPAddress.Broadcast, port); //创建客户端
ClientIP = (EndPoint)Server; //获取服务器端IP 地址
//创建Socket 对象
mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//设置Socket 网络操作
mySocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
mySocket.Bind(Server); //绑定服务器端
//开始接收消息
mySocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref ClientIP, new AsyncCallback(StartLister), null);
ISPort = true; //打开指定端口号
}
catch { }
}
单击“发送”按钮,首先判断是否有打开的端口,如果没有,弹出提示信息,否则根据发送和接收的消息
计算已发送、已接收和丢失的数据报,并显示在相应的文本框中,然后使用DES对要发送的消息进行加密发送。
“发送”按钮的Click事件的代码如下:
//发送信息
private void button2_Click(object sender, EventArgs e)
{
if (ISPort == true) //判断是否有打开的端口号
{
IPGlobalProperties NetInfo = IPGlobalProperties.GetIPGlobalProperties();
UdpStatistics myUdpStat = null;
myUdpStat = NetInfo.GetUdpIPv4Statistics();
try
{
if (blFlag == false) //非第一次发送
{
SendNum2 = Int32.Parse(myUdpStat.DatagramsSent.ToString());
ReceiveNum2 = Int32.Parse(myUdpStat.DatagramsReceived.ToString());
DisNum2 = Int32.Parse(myUdpStat.IncomingDatagramsDiscarded.ToString());
textBox1.Text = Convert.ToString(SendNum2 - SendNum3);
textBox2.Text = Convert.ToString(ReceiveNum2 - ReceiveNum3);
textBox3.Text = Convert.ToString(DisNum2 - DisNum3);
}
SendNum2 = Int32.Parse(myUdpStat.DatagramsSent.ToString());
ReceiveNum2 = Int32.Parse(myUdpStat.DatagramsReceived.ToString());
DisNum2 = Int32.Parse(myUdpStat.IncomingDatagramsDiscarded.ToString());
SendNum3 = SendNum2; //记录本次的发送数据报
ReceiveNum3 = ReceiveNum2; //记录本次的接收数据报
DisNum3 = DisNum2; //记录本次的丢失数据报
if (blFlag == true) //第一次发送
{
textBox1.Text = Convert.ToString(SendNum2 - SendNum1);
textBox2.Text = Convert.ToString(ReceiveNum2 - ReceiveNum1);
textBox3.Text = Convert.ToString(DisNum2 - DisNum1);
blFlag = false;
C#开发实战1200 例(第II卷)
838
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
string str = EncryptDES(rtbSend.Text, "mrsoftxk"); //加密要发送的信息
data = Encoding.Unicode.GetBytes(str);
mySocket.SendTo(data, data.Length, SocketFlags.None, Client); //发送消息
rtbSend.Text = "";
}
else
{
MessageBox.Show("请首先打开端口!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
button4.Focus();
}
}
上面的代码中用到了EncryptDES 方法,该方法为自定义的、返回值类型为string 的方法,主要用来使用
DES 加密数据报,它有两个string 类型的参数,分别用来表示待加密的字符串和加密密钥,返回值为加密后的
字符串。EncryptDES 方法的实现代码如下:
#region DES 加密字符串
///
///DES 加密字符串
///
///
待加密的字符串
///
加密密钥,要求为8 位
///
加密成功返回加密后的字符串,失败返回源字符串
public string EncryptDES(string str, string key)
{
try
{
byte[] rgbKey = Encoding.UTF8.GetBytes(key.Substring(0, 8)); //将加密密钥转换为字节数组
byte[] rgbIV = Keys; //记录原始密钥数组
byte[] inputByteArray = Encoding.UTF8.GetBytes(str); //将加密字符串转换为字节数组
DESCryptoServiceProvider myDES = new DESCryptoServiceProvider(); //创建加密对象
MemoryStream MStream = new MemoryStream(); //创建内存数据流
//创建加密流对象
CryptoStream CStream = new CryptoStream(MStream, myDES.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
CStream.Write(inputByteArray, 0, inputByteArray.Length); //向加密流中写入数据
CStream.FlushFinalBlock(); //释放加密流对象
return Convert.ToBase64String(MStream.ToArray()); //返回内存流中的数据
}
catch
{
return str;
}
}
#endregion
秘 笈心法
心法领悟575:如何根据标点符号分行?
根据标点符号分行时,首先要使用string 类的Split 方法分割字符串,然后再通过“\n”回车换行符将分割
的字符串换行显示。根据标点符号分行的代码如下:
string oldstr = textBox1.Text.Trim();
string[] newstr = oldstr.Split('。');
for (int i = 0; i < newstr.Length; i++)
{
if (richTextBox1.Text == "")
richTextBox1.Text = newstr[i].ToString();
else
richTextBox1.Text += "\n" + newstr[i].ToString();
}
第19章 加密与解密技术
839
实例576
使用one-time pad算法加密数据
光盘位置:光盘\MR\19\576
高级
趣味指数:
实 例说明
在密码学里,有一种理想的加密方案,叫做一次一密乱码本,即
one-time pad 算法,该算法是最安全的加密算法,双方一旦安全交换
了密钥,之后交换信息的过程就可以保证绝对安全。本实例使用C#
实现了one-time pad 加密算法,实例运行效果如图19.6 所示。
注意:程序中使用one-time pad 算法时,一定要保证密钥和密文
的长度是一样的。
关 键技术
本实例在实现one-time pad 加密算法时,主要用到了Encoding 类的GetBytes 方法和GetString 方法,下面
分别对它们进行详细介绍。
(1)Encoding 类的GetBytes方法
Encoding 类表示字符编码,其GetBytes方法主要用来将一组字符编码为一个字节序列,该方法为可重载方
法,本实例中用到的它的重载形式如下:
public virtual byte[] GetBytes(string s)
参数说明
s:字符串。
返回值:一个字节数组,包含对指定的字符集进行编码的结果。
说明:Encoding 类位于System.Text 命名空间下。
(2)Encoding 类的GetString方法
Encoding 类的GetString方法主要用来将一个字节序列解码为一个字符串,该方法为可重载方法,本实例中
用到的它的重载形式如下:
public virtual string GetString(byte[] bytes)
参数说明
bytes:包含要解码的字节序列的字节数组。
返回值:包含指定字节序列解码结果的字符串。
设 计过程
(1) 打开Visual Studio 2008开发环境,新建一个Windows窗体应用程序,并将其命名为OneTimePadArithmetic。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加4 个TextBox 控件,分别用来输入
要加密的数据和密钥,以及显示加密后的数据和解密后的数据;添加两个Button控件,分别用来实现使用one-time
pad 算法加密数据和解密数据的功能。
(3)程序主要代码如下。
在Frm_Main 窗体中输入要加密的数据和密钥后,单击“加密”按钮,使用one-time pad 算法对输入的数据
进行加密,实现代码如下:
private void button1_Click(object sender, EventArgs e)
{
textBox2.Text = ""; //清空文本框
Encoding encoding = Encoding.Default; //获取字符编码
byte[] btData = encoding.GetBytes(textBox1.Text); //将要加密的数据转换为字节数组
byte[] btKey = encoding.GetBytes(textBox4.Text); //将密钥转换为字节数组
图19.6 使用one-time pad 算法加密数据
C#开发实战1200 例(第II卷)
840
if (btData.Length == btKey.Length) //判断长度是否相等
{
byte[] btEncrypt = Encrypt(btData, btKey); //加密数据
for (int i = 0; i < btEncrypt.Length; i++) //遍历加密后的字节数组
{
textBox2.Text += btEncrypt[i]; //显示在文本框中
}
}
}
上面的代码中用到了Encrypt 方法,该方法为自定义的、返回值类型为byte[]的方法,主要用来对指定的数
据使用one-time pad 算法进行加密。Encrypt方法的实现代码如下:
public static byte[] Encrypt(byte[] btData, byte[] btKey)
{
if (btKey.Length != btData.Length) //判断长度是否相等
{
MessageBox.Show("请确保要加密数据的长度与密钥的长度一致!");
}
byte[] btResult = new byte[btData.Length]; //声明一个字节数组,用来存储加密数据
for (int i = 0; i < btResult.Length; ++i) //遍历字节数组
{
btResult[i] = (byte)(btKey[i] ^ btData[i]); //为字节数组赋值
}
return btResult; //返回得到的加密数据
}
单击“解密”按钮,调用Encrypt 方法对加密过的数据进行逆向加密,并返回一个byte[]数组,然后使用
Encoding 类的GetString方法从该数组中获取解密字符串。“解密”按钮的Click事件的代码如下:
private void button2_Click(object sender, EventArgs e)
{
Encoding encoding = Encoding.Default; //获取字符编码
byte[] btData = encoding.GetBytes(textBox1.Text); //将要加密的数据转换为字节数组
byte[] btKey = encoding.GetBytes(textBox4.Text); //将密钥转换为字节数组
if (btData.Length == btKey.Length) //判断长度是否相等
{
byte[] btDecrypt = Encrypt(Encrypt(btData, btKey), btKey); //解密数据
textBox3.Text = encoding.GetString(btDecrypt); //将解密后的字节数组转换为字符串并显示
}
}
秘 笈心法
心法领悟576:如何在字符串中添加多个空格?
开发程序时,有时会根据需要在字符串中添加一些空格,这时可以使用string 类的Insert方法,该方法可以
在字符串中的指定位置插入一个新的字符串(包括空格)。在字符串中添加空格的代码如下:
textBox3.Text = textBox1.Text.Insert(Convert.ToInt32(textBox2.Text.Trim()), " ");
实例577
使用伪随机数加密技术加密用户登录密码
光盘位置:光盘\MR\19\577
高级
趣味指数:
实 例说明
为了保障用户登录密码的安全,本实例使用伪随机数技术对用
户的登录密码进行加密,运行本实例,当用户在“登录密码”文本
框中输入登录密码时,程序会自动将使用过伪随机数加密技术加密
过的登录密码显示在下面的“加密密码”文本框中,单击“登录”
按钮,程序对“加密密码”文本框中的加密数据进行解密,然后再
与用户输入的登录密码相比较,如果相同,则登录成功;否则,登
录失败。实例运行效果如图19.7 所示。
图19.7 使用伪随机数加密技术
加密用户登录密码
第19章 加密与解密技术
841
关 键技术
本实例对用户登录密码加密时用到伪随机数加密技术,伪随机数加密技术实质上就是通过伪随机数序列使
登录密码字符串的字节值发生变化而产生密文,由于相同的初值能得到相同的随机数序列,因此,可以采用同
样的伪随机数序列来对密文进行解密。产生伪随机数时主要用到Random 类,该类表示伪随机数生成器,它是
一种能够产生满足某些随机性统计要求的数字序列的设备,其Next方法用来返回随机数,语法格式如下:
public virtual int Next(int maxValue)
参数说明
maxValue:要生成的随机数的上界(随机数不能取该上界值),maxValue 必须大于等于零。
返回值:大于等于零且小于maxValue 的32 位带符号整数,即返回值的范围通常包括零但不包括
maxValue;不过,如果maxValue 等于零,则返回maxValue。
设 计过程
(1)打开Visual Studio 2008开发环境,新建一个Windows窗体应用程序,并将其命名为PRanDataEncrypt。
(2)更改默认窗体Form1 的Name属性为Frm_Main,在该窗体中添加3 个TextBox 控件,分别用来输入
登录用户、登录密码和显示加密密码;添加两个Button 控件,分别用来执行用户登录和清空文本框操作。
(3)程序主要代码如下。
Frm_Main 窗体的后台代码中,首先定义加密用户密码所用的伪随机数,代码如下:
//定义加密用户密码所用的伪随机数
private string randStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
当在“登录密码”文本框中输入登录密码时,实时将使用伪随机数加密过的登录密码显示在“加密密码”
文本框中,实现代码如下:
private void textBox2_TextChanged(object sender, EventArgs e)
{
textBox3.Text = EncryptPwd(textBox2.Text); //显示加密后的用户登录密码
}
上面的代码中用到EncryptPwd 方法,该方法为自定义的、返回值类型为string 的方法,主要用来使用伪随
机数技术加密用户登录密码,它有一个参数,用来表示用户登录密码。EncryptPwd 方法的实现代码如下:
///
/// 使用伪随机数加密用户登录密码
///
///
用户登录密码
///
加密后的用户登录密码
private string EncryptPwd(string str)
{
byte[] btData = Encoding.Default.GetBytes(str); //将登录密码转换为字节数组
int j, k, m;
int len = randStr.Length; //记录伪随机数长度
StringBuilder sb = new StringBuilder(); //创建StringBuilder对象
Random rand = new Random(); //创建Random 对象
for (int i = 0; i < btData.Length; i++)
{
j = (byte)rand.Next(6); //产生伪随机数
btData[i] = (byte)((int)btData[i] ^ j); //使用伪随机数对密码字节数组进行移位
k = (int)btData[i] % len;
m = (int)btData[i] / len;
m = m * 8 + j;
sb.Append(randStr.Substring(k, 1) + randStr.Substring(m, 1)); //组合加密字符串
}
return sb.ToString(); //返回生成的加密字符串
}
单击“登录”按钮,判断“加密密码”文本框是否为空。如果不为空,调用DecryptPwd 方法解密“加密
密码”文本框中的字符串;然后使用解密后的字符串与“登录密码”文本框中的字符串相比较,如果相同,则
用户登录成功;否则,弹出提示信息。“登录”按钮的Click事件代码如下:
C#开发实战1200 例(第II卷)
842
private void button1_Click(object sender, EventArgs e)
{
if (textBox3.Text != "")
{
if (DecryptPwd(textBox3.Text) == textBox2.Text) //对加密过的登录密码进行解密
MessageBox.Show("用户登录成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
else
MessageBox.Show("用户密码错误!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
上面的代码中用到了DecryptPwd 方法,该方法为自定义的、返回值类型为string 的方法,主要用来解密用
户登录密码,它有一个参数,主要用来表示经过加密的用户登录密码。DecryptPwd 方法的实现代码如下:
///
/// 解密用户登录密码
///
///
经过加密的用户登录密码
///
解密后的用户登录密码
private string DecryptPwd(string str)
{
try
{
int j, k, m, n = 0;
int len = randStr.Length; //获取伪随机数长度
byte[] btData = new byte[str.Length / 2]; //定义一个字节数组,并指定长度
for (int i = 0; i < str.Length; i += 2) //对登录密码进行解密
{
k = randStr.IndexOf(str[i]);
m = randStr.IndexOf(str[i + 1]);
j = m / 8;
m = m - j * 8;
btData[n] = (byte)(j * len + k);
btData[n] = (byte)((int)btData[n] ^ m);
n++;
}
return Encoding.Default.GetString(btData); //返回解密后的登录密码
}
catch { return ""; }
}
秘 笈心法
心法领悟577:如何将字符串颠倒输出?
颠倒输出字符串时,可以先将要输出的字符串保存到一个char 类型的数组中,然后使用Array 类的Reverse
方法。将字符串颠倒输出的代码如下:
string str1 = textBox1.Text.Trim();
char[] charstr = str1.ToCharArray();
Array.Reverse(charstr);
string str2 = new string(charstr);
textBox2.Text = str2;
实例578
以XML格式导入导出密钥
光盘位置:光盘\MR\19\578
高级
趣味指数:
实 例说明
本实例主要实现以XML 格式导入导出密钥,从而实现对数据进行加密和解密的功能。运行本实例,首先
在窗体中显示生成的公钥和私钥,然后输入明文数据,单击“加密”按钮,对输入的明文数据进行加密;单击
“解密”按钮,对加密后的数据进行解密。实例运行效果如图19.8 所示。
第19章 加密与解密技术
843
图19.8 以XML 格式导入导出密钥
关 键技术
本实例实现时主要用到了RSACryptoServiceProvider 类的ToXmlString方法、Encrypt 方法和Decrypt 方法,
下面对本实例中用到的关键技术进行详细讲解。
(1)RSACryptoServiceProvider 类的ToXmlString方法
RSACryptoServiceProvider 类用来使用加密服务提供程序(CSP)提供的RSA 算法的实现执行不对称加密和
解密,其ToXmlString 方法主要用来创建并返回包含当前RSA 对象的密钥的XML 字符串,该方法的语法格式
如下:
public override string ToXmlString(bool includePrivateParameters)
参数说明
includePrivateParameters:true 表示同时包含RSA公钥和私钥,false 表示仅包含公钥。
返回值:包含当前RSA对象的密钥的XML字符串。
说明:RSACryptoServiceProvider 类位于System.Security.Cryptography 命名空间下。
(2)RSACryptoServiceProvider 类的Encrypt方法
该方法主要使用RSA算法对数据进行加密,其语法格式如下:
public byte[] Encrypt(byte[] rgb,bool fOAEP)
参数说明
rgb:要加密的数据。
fOAEP:如果为true,则使用OAEP 填充(仅在运行Microsoft Windows XP 或更高版本的计算机上可用)
执行直接的RSA 加密;如果为false,则使用PKCS#1 1.5 版填充。
返回值:字节数组,表示已加密的数据。
(3)RSACryptoServiceProvider 类的Decrypt方法
该方法主要使用RSA算法对数据进行解密,其语法格式如下:
public byte[] Decrypt(byte[] rgb,bool fOAEP)
参数说明
rgb:要解密的数据。
fOAEP:如果为true,则使用OAEP 填充(仅在运行Microsoft Windows XP 或更高版本的计算机上可用)
执行直接的RSA 解密;如果为false,则使用PKCS#1 1.5 版填充。
返回值:字节数组,表示已解密的数据,它是加密前的原始纯文本。
设 计过程
(1)打开Visual Studio 2008 开发环境,新建一个Windows窗体应用程序,并将其命名为KeyToXML。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加5 个TextBox 控件,分别用来显示
C#开发实战1200 例(第II卷)
844
公钥、显示私钥、输入明文数据、显示加密后的数据和显示解密后的数据;添加两个Button 控件,分别用来执
行数据加密和解密操作。
(3)程序主要代码如下。
在Frm_Main 窗体的后台代码中,首先创建RSACryptoServiceProvider 对象,并且定义一个字节数组,用来
存储临时数据,代码如下:
RSACryptoServiceProvider RSACrypto = new RSACryptoServiceProvider(); //创建RSA 算法加密解密对象
byte[] M_bt_Data; //定义一个字节数组,用来存储临时数据
Frm_Main 窗体加载时,在文本框中显示程序自动生成的公钥和私钥数据,代码如下:
private void Frm_Main_Load(object sender, EventArgs e)
{
this.textBox1.Text = RSACrypto.ToXmlString(true); //显示生成的公钥
this.textBox2.Text = RSACrypto.ToXmlString(false); //显示生成的私钥
}
当用户输入明文数据之后,单击“加密”按钮,调用RSACryptoServiceProvider 类的Encrypt方法对数据进
行加密,并且使用Encoding 类的UTF8 编码方式的GetString 方法得到加密后的数据,显示在文本框中。“加密”
按钮的Click事件代码如下:
private void button1_Click(object sender, EventArgs e)
{
if (textBox3.Text != "") //判断是否输入了要加密的数据
{
byte[] P_bt_Encrypt = Encoding.UTF8.GetBytes(textBox3.Text); //将要加密的数据转换为字节数组
M_bt_Data = RSACrypto.Encrypt(P_bt_Encrypt, false); //加密数据
textBox4.Text = Encoding.UTF8.GetString(M_bt_Data); //显示加密数据
}
}
单击“解密”按钮,调用RSACryptoServiceProvider 类的Decrypt方法对加密过的数据进行解密,并且使用
Encoding 类的UTF8 编码方式的GetString 方法得到解密后的数据,显示在文本框中。“解密”按钮的Click 事
件代码如下:
private void button2_Click(object sender, EventArgs e)
{
if (textBox4.Text != "") //判断是否有加密过的数据
{
byte[] P_bt_Decrypt = RSACrypto.Decrypt(M_bt_Data, false); //对数据进行解密
textBox5.Text = Encoding.UTF8.GetString(P_bt_Decrypt); //显示解密数据
}
}
秘 笈心法
心法领悟578:如何判断字符串是否为日期格式?
判断字符串是否为日期格式时,可以使用正则表达式。验证日期格式的正则表达式主要有以下3 种:
\b(?
\d{2,4})/(?\d{1,2})/(?\d{1,2})\b
或
\b(?\d{2,4})-(?\d{1,2})-(?\d{1,2})\b
或
\b(?\d{2,4})年(?\d{1,2})月(?\d{1,2})日\b
实例579
以参数格式导入导出密钥
光盘位置:光盘\MR\19\579
高级
趣味指数:
实 例说明
本实例主要实现以参数格式导入导出密钥,从而实现对数据进行加密和解密的功能。运行本实例,在窗体
第19章 加密与解密技术
845
中输入明文数据,单击“加密”按钮,对输入的明文数据进行加密;单击“解
密”按钮,对加密后的数据进行解密。实例运行效果如图19.9 所示。
关 键技术
本实例实现时主要用到了RSACryptoServiceProvider 类的ExportParameters
方法、ImportParameters 方法、Encrypt 方法和Decrypt 方法,下面对本实例
中用到的关键技术进行详细讲解。
(1)RSACryptoServiceProvider 类的ExportParameters 方法
该方法主要用来导出RSAParameters标准参数,其语法格式如下:
public override RSAParameters ExportParameters(bool includePrivateParameters)
参数说明
includePrivateParameters:如果要包括私有参数,则为true;否则为false。
返回值:RSA 算法的标准参数。
(2)RSACryptoServiceProvider 类的ImportParameters 方法
该方法主要用来导入指定的RSAParameters标准参数,其语法格式如下:
public override void ImportParameters(RSAParameters parameters)
参数说明
parameters:RSA 算法的标准参数。
说明:关于RSACryptoServiceProvider 类的Encrypt 方法和Decrypt 方法的详细讲解,请参见实例578 中的
关键技术。
设 计过程
(1)打开Visual Studio 2008 开发环境,新建一个Windows窗体应用程序,并将其命名为KeyToParameter。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加3 个TextBox 控件,分别用来输入
明文数据、显示加密后的数据和解密后的数据;添加两个Button 控件,分别用来执行数据加密和解密操作。
(3)程序主要代码如下。
Frm_Main 窗体的后台代码中,首先创建RSACryptoServiceProvider 对象和RSAParameters 标准参数对象,
并且定义一个字节数组,用来存储临时数据,代码如下:
RSACryptoServiceProvider RSACrypto; //声明RSA 算法加密解密对象
RSAParameters RSAParame; //声明RSAParameters 参数对象
byte[] M_bt_Data; //定义一个字节数组,用来存储临时数据
在Frm_Main 窗体的构造函数中,调用RSACryptoServiceProvider 类的ImportParameters 方法导入
RSAParameters标准参数,实现代码如下:
public Frm_Main()
{
InitializeComponent();
RSACrypto = new RSACryptoServiceProvider(); //初始化RSA 算法加密解密对象
RSAParame = RSACrypto.ExportParameters(true); //初始化RSAParameters 参数
RSACrypto.Clear(); //清空RSACryptoServiceProvider 对象
RSACrypto = new RSACryptoServiceProvider(); //初始化RSA 算法加密解密对象
RSACrypto.ImportParameters(RSAParame); //导入密钥
}
当用户输入明文数据之后,单击“加密”按钮,调用RSACryptoServiceProvider 类的Encrypt方法对数据进
行加密,并且使用Encoding 类的UTF8 编码方式的GetString 方法得到加密后的数据,显示在文本框中。“加密”
按钮的Click事件代码如下:
private void button1_Click(object sender, EventArgs e)
{
if (textBox1.Text != "") //判断是否输入了要加密的数据
{
图19.9 以参数格式导入导出密钥
C#开发实战1200 例(第II卷)
846
byte[] P_bt_Encrypt = Encoding.UTF8.GetBytes(textBox1.Text); //将要加密的数据转换为字节数组
M_bt_Data = RSACrypto.Encrypt(P_bt_Encrypt, false); //加密数据
textBox2.Text = Encoding.UTF8.GetString(M_bt_Data); //显示加密数据
}
}
单击“解密”按钮,调用RSACryptoServiceProvider 类的Decrypt方法对加密过的数据进行解密,并且使用
Encoding 类的UTF8 编码方式的GetString 方法得到解密后的数据,显示在文本框中。“解密”按钮的Click 事
件代码如下:
private void button2_Click(object sender, EventArgs e)
{
if (textBox2.Text != "") //判断是否有加密过的数据
{
byte[] P_bt_Decrypt = RSACrypto.Decrypt(M_bt_Data, false); //对数据进行解密
textBox3.Text = Encoding.UTF8.GetString(P_bt_Decrypt); //显示解密数据
}
}
秘 笈心法
心法领悟579:巧截字符串中的数字。
截取字符串中的数字时,可以先使用CharEnumerator 对象的MoveNext 方法循环访问字符串中的每个字符,
并将字符用System.Text.Encoding 类中ASCII 编码方式的GetBytes 方法进行编码,然后判断经过编码之后的字符
的ASCII码值是否介于48和57之间,如果是,则将其显示在textBox文本框中。截取字符串中数字的代码如下:
CharEnumerator CEnumerator = textBox1.Text.GetEnumerator();
while (CEnumerator.MoveNext())
{
byte[] array = new byte[1];
array = System.Text.Encoding.ASCII.GetBytes(CEnumerator.Current.ToString());
int asciicode = (short)(array[0]);
if (asciicode >= 48 && asciicode <= 57)
{
textBox2.Text += CEnumerator.Current.ToString();
}
}
19.2 文件的加密与解密
实例580
文本文件加密与解密
光盘位置:光盘\MR\19\580
高级
趣味指数:
实 例说明
在本实例的窗体中,首先选择要加密或解密的文本文件,然后单击“加
密”或“解密”按钮对文本文件进行加密或解密。实例运行效果如图19.10
所示。
关 键技术
本实例实现时主要用到了System.Security.Cryptography命名空间下的
RijndaelManaged 类的CreateDecryptor 方法、CreateEncryptor 方法和CryptoStream 类的Write 方法,下面对本实
例中用到的关键技术进行详细讲解。
(1)RijndaelManaged 类
该类是访问System.Security.Cryptography.Rijndael 对称加密算法的托管版本,其语法格式如下:
public sealed class RijndaelManaged : Rijndael
图19.10 文本文件加密与解密
第19章 加密与解密技术
847
注意:此算法支持128、192或256 位的密钥长度。
(2)CreateDecryptor 方法
该方法位于RijndaelManaged 类中,使用指定的Key和初始化向量(IV)创建对称的Rijndael 解密器对象,
其语法格式如下:
public override IcryptoTransform CreateDecryptor (byte[] rgbKey,byte[] rgbIV)
参数说明
rgbKey:用于对称算法的机密密钥。
rgbIV:用于对称算法的IV。
返回值:对称的Rijndael 解密器对象。
(3)CreateEncryptor 方法
该方法位于RijndaelManaged 类中,使用指定的Key和初始化向量(IV)创建对称的Rijndael 加密器对象,
其语法格式如下:
public override ICryptoTransform CreateEncryptor (byte[] rgbKey,byte[] rgbIV)
参数说明
rgbKey:用于对称算法的机密密钥。
rgbIV:用于对称算法的IV。
返回值:对称的Rijndael 加密器对象。
说明:关于CryptoStream 类的Write 方法的详细讲解,请参见实例575中的关键技术。
设 计过程
(1) 打开Visual Studio 2008开发环境,新建一个Windows窗体应用程序,并将其命名为EncryptTextFileOne。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加一个TextBox 控件,用来显示文本
文件路径;添加一个OpenFileDialog 控件,用来选择要加密或解密的文本文件;添加3 个Button 控件,用来执
行选择文本文件、加密和解密操作。
(3)程序主要代码如下。
单击“加密”按钮实现对选择的文本文件进行加密,“加密”按钮的Click事件的代码如下:
private void button2_Click(object sender, EventArgs e)
{
if (textBox1.Text == "") //若未选择要加密的文本文件
{ MessageBox.Show("请选择要加密的文件"); } //如果没有选择则弹出提示
else
{
try{
string strPath = textBox1.Text; //加密文件的路径
int intLent=strPath.LastIndexOf("\\")+1; //设置截取的起始位置
int intLong = strPath.Length; //设置截取的长度
string strName = strPath.Substring(intLent,intLong-intLent); //要加密的文件名称
int intTxt = strName.LastIndexOf("."); //设置截取的起始位置
int intTextLeng = strName.Length; //设置截取的长度
string strTxt = strName.Substring(intTxt,intTextLeng-intTxt); //取出文件的扩展名
strName = strName.Substring(0,intTxt);
//加密后的文件名及路径
string strOutName = strPath.Substring(0, strPath.LastIndexOf("\\") + 1) + strName + "Out" + strTxt;
//加密文件密钥
byte[] key = { 24, 55, 102, 24, 98, 26, 67, 29, 84, 19, 37, 118, 104, 85, 121, 27, 93, 86, 24, 55, 102, 24, 98, 26, 67, 29, 9, 2, 49, 69, 73, 92 };
byte[] IV ={ 22, 56, 82, 77, 84, 31, 74, 24, 55, 102, 24, 98, 26, 67, 29, 99 };
RijndaelManaged myRijndael = new RijndaelManaged();
FileStream fsOut = File.Open(strOutName, FileMode.Create, FileAccess.Write);
FileStream fsIn = File.Open(strPath, FileMode.Open, FileAccess.Read);
//写入加密文本文件
CryptoStream csDecrypt = new CryptoStream(fsOut, myRijndael.CreateEncryptor(key, IV), CryptoStreamMode.Write);
BinaryReader br = new BinaryReader(fsIn); //创建阅读器来读加密文本
csDecrypt.Write(br.ReadBytes((int)fsIn.Length), 0, (int)fsIn.Length); //将数据写入加密文本
C#开发实战1200 例(第II卷)
848
csDecrypt.FlushFinalBlock();
csDecrypt.Close(); //关闭CryptoStream 对象
fsIn.Close(); //关闭FileStream 对象
fsOut.Close(); //关闭FileStream 对象
if (MessageBox.Show("加密成功!加密后的文件名及路径为:\n" + strOutName + ",是否删除源文件", "信息提示", MessageBoxButtons.
YesNo) == DialogResult.Yes)
{
File.Delete(strPath); //删除指定文件
textBox1.Text = ""; //清空文本框
}else
{ textBox1.Text = ""; }
}
catch (Exception ee) //如果出现异常
{
MessageBox.Show(ee.Message); //输出异常信息
}
}
}
单击“解密”按钮实现对加密的文本文件进行解密,“解密”按钮的Click事件代码如下:
private void button3_Click(object sender, EventArgs e)
{
if (textBox1.Text == "") //若未选择要解密的文件
{
MessageBox.Show("请选择要解密的文件路径"); //如果没有选择则弹出提示
}
else
{
string strPath = textBox1.Text; //加密文件的路径
int intLent = strPath.LastIndexOf("\\") + 1; //设置截取字符串的起始位置
int intLong = strPath.Length; //设置截取长度
string strName = strPath.Substring(intLent, intLong - intLent); //要加密的文件名称
int intTxt = strName.LastIndexOf("."); //截取字符串的起始位置
int intTextLeng = strName.Length; //截取长度
strName = strName.Substring(0, intTxt); //获取扩展名
if (strName.LastIndexOf("Out") != -1)
{
strName = strName.Substring(0, strName.LastIndexOf("Out"));
}
else
{
strName = strName + "In";
}
//加密后的文件名及路径
string strInName = strPath.Substring(0, strPath.LastIndexOf("\\") + 1) + strName + ".txt";
//解密文件密钥
byte[] key = { 24, 55, 102, 24, 98, 26, 67, 29, 84, 19, 37, 118, 104, 85, 121, 27, 93, 86, 24, 55, 102, 24, 98, 26, 67, 29, 9, 2, 49, 69, 73, 92 };
byte[] IV ={ 22, 56, 82, 77, 84, 31, 74, 24, 55, 102, 24, 98, 26, 67, 29, 99 };
RijndaelManaged myRijndael = new RijndaelManaged(); //创建RijndaelManaged 对象
//创建FileStream 对象
FileStream fsOut = File.Open(strPath, FileMode.Open, FileAccess.Read);
CryptoStream csDecrypt = new CryptoStream(fsOut, myRijndael.CreateDecryptor(key, IV), CryptoStreamMode.Read);
StreamReader sr = new StreamReader(csDecrypt); //把文件读出来
StreamWriter sw = new StreamWriter(strInName); //解密后写入一个新文件
sw.Write(sr.ReadToEnd());
sw.Flush();
sw.Close();
sr.Close();
fsOut.Close();
if (MessageBox.Show("解密成功!解密后的文件名及路径为:"+strInName+",是否删除源文件", "信息提示", MessageBoxButtons.YesNo)
== DialogResult.Yes)
{
File.Delete(strPath); //删除指定文件
textBox1.Text = ""; //清空文本框
}
else
{
第19章 加密与解密技术
849
textBox1.Text = "";
}
}
}
秘 笈心法
心法领悟580:如何存储变长字符串?
在程序中存储变长字符串时,需要使用StringBuilder对象。相对于string 对象来说,StringBuilder 对象是可
变的,不用生成中间对象,因此,在连接的字符串较多或字符串长度较长时,通常都使用StringBuilder 对象。
实例581
利用图片加密文件
光盘位置:光盘\MR\19\581
高级
趣味指数:
实 例说明
本实例在加密时,使用指定的图片生成加密密钥,然后对文本文件进
行加密;在解密时,使用加密时的图片生成解密密钥,然后对加密的文本
文件进行解密。运行本实例,首先打开一张图片,用来生成加密或解密的
密钥,然后选择要加密或解密的文本文件,最后单击“加密”或“解密”
按钮,实现对文本文件的加密或解密。实例运行效果如图19.11 所示。
关 键技术
本实例实现时主要用到了RC2CryptoServiceProvider 类、BinaryWriter
类的Write 方法、File 类的Delete 方法和Copy 方法,下面对本实例中用到
的关键技术进行详细讲解。
(1)RC2CryptoServiceProvider 类
该类定义访问RC2算法的加密服务提供程序(CSP)实现的包装对象,无法继承此类。
(2)BinaryWriter 类
该类以二进制形式将基元类型写入流,并支持用特定的编码写入字符串,其构造器的语法格式如下:
public BinaryWriter (Stream output)
参数说明
output:表示输出流。
(3)BinaryWriter 类的Write 方法
该方法将一个无符号字节写入当前流,并将流的位置提升一个字节,其语法格式如下:
public virtual void Write (byte value)
参数说明
value:表示要写入的无符号字节。
(4)File 类的Delete 方法
File 类提供用于创建、复制、删除、移动和打开文件的静态方法,并协助创建FileStream 对象,该类是个
静态类,其Delete方法用于删除指定的文件,如果指定的文件不存在,则引发异常。该方法的语法格式如下:
public static void Delete (string path)
参数说明
path:表示要删除的文件的名称。
(5)File 类的Copy 方法
该方法将现有文件复制到新文件,不允许改写同名的文件,其语法格式如下:
public static void Copy (string sourceFileName,string destFileName)
图19.11 利用图片加密文件
C#开发实战1200 例(第II卷)
850
参数说明
sourceFileName:要复制的文件。
destFileName:目标文件的名称,不能是一个目录或现有文件。
设 计过程
(1) 打开Visual Studio 2008开发环境,新建一个Windows窗体应用程序,并将其命名为EncryptTextFileTwo。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加一个TextBox 控件,用来显示加密
或解密文件的路径;添加一个OpenFileDialog 控件,用来选择要加密或解密的文件和打开密钥的图片;添加4
个Button 控件,分别用来执行加密、解密、打开文件和打开图片操作;添加一个PictureBox 控件,用于显示密
钥图片。
(3)程序主要代码如下。
单击“加密”按钮,实现利用图片对文本文件进行加密的功能,“加密”按钮的Click 事件的代码如下:
private void button3_Click(object sender, EventArgs e)
{
try
{
if (pictureBox1.ImageLocation==null) //判断是否选择了图片
{ MessageBox.Show("请选择一幅图片用于加密"); return; } //如果没有选择则弹出提示
if (textBox1.Text == "") //若未选择需要加密的文件
{ MessageBox.Show("请选择加密文件路径"); return; } //如果没有选择则弹出提示
//图片流
FileStream fsPic = new FileStream(pictureBox1.ImageLocation, FileMode.Open, FileAccess.Read);
//加密文件流
FileStream fsText = new FileStream(textBox1.Text, FileMode.Open, FileAccess.Read);
//初始化对称算法的密钥和向量
byte[] bykey = new byte[16]; //定义存储密钥的字节数组
byte[] byIv = new byte[8]; //定义存储向量的字节数组
fsPic.Read(bykey, 0, 16); //把图片流写入密钥缓冲区
fsPic.Read(byIv, 0, 8); //把图片流写入向量缓冲区
//临时加密文件
string strPath = textBox1.Text; //加密文件的路径
int intLent = strPath.LastIndexOf("\\") + 1;
int intLong = strPath.Length;
string strName = strPath.Substring(intLent, intLong - intLent); //要加密的文件名称
string strLinPath = "C:\\" + strName; //临时加密文件路径
FileStream fsOut = File.Open(strLinPath, FileMode.Create, FileAccess.Write);
//开始加密,首先创建RC2CryptoServiceProvider 对象
RC2CryptoServiceProvider desc = new RC2CryptoServiceProvider();
BinaryReader br = new BinaryReader(fsText); //创建BinaryReader 对象
//创建CryptoStream 对象,用于写入临时加密文件
CryptoStream cs = new CryptoStream(fsOut, desc.CreateEncryptor(bykey, byIv), CryptoStreamMode.Write);
cs.Write(br.ReadBytes((int)fsText.Length), 0, (int)fsText.Length); //写入加密流
cs.FlushFinalBlock();
cs.Flush();
cs.Close();
fsPic.Close();
fsText.Close();
fsOut.Close();
File.Delete(textBox1.Text.TrimEnd()); //删除原文件
File.Copy(strLinPath, textBox1.Text); //复制加密文件
File.Delete(strLinPath); //删除临时文件
MessageBox.Show("加密成功");
pictureBox1.ImageLocation = null;
textBox1.Text = "";
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
}
第19章 加密与解密技术
851
单击“解密”按钮,实现利用图片对加密的文本文件进行解密的功能,“解密”按钮的Click事件的代码如下:
private void button4_Click(object sender, EventArgs e)
{
try
{
//图片流
FileStream fsPic = new FileStream(pictureBox1.ImageLocation, FileMode.Open, FileAccess.Read);
//解密文件流
FileStream fsOut = File.Open(textBox1.Text, FileMode.Open, FileAccess.Read);
//初始化对称算法的密钥和向量
byte[] bykey = new byte[16]; //定义存储密钥的字节数组
byte[] byIv = new byte[8]; //定义存储向量的字节数组
fsPic.Read(bykey, 0, 16); //把图片流写入密钥缓冲区
fsPic.Read(byIv, 0, 8); //把图片流写入向量缓冲区
//创建临时解密文件
string strPath = textBox1.Text; //加密文件的路径
int intLent = strPath.LastIndexOf("\\") + 1; //获取不含文件名的路径长度
int intLong = strPath.Length; //获取含文件名的路径长度
//获取要解密文件的名称,即加密文件的名称
string strName = strPath.Substring(intLent, intLong - intLent);
string strLinPath = "C:\\" + strName; //临时解密文件路径
FileStream fs = new FileStream(strLinPath, FileMode.Create, FileAccess.Write);
//开始解密,首先创建RC2CryptoServiceProvider 对象
RC2CryptoServiceProvider desc = new RC2CryptoServiceProvider();
//创建CryptoStream 对象,用于读取加密文件
CryptoStream csDecrypt = new CryptoStream(fsOut, desc.CreateDecryptor(bykey, byIv), CryptoStreamMode.Read);
BinaryReader sr = new BinaryReader(csDecrypt); //创建BinaryReader 对象
BinaryWriter sw = new BinaryWriter(fs); //创建BinaryWriter 对象
sw.Write(sr.ReadBytes(Convert.ToInt32(fsOut.Length))); //写入解密流
sw.Flush();
sw.Close();
sr.Close();
fs.Close();
fsOut.Close();
fsPic.Close();
csDecrypt.Flush();
File.Delete(textBox1.Text.TrimEnd()); //删除原文件
File.Copy(strLinPath, textBox1.Text); //复制加密文件
File.Delete(strLinPath); //删除临时文件
MessageBox.Show("解密成功"); //弹出提示信息
pictureBox1.ImageLocation = null; //清空图片
textBox1.Text = ""; //清空文本框
}
catch (Exception ee) //如果出现异常
{
MessageBox.Show(ee.Message); //输出异常
}
}
秘 笈心法
心法领悟581:如何去除字符串尾空格?
去除字符串尾空格需要使用string 类的Trim 方法,该方法用来从字符串的开始和末尾处移除空白字符的所
有匹配项。例如,下面的代码用来去掉textBox1 文本框中字符串的尾空格,并将结果显示在textBox2 文本框中:
textBox2.Text = textBox1.Text.Trim();
实例582
对文件进行加密保护
光盘位置:光盘\MR\19\582
高级
趣味指数:
实 例说明
随着计算机的普及,文件的安全越来越重要,本实例使用C#制作了一个对文件进行加密保护的实例。运行
C#开发实战1200 例(第II卷)
852
本实例,选择要加密或解密的文件,用程序来判断是否是加密过的文件,
如果不是,输入加密密码,单击“加密”按钮,加密已选择的文件;如果
是,输入解密密码,单击“解密”按钮,解密选择的加密文件。实例运行
效果如图19.12 所示。
关 键技术
本实例制作对文件进行加密保护程序时,首先选择要加密或解密的文
件,并输入加密或解密密码,然后启动一个新的线程,使用输入的密码对
指定的文件进行加密或解密操作。另外,如果对文件执行的是加密操作,则加密成功后删除原文件。具体实现
过程中,主要用到了DES 类的CreateEncryptor 和CreateDecryptor 方法、CryptoStream 类的构造函数及其Write
方法。
说明:关于DES 类的CreateEncryptor 方法和CreateDecryptor 方法、CryptoStream 类的构造函数及其Write
方法的详细讲解,请参见实例575中的关键技术。
设 计过程
(1)打开Visual Studio 2008 开发环境,新建一个Windows窗体应用程序,并将其命名为ProtectFile。
(2)更改默认窗体Form1 的Name 属性为Frm_Main,在该窗体中添加一个OpenFileDialog 控件,用来显
示“打开”对话框;添加两个TextBox 控件,分别用来显示选择的文件路径和输入加密、解密密码;添加3 个
Button控件,分别用来执行选择加密或解密的文件、加密文件和解密文件操作;添加一个ProgressBar控件,用
来显示加密或解密的进度。
(3)程序主要代码如下。
Frm_Main 窗体加载时,首先将加密文件
1