在即時通訊系統(IM)中,加密重要的通訊訊息,是一個常見的需求。尤其在一些政府部門的即時通訊軟體中(如稅務系統),對即時聊天訊息進行加密是非常重要的一個功能,因為談話中可能會涉及到機密的資料。我在最新的GG 4.5中,增加了對即時聊天訊息進行加密的功能,但這一功能並不是強制的,可以通過開關來進行控制。本文就從 為什麼要加密訊息、不加密有什麼風險開始說起,一直到把GG即時通訊系統中實現加密訊息的完整實現介紹清楚。
想要直接下載體驗的朋友請點選:下載中心
一.為什麼要加密即時聊天訊息?
我們知道所有的訊息在底層是以bytep[]進行傳輸的,如果文字聊天訊息不加密,表示的意思是:直接將string使用utf-8或者unicode編碼成byte[],然後,通過網路進行傳送。如果在傳送過程中的某個環節byte[]被惡意擷取,則攔截者將byte[]使用utf-8或unicode進行解碼,即可看到原來string的內容。這個過程如下圖所示:
對於某些重要的訊息而言,這樣明文傳輸的方式實在是太危險了。
將聊天訊息加密的意思是:將string使用utf-8或unicode編碼成byte[]後,再做一次加密運算,得到一個新的byte[],然後將這個新的byte[]通過網路傳送給對方;對方接收到byte[]後,先將其做解密運算,然後再用utf-8或unicode轉為string。這個新過程如下圖所示:
這樣,即使在網路傳送過程中的某個環節byte[]被惡意擷取了,攔截者也無法正確的解析它,如此就規避了原來方案的風險。
二.3DES加密
3DES(或稱為Triple DES)是非常常用的對稱加密演算法,是對DES演算法的增強,它相當於是對每個資料塊應用三次DES加密演算法。
在GG即時通訊系統 4.5的客戶端原始碼中,Des3Encryption類是實現3DES演算法的類,我是根據3DES的演算法原理實現的,可能與某些標準的3DES演算法實現細節不一樣,但是,使用其進行3DES加密、解密是完全能正常運作的。
可以將Des3Encryption類作為一個工具類,從GG即時通訊系統中抽離出來,複用在任何需要的地方。
三.加密/解密即時聊天訊息
現在我們正式回到GG即時通訊系統的文字聊天邏輯上面來,看看GG是怎麼實現聊天訊息的加密解密的。
1.準備工作
GG2014客戶端專案中,增加了Des3Encryption.cs檔案,實現了3DES演算法。
GlobalResourceManager類增加了加密元件的設定:
private static Des3Encryption des3Encryption = new Des3Encryption("abcd1234"); // null; /// <summary> /// 3DES加密。如果訊息不需要加密,則返回null。 /// </summary> public static Des3Encryption Des3Encryption { get { return des3Encryption; } }
這裡有一個開關的功能,即可以開啟或關閉聊天訊息加密功能。如果將des3Encryption設定為null,就表示不啟用聊天訊息加密。
2.傳送聊天訊息
在GG即時通訊系統中,聊天訊息有兩類,一類是1對1的聊天,另一類是群聊天。如果啟用了加密,兩類聊天訊息都需要做相應的處理,它們的流程是一樣的。
在得到聊天內容後,先進行簡單的序列化,然後對序列化的結果進行3DES加密:(以1對1聊天的ChatForm視窗中的實現為例,原始碼的第866行)
ChatBoxContent content = this.chatBoxSend.GetContent(); byte[] buff = CompactPropertySerializer.Default.Serialize(content); byte[] encrypted = buff; if (GlobalResourceManager.Des3Encryption != null) { encrypted = GlobalResourceManager.Des3Encryption.Encrypt(buff); }
然後,將加密的結果通過IRapidPassiveEngine傳送出去。
3.處理接收到的聊天訊息
接收到1對1的聊天訊息或是群聊天訊息後,首先要做的是解密,然後再反序列化:(以1對1聊天訊息的實現為例,MainFormPartial.cs檔案中的原始碼的第37行)
byte[] decrypted = info; if (GlobalResourceManager.Des3Encryption != null) { decrypted = GlobalResourceManager.Des3Encryption.Decrypt(info); } ChatBoxContent content = CompactPropertySerializer.Default.Deserialize<ChatBoxContent>(decrypted, 0);
之後,ChatBoxContent物件就可以在聊天窗中顯示出來了。
4.處理離線訊息
離線訊息是當接收者不再時,將該聊天訊息暫存在伺服器上,等接收者上線時,再傳送給他。所以,離線訊息的解密處理與普通聊天訊息的處理是一樣的。(MainFormPartial.cs檔案中的原始碼的第86行)
if (informationType == InformationTypes.OfflineMessage) { byte[] bChatBoxContent = null; OfflineMessage msg = CompactPropertySerializer.Default.Deserialize<OfflineMessage>(info, 0); if (msg.InformationType == InformationTypes.Chat) //目前只處理離線的聊天訊息 { sourceUserID = msg.SourceUserID; bChatBoxContent = msg.Information; byte[] decrypted = bChatBoxContent; if (GlobalResourceManager.Des3Encryption != null) { decrypted = GlobalResourceManager.Des3Encryption.Decrypt(bChatBoxContent); } ChatBoxContent content = CompactPropertySerializer.Default.Deserialize<ChatBoxContent>(decrypted, 0); } }
四.聊天記錄要怎麼處理了?
根據上面的流程描述,我們可以知道,在服務端看到的聊天訊息是經過加密的,而GG在服務端有將聊天記錄儲存到資料庫中的功能,因此,資料庫中聊天內容那一列儲存的資料也是加密的。
在GG即時通訊系統中,服務端不需要檢視聊天訊息的真正內容,所以,服務端不需要使用到Des3Encryption類。
GG在客戶端本地也有儲存聊天記錄(使用Sqlite),與伺服器上資料庫中儲存的不一樣的是,本地儲存的是明文的。所以,在檢視聊天記錄時,要根據使用者選擇的是從本地檢視還是從伺服器檢視來決定是否需要對資料進行解密:(對應ChatRecordForm窗體,原始碼177行)
byte[] decrypted = record.Content; if (this.skinRadioButton_Server.Checked) { if (GlobalResourceManager.Des3Encryption != null) { decrypted = GlobalResourceManager.Des3Encryption.Decrypt(decrypted); } } ChatBoxContent content = CompactPropertySerializer.Default.Deserialize<ChatBoxContent>(decrypted, 0);
五.原始碼下載
GGTalk即時通訊系統是可在廣域網部署執行的C#開源即時通訊系統,2013.8.7釋出V1.0版本,至今最新是4.5版本,關於GG更詳細的介紹,可以檢視 可在廣域網部署執行的QQ高仿版 -- GG2014總覽。
1.GG服務端和PC端原始碼
原始碼下載:GG-V4.5.rar 網盤下載更快
部署下載:GG V4.5 可直接部署版本 網盤下載更快
(壓縮包中有 《部署說明.txt》 和 建立資料庫的指令碼 《GG2014.sql》)
2.GG安卓版原始碼
自從GG4.4版本開始,GG增加了安卓版本,其執行介面截圖如下所示:
原始碼下載:GG-android.rar 網盤下載更快
若要測試,請先部署服務端,然後修改安卓原始碼中MainActivity中的伺服器的IP和埠(如下圖所示),並重新編譯生成apk。
(若要和PC端聯合測試,請關閉PC端那邊的聊天訊息加密功能:將PC客戶端專案的GlobalResourceManager類的 des3Encryption 成員賦值為 null 即可!)
注:GG安卓版的原始碼質量不是很高,屬於安卓初學者水平,很多地方有待改進,目前只是展示與PC打通的功能如何實現。若要將GG安卓版本的原始碼用於正式專案中,建議先對其進行重構。