分组密码是将明文消息编码表示后数字序列划分红长为n的分组,各组分别在密钥的做用下进行变换输出等长的数字序列,即密文。一次加密一个数据组,加解密所使用的是同一密钥,故其一般也称为对称加密。分组长n各类不一样的对称加密算法取值不一样(DES和TripleDES为64位,AES默认为128位,也能够为192位和256位),在对明文消息进行分组时若是最后个分组小于n,则要进行数据填充,使分组长达到n才能进行后续的加密处理。.net平台提供的加密类都很好的处理了上述问题,因此在用C#语言进行实际编码能很简便的完成加解密操做。算法
Rijndael算法做为AES的一种,已经取代TripleDES(三重DES)成为新的数据加密标准。其分组长度及密钥长度均可变,且比DES算法都要长,使其也具备了更高的安全性。本文的示例程序采用的就是Rijndael算法。安全
分组密码在加密时,明文分组的长度是固定,而实用中待加密消息的数据量是不定的,相邻的两个分组加解密时是否相关,就产生了不一样的运行模式。下面主要介绍两种经常使用的分组密码运行模式网络
ECB模式是最简单的运行模式,各个分组使用相同的密钥进行加密,如图1所示。编码
图1. ECB模式示意图加密
当密钥取定时,对明文的每个分组,都有一个惟一的密文与之对应。这也造就了ECB模式的最大特性,同一明文分组在消息中重复出现的话,产生的密文分组也相同。故ECB用于长消息时可能不够安全,若是消息有固定结构,攻击者可能找出这种关系。但由于在ECB模式中,各分组加解密相互独立,因此很方便进行并行计算,提升大型数据加解密的运行效率。spa
为了解决ECB模式的安全缺陷,可让重复的明文分组产生不一样的密文分组,CBC模式就可知足这一要求。如图2所示,在CBC模式中,一次对一个明文分组加密,每次加密使用同一密钥,加密算法的输入是当前明文分组和前一次密文分组的异或,所以加密算法的输入不会显示出于此次的明文之间的固定关系,因此重复的明文分组不会在密文中暴露出这种重复关系。.net
图2 CBC模式示意图code
在产生第一个密文分组时,须要有一个IV与第一个明文分组异或。解密时,IV和解密算法对第一个密文分组的输出进行异或以恢复第一个明文分组。IV和密钥同样对于收发双方都是已知的,为了使安全性最高,IV应像密钥同样被保护。blog
在.NET平台提供的分组加密类默认使用的是CBC模式,可是能够根据须要更改此默认设置。ip
在实现数据加解密主要涉及到System.Security.Cryptography
下的RijndaelManaged
和CryptoStream
类。前面提到.NET平台的分组加密类默认使用的是CBC模式,因此首先要生成密钥Key和IV。在生成RijndaelManaged
实例时默认会生成一组长度为16字节随机的Key和IV,在本示例中为了省去通讯双方的密钥交换过程,直接指定了Key和IV,加解密都相同。具体看代码,看注释。
1 //建立RijndaelManaged实例 2 RijndaelManaged RMCrypto = new RijndaelManaged(); 3 //byte[] key = RMCrypto.Key; 4 //byte[] IV = RMCrypto.IV; 5 //初始化Key,IV 6 byte[] Key = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }; 7 byte[] IV = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }; 8 9 Console.WriteLine("connecte successed! Enter the message to send:"); 10 string sMessage = Console.ReadLine(); 11 //把明文消息转换成UTF8编码的字节流,避免乱码 12 byte[] messageByte = Encoding.UTF8.GetBytes(sMessage); 13 //实例化一个MemoryStream用于存放加密后的数据流 14 MemoryStream mStream = new MemoryStream(); 15 //建立用于加密的CryptoStream实例 16 CryptoStream CryptStream = new CryptoStream(mStream, 17 RMCrypto.CreateEncryptor(Key, IV), 18 CryptoStreamMode.Write); 19 //把明文消息字节流写入到CryptoStream中,进行加密处理 20 CryptStream.Write(messageByte,0,messageByte.Length); 21 //把CryptoStream中的数据更新到MemoryStream中 22 CryptStream.FlushFinalBlock(); 23 //把加密后的数据流转换成字节流 24 byte[] encryptoByte = mStream.ToArray();
1 //建立一个MemoryStream实例,存放收到的加密数据字节流 2 MemoryStream encryptoStream = new MemoryStream(encryptoByte); 3 //建立RijndaelManaged实例 4 RijndaelManaged RMCrypto = new RijndaelManaged(); 5 //byte[] key = RMCrypto.Key; 6 //byte[] IV = RMCrypto.IV; 7 //初始化Key,IV 8 byte[] Key = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }; 9 byte[] IV = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }; 10 11 12 //建立用于解密的CryptoStream实例 13 CryptoStream CryptStream = new CryptoStream(encryptoStream, 14 RMCrypto.CreateDecryptor(Key, IV), 15 CryptoStreamMode.Read); 16 17 //建立StreamReader实例,从CryptoStream中读出数据, 18 //StreamReader默认使用UTF8编码读出的数据 19 StreamReader SReader = new StreamReader(CryptStream); 20 21 //输出解密后的消息. 22 Console.WriteLine("The decrypted original message: {0}",SReader.ReadToEnd());
数据传输使用的是TCP链接,.net平台也对Socket进行了很好的封装,使网络IO操做很是方便。在密文数据发送前被编码成Base64形式的字符串,一个是方便加密数据的正常显示,另外一方面是便于数据接收端在接收字节流的数据时便于转码成字符串。Base64是用64个可打印的ASCII码字符来表示二进制数据,因此Base64字符串与字节流的转换是一对一的转换,即一个字符对应一个字节,这样在进行字节流与字符串间的转换时不会因编码方式的不一样出现误差,形成后续的解密操做出现异常。
1 //建立TCP链接 2 TcpClient TCP = new TcpClient("localhost", 11000); 3 4 //从TCP链接中得到网络数据流 5 NetworkStream NetStream = TCP.GetStream(); 6 7 //便于显示,将加密后的数据字节流转成Base64编码的字符串 8 string encryptBase64 = Convert.ToBase64String(encryptoByte); 9 //将字符串转成字节流 10 encryptoByte = Encoding.ASCII.GetBytes(encryptBase64); 11 12 //把加密后的数据写入到NetworkStream中,发送到服务端。 13 NetStream.Write(encryptoByte, 0, encryptoByte.Length); 14 Console.WriteLine("The encryptoed message: {0}", encryptBase64); 15 Console.WriteLine("The message was sent.");
1 //初始化TCPListen绑定IP地址和监听端口 2 TcpListener TCPListen = new TcpListener(IPAddress.Any, 11000); 3 4 //开始监听 5 TCPListen.Start(); 6 7 //每隔五秒钟,检查是否有链接 8 while (!TCPListen.Pending()) 9 { 10 Console.WriteLine("Still listening. Will try in 5 seconds."); 11 Thread.Sleep(5000); 12 } 13 14 //接受TCP链接. 15 TcpClient TCP = TCPListen.AcceptTcpClient(); 16 17 //为此链接建立NetworkStream. 18 NetworkStream NetStream = TCP.GetStream(); 19 20 //循环从NetworkStream中读出数据 21 string encryptoString = ""; 22 int bytes; 23 while (true) 24 { 25 byte[] byteMessage = new byte[10]; 26 bytes = NetStream.Read(byteMessage, 0, 10); 27 if (bytes <= 0) 28 { 29 break; 30 } 31 //加密后的数据是经过Base64编码成字符串后发送,可直接经过ASCII编码将字节转成ASCII码字符 32 //组装成完整的Bas64编码的字符串 33 encryptoString += Encoding.ASCII.GetString(byteMessage,0,bytes); 34 } 35 Console.WriteLine("The Encryptoed Message: {0}", encryptoString); 36 //把Base64编码的字符串转换成字节流 37 byte[] encryptoByte = Convert.FromBase64String(encryptoString);
因CryptoStream类使用的派生自Stream的类进行初始化,因此在本示例程序中能够直接使用NetworStream替代MemoryStream建立CryptoStream示例。示例程序见MSDN-加密数据。示例程序使用MemoryStream是便于得到加密后的数据。