分組密碼是將明文消息編碼表示後數字序列劃分紅長爲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是便於得到加密後的數據。