在前面兩篇介紹了Socket框架的設計思路以及數據傳輸方面的內容,整個框架的設計指導原則就是易於使用及安全性較好,可以用來從客戶端到服務端的數據安全傳輸,那麼實現這個目標就需要設計好消息的傳輸和數據加密的處理。本篇主要介紹如何利用Socket傳輸協議來實現數據加密和數據完整性校驗的處理,數據加密我們可以采用基於RSA非對稱加密的方式來實現,數據的完整性,我們可以對傳輸的內容進行MD5數據的校驗對比。
前面介紹過Socket的協議,除了起止標識符外,整個內容是一個JSON的字符串內容,這種格式如下所示。
上述消息內容,我們可以通過開始標識位和結束標識位,抽取出一個完整的Socket消息,這樣我們對其中的JSON內容進行序列號就可以得到對應的實體類,我們定義實體類的內容如下所示。
我們把消息對象分為請求消息對象和應答消息對象,他們對應的是Request和Response的消息,也就是一個是發起的消息,一個是應答的消息。其中上圖的“承載的JSON內容就是我們另一個傳輸對象的JSON字符串,這樣我們通過這種字符串來傳輸不同對象的信息,就構造出了一個通用的消息實體對象。
另外這些傳輸的消息對象,它本身可以繼承於一個實體類的基類,這樣方便我們對它們的統一處理,如下圖所示,就是一個通用的消息對象BaseMessage和其中JSON內容的對象關系圖,如AuthRequest是登陸驗證請求,AuthorRepsonse是登陸驗證的應答。
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { this.RSAPublicKey = rsa.ToXmlString(false);// 公鑰 this.RSAPrivateKey = rsa.ToXmlString(true);// 私鑰 }
例如在服務器端,在客戶端Socket成功接入後,我們就給對應的客戶端發送公鑰請求消息,如下代碼所示。
/// <summary> /// 客戶端連接後的處理(如發送公鑰秘鑰) /// </summary> /// <param name="client">連接客戶端</param> protected override void OnAfterClientConnected(ClientOfShop client) { //先記錄服務端的公鑰,私鑰 client.RSAPrivateKey = Portal.gc.RSAPrivateKey; client.RSAPublicKey = Portal.gc.RSAPublicKey; //發送一個公鑰交換命令 var request = new RsaKeyRequest(Portal.gc.RSAPublicKey); var data = request.PackData(); client.SendData(data); Thread.Sleep(100); }
那麼在客戶端,接收到服務端的消息後,對消息類型判斷,如果是公鑰請求,那麼我們需要進行回應,把自己的公鑰發給服務器,否則就進行其他的業務處理了。
/// <summary> /// 重寫讀取消息的處理 /// </summary> /// <param name="message">獲取到的完整Socket消息對象</param> protected override void OnMessageReceived(BaseMessage message) { if (message.MsgType == DataTypeKey.RSARequest) { var info = JsonTools.DeserializeObject<RsaKeyRequest>(message.Content); if (info != null) { //記錄對方的公鑰到Socket對象裡面 this.PeerRSAPublicKey = Portal.gc.UseRSAEncrypt ? info.RSAPublicKey : ""; Console.WriteLine("使用RAS加密:{0},獲取到加密公鑰為:{1}", Portal.gc.UseRSAEncrypt, info.RSAPublicKey); //公鑰請求應答 var publicKey = Portal.gc.UseRSAEncrypt ? Portal.gc.RSAPublicKey : ""; var data = new RsaKeyResponse(publicKey);//返回客戶端的公鑰 var msg = data.PackData(message); SendData(msg); Thread.Sleep(100);//暫停下 } } else { //交給業務消息處理過程 this.MessageReceiver.AppendMessage(message); this.MessageReceiver.Check(); } }
如果我們交換成功後,我們後續的消息,就可以通過RSA非對稱加密進行處理了,如下代碼所示。
data.Content = RSASecurityHelper.RSAEncrypt(this.PeerRSAPublicKey, data.Content);
而解密消息,則是上面代碼的逆過程,如下所示。
message.Content = RSASecurityHelper.RSADecrypt(this.RSAPrivateKey, message.Content);
最後我們把加密後的內容組成一個待發送的Socket消息,包含起止標識符,如下所示。
//轉為JSON,並組裝為發送協議格式 var json = JsonTools.ObjectToJson(data); toSendData = string.Format("{0}{1}{2}", (char)this.StartByte, json, (char)this.EndByte);
這樣就是我們需要發送的消息內容了,我們攔截內容,可以看到大概的內容如下所示。
上面紅色框的內容,必須使用原有的私鑰才能進行解密,也就是在網絡上,被誰攔截了,也無法進行解開,保證了數據的安全性。
數據的完整性,我們可以通過消息內容的MD5值進行比對,實現檢查是否內容被篡改過,不過如果是采用了非對稱加密,這種 完整性檢查也可以忽略,不過我們可以保留它作為一個檢查處理。
因此在封裝數據的時候,就把內容部分MD5值計算出來,如下所示。
data.MD5 = MD5Util.GetMD5_32(data.Content);//獲取內容的MD5值
然後在獲得消息,並進行解密後(如果有),那麼在服務器端計算一下MD5值,並和傳遞過來的MD5值進行比對,如果一致則說明沒有被篡改過,如下代碼所示。
var md5 = MD5Util.GetMD5_32(message.Content); if (md5 == message.MD5) { OnMessageReceived(message);//給子類重載 } else { Log.WriteInfo(string.Format("收到一個被修改過的消息:\r\n{0}", message.Content)); }
以上就是我在Socket開發框架裡面,實現傳輸數據的非對稱加密,以及數據完整性校驗的處理過程。