手把手教你為基于Netty的IM生成自簽名SSL/TLS證書

1、引言
對于IM聊天應用來說,為了提升安全性,對聊天消息加密是常規(guī)操作。
眾所周之,Netty是高性能的Java NIO網絡通信框架,因而用Netty來寫IM是再正常不過了。網上關于為Netty生成、以及使用SSL/TLS證書的文章有很多,但由于各種原因,生成的證書要么是Netty中無法讀取和使用,要么是代碼不全或不具體導致根本配不通SSL/TLS加密。
正好這段時間專門為?MobileIMSDK?生成了一套測試證書,順手把這個過程記錄了下來,分享給大家。
本文要分享的是如何使用OpenSSL生成在基于Netty的IM中真正可用的SSL/TLS證書,內容包括:證書的創(chuàng)建、創(chuàng)建過程中的注意點,以及在Server端、Android端、iOS端、Java桌面端、H5端使用證書的代碼范例。

注:對于那些付費購買了第3方權威CA機構簽發(fā)的證書,他們都有相應的使用文檔,這就沒什么好說的。本文里的證書指的是不需要花錢的自簽名證書。
學習交流:
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發(fā)布于:http://www.52im.net/thread-4142-1-1.html)
2、知識準備
? 如果你對IM系統(tǒng)毫無概念,建議先閱讀《零基礎IM開發(fā)入門(一):什么是IM系統(tǒng)?》系列文章,通俗易懂,適合小白。
? 如果你想系統(tǒng)學習IM開發(fā)相關的理論知識,比如網格編程、IM架構設計等,建議先閱讀《新手入門一篇就夠:從零開發(fā)移動端IM》。
??如果你不了解Netty是什么,建議閱讀以下幾篇Netty的基礎入門好文章:
1)新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析
2)寫給初學者:Java高性能NIO框架Netty的學習方法和進階策略
3)史上最通俗Netty框架入門長文:基本介紹、環(huán)境搭建、動手實戰(zhàn)
? 如果你已掌握IM理論知識,同時也對Netty基本掌握,正準備動手實戰(zhàn),則可以閱讀《基于Netty,從零開發(fā)IM》和《跟著源碼學IM》這個系列文章,有各種入門級實戰(zhàn)代碼,圖文并茂,適合學習。
? 如果你對IM、Netty已基本上手,但對IM安全方面的技術概念有點理不清,建議必讀《基于Netty的IM聊天加密技術學習:一文理清常見的加密概念、術語等》。
3、什么是Netty

Netty是一個Java NIO技術的開源異步事件驅動的網絡編程框架,用于快速開發(fā)可維護的高性能協(xié)議服務器和客戶端。往通俗了講,可以將Netty理解為:一個將Java NIO進行了大量封裝,并大大降低Java NIO使用難度和上手門檻的超牛逼框架。(引用自《史上最通俗Netty框架入門長文:基本介紹、環(huán)境搭建、動手實戰(zhàn)》)
PS:限于篇幅,對于Netty方面的入門知識就不再贅述,如有必要,請仔細跟著本文第二節(jié)“2、知識準備”里有關Netty的文章進行閱讀。
4、什么是OpenSSL

OpenSSL是一個開放源代碼的軟件庫,應用程序可以使用這個包來進行安全通信,它包括代碼、腳本、配置和過程的集合。其主要庫是以 C 語言所寫成,實現(xiàn)了基本的加密功能,實現(xiàn)了 SSL 與 TLS 協(xié)議。OpenSSL整個軟件包大概可以分成三個主要功能部分:SSL協(xié)議庫、應用程序、密碼算法庫。
PS:OpenSSL的介紹就點到為止,如有興趣,可仔細閱讀《基于Netty的IM聊天加密技術學習:一文理清常見的加密概念、術語等》。
5、下載和安裝OpenSSL
1)方法一:可以從OpenSSL的Github倉庫下載源碼自行編譯(源碼下載地址),對于一般使用者來說,自已編譯著實有點麻煩,不推薦這么玩。
2)方法一:也可以從這個網站下載第3方編譯好的OpenSSL安裝程序(安裝程序下載地址),這樣上手簡單快捷。具體可以參考《openssl安裝教程(windows7系統(tǒng),超詳細)》這篇文章。
3)方法一:也可以直接用下面附件里的安裝程序(這是我一直用的版本,版本較老,有興趣可直接下載使用):
?Openssl-windows-0.9.8k(52im.net).rar?(874.97 KB?, 下載次數:?1?, 售價:?1?金幣)
4)解決 “openssl.cnf找不到” 的問題:如果你安裝好OpenSSL后,使用時報“openssl.cnf找不到”或“計算機缺少openssl.cnf”等之類錯誤提示,可以下載下面這個?openssl.cnf文件。
openssl.cnf?文件附件下載:
?openssl_conf(52im.net).rar?(4.63 KB?, 下載次數:?1?, 售價:?1?金幣)
openssl.cnf?文件解壓縮后:

openssl.cnf文件配置使用:

以下是?openssl.cnf?文件的配置使用命令:(以我的安裝目錄為例)
C:\Openssl-windows-0.9.8k-out32dll>set OPENSSL_CONF=c:/WINDOWS/system32/openssl.cnf
準備就緒,接下來我們就可以開始生成SSL/TLS證書了!
6、生成Netty可用的SSL/TLS證書
6.1概述
經過實踐,生成Netty可用的SSL/TLS證書需要4步:
1)創(chuàng)建私鑰證書;
2)將私鑰格式轉成pk8;
3)創(chuàng)建證書請求;
4)生成公鑰證書。
接下來,跟著本節(jié)內容,一步步使用OpenSSL生成一個真正能在Netty中能使用的自簽名證書。
6.2第一步:創(chuàng)建私鑰證書
在CMD控制臺下執(zhí)行如下指令:(記得手動創(chuàng)建?netty?目錄)
openssl genrsa -des3 -out netty/netty-key.pem 1024

提示:以上指令中,如無“-des3”參數,則Netty的代碼中使用時將報“File does not contain valid private key”等錯誤(如下圖所示)。

6.3第二步:將私鑰格式轉成pk8
在CMD控制臺下執(zhí)行如下指令:
openssl pkcs8 -innetty/netty-key2.pem -topk8 -out netty/netty-key2.pk8

提示1:如不轉pk8格式,則Netty的代碼中使用時會報以下錯誤:

提示2:如代碼中不為key加入密碼,則Netty的代碼中使用時會報以下錯誤:

提示3:Netty的代碼中使用時要加入上方生成Key證書時的密碼即可:

6.4第三步:創(chuàng)建證書請求
在CMD控制臺下執(zhí)行如下指令:
openssl req -new -out netty/netty-req2.csr -key netty/netty-key2.pem

提示:經上指令中,Common Name指明的是證書綁定的域名,你可以用域名或ip,本次生成用了子域名。
6.5第四步:生成公鑰證書
在CMD控制臺下執(zhí)行如下指令:
openssl x509 -req -inca/ca-req2.csr -out netty/netty-cert2.crt -signkey netty/netty-key2.pem -days 3650

提示:out?參數生成的是.crt,而在前面的是.pem,這只是擴展名區(qū)別,內容都一樣。
6.6最終成果

?

至此,我們已經為Netty創(chuàng)建好了證書,接下來的章節(jié),就是分享如何讀取和使用這些證書的。
7、實戰(zhàn)代碼
7.1概述
本節(jié)將為你演示如何在基于Netty的IM中使用上節(jié)中生成的證書。
為了讓示例代碼更具實戰(zhàn)意義,本節(jié)的示例代碼將引用的是開源IM框架MobileIMSDK??的源碼,如果有興趣深入學習,可以從下面的開源倉庫中下載到MobileIMSDK的完整源碼。
1)GitHub.com 托管地址:https://github.com/JackJiang2011/MobileIMSDK
2)碼云gitee托管地址:https://gitee.com/jackjiang/MobileIMSDK
7.2基于Netty的IM服務端如何開啟SSL/TLS
首先將上節(jié)中生成的證書,放置到你的IM服務端磁盤目錄下。以下截圖和示例代碼以MobileIMSDK的開源代碼為例。
我們可以將證書放到這個位置:

使用證書的示例代碼片段:(完整代碼詳見?ServerLauncherImpl.java)
/**
?* 創(chuàng)建SslContext對象,用于開啟SSL/TLS加密傳輸。
?*
?* @return 如果成功創(chuàng)建則返回SslContext對象,否則返回null
?*/
privatestaticSslContext createSslContext() {???????????????
????try{
?????????// 證書文件
?????????InputStream certChainFile = ServerLauncherImpl.class.getResourceAsStream("certs/netty-cert2.crt");
?????????// 私鑰文件(注意:Netty只支持.pk8格式)
?????????InputStream keyFile = ServerLauncherImpl.class.getResourceAsStream("certs/netty-key2.pk8");
?????????// 私鑰密碼
?????????String keyPassword = "123456";
?????????// 生成SslContext對象(為了方便理解,此處使用的是單向認證)
?????????SslContext sslCtx = SslContextBuilder.forServer(certChainFile, keyFile, keyPassword).clientAuth(ClientAuth.NONE).build();?????????????????
?????????returnsslCtx;
????} catch(Exception e) {
????????logger.warn("createSslContext()時出錯了,原因:"+e.getMessage(), e);
????}
????returnnull;
}
PS:如果你想自已動手完整運行一下,可以閱讀《MobileIMSDK的Demo使用幫助:Server端》。
接下來的內容,我們將實現(xiàn)客戶端連接到使用SSL/TLS證書的Netty IM服務端。
7.3Android端如何開啟SSL/TLS
因為服務端已經開啟了SSL/TLS加密,我們在開發(fā)IM的客戶端時,該如何啟用SSL/TLS呢(否則你未開啟SSL/TLS的客戶端肯定是連不上你的服務端的)?
這里為了方便示例,我們同樣以?MobileIMSDK的Android端開源代碼為例。
Android端開啟SSL/TLS加密的示例代碼片段:(完整代碼詳見?IMClientManager.java)
/**
?* 創(chuàng)建SslContext對象,用于開啟SSL/TLS加密傳輸。
?*
?* @return 如果成功創(chuàng)建則返回SslContext對象,否則返回null
?*/
publicSslContext createSslContext() {
????????SslContext sslContext = null;
????????try{
????????????????sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
????????????????Log.d(TAG, "【IMCORE-TCP】已開啟SSL/TLS加密(單向認證),且sslContext創(chuàng)建成功。");
????????} catch(Exception e) {
????????????????Log.w(TAG, "【IMCORE-TCP】創(chuàng)建sslContext時出錯,原因是:"+ e.getMessage(), e);
????????}
?
????????returnsslContext;
}
PS:如果你想自已動手完整運行一下,可以閱讀《MobileIMSDK的Demo使用幫助:Android版》。
7.4iOS端如何開啟SSL/TLS
同樣的,iOS端該如何開啟SSL/TLS呢?
這里我們依然以?MobileIMSDK的iOS端開源代碼為例(MobileIMSDK的iOS使用的是?CocoaAsyncSocket?網絡庫,如果你也是用的它,就可以直接參考了,因為開啟了SSL/TLS的CocoaAsyncSocket代碼跟未開啟加密的代碼用法差異較多,且這方面可以參考的資料較少)。
iOS端開啟SSL/TLS加密的示例代碼片段:(完整代碼詳見?LocalSocketProvider.m)
/**
?* 當socket已經完整連接并準備好讀和寫數據時,將調用此方法。
?*/
- (void)socket:(MBGCDAsyncSocket *)socket didConnectToHost:(NSString*)host port:(uint16_t)port
{
????if([ClientCoreSDK isENABLED_DEBUG])
????????NSLog(@"【IMCORE-TCP-SOCKET】成收到的了TCP的connect反饋, isConnected? %d、已開啟ssl加密? %d", [socket isConnected], [ClientCoreSDK isSSL]);
?
????// 如果未開啟SSL加密傳輸,則正常進入連接完成后的代碼邏輯
????if(![ClientCoreSDK isSSL]) {
????????[selfwhenDidConnect:socket];
????}
????// 如果已開啟SSL加密傳輸,則需要在回調中調用startTLS方法,以便實現(xiàn)跟服務端的SSL握手過程,
????// 如果ssl握手成功,則會通過 socketDidSecure: 回調通知開發(fā)者
????else{
????????// 配置 SSL/TLS 設置信息
????????NSMutableDictionary*settings = [NSMutableDictionarydictionaryWithCapacity:3];
????????// 允許自簽名證書手動驗證
????????[settings setObject:@YESforKey:GCDAsyncSocketManuallyEvaluateTrust];
????????// 經測試,本項不設置并不影響SSL的啟用
//????? [settings setObject:@"此處填服務器IP地址" forKey:GCDAsyncSocketSSLPeerName];
????????// 如果不是自簽名證書,而是權威證書頒發(fā)機構注冊申請的證書,這個settings字典可不傳(將使用GCDAsyncSocket的默認配置)
????????[socket startTLS:settings];
????}
}
?
/**
?* 當SSL握手成功后(也就是上方調用startSSL:方法后),將調用此方法。
?*/
- (void)socketDidSecure:(MBGCDAsyncSocket *)socket
{
????[selfwhenDidConnect:socket];
}
?
/**
?* Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to.
?*/
- (void)socket:(MBGCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void(^)(BOOLshouldTrustPeer))completionHandler
{
????NSLog(@"【IMCORE-TCP-SOCKET】didReceiveTrust...");
?
????// 以下沒有做更復雜的ssl證書驗證邏輯,如您需要實現(xiàn)更強大的雙向認證等邏輯,可以參考這里:
????// [url=https://github.com/FuangCao/cavan/blob/338ca8c09d6c78c5b38b95c6ffe994241afcc96e/xcode/TestSSL/TestSSL/ViewController.m]https://github.com/FuangCao/cava ... SL/ViewController.m[/url]
????if(completionHandler) {
????????completionHandler(YES);
????}
}
說明:CocoaAsyncSocket中開啟SSL/TLS并不像Android和Java中那么簡單,它不只是幾行代碼的事,而是整個數據讀取邏輯的變化。
PS:如果你想自已動手完整運行一下,可以閱讀《MobileIMSDK的Demo使用幫助:iOS版》。
7.5Java桌面端如何開啟SSL/TLS
Java桌面端開啟SSL/TLS的代碼跟Android端是一樣。我們同樣以?MobileIMSDK的Java端開源代碼為例。
Java桌面端開啟SSL/TLS加密的示例代碼片段:(完整代碼詳見?IMClientManager.java)
/**
?* 創(chuàng)建SslContext對象,用于開啟SSL/TLS加密傳輸。
?*
?* @return 如果成功創(chuàng)建則返回SslContext對象,否則返回null
?*/
publicSslContext createSslContext() {
????????SslContext sslContext = null;
????????try{
????????????????sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
????????????????Log.d(TAG, "【IMCORE-TCP】已開啟SSL/TLS加密(單向認證),且sslContext創(chuàng)建成功。");
????????} catch(Exception e) {
????????????????Log.w(TAG, "【IMCORE-TCP】創(chuàng)建sslContext時出錯,原因是:"+ e.getMessage(), e);
????????}
?
????????returnsslContext;
}
PS:如果你想自已動手完整運行一下,可以閱讀《MobileIMSDK的Demo使用幫助:Java版》。
7.6H5端如何開啟SSL/TLS
我這里說的H5端,指的是能支持標準HTML5端WebSocket協(xié)議的PC瀏覽器端、手機移動端內嵌的Web引擎等場景。
H5端能開啟SSL/TLS有兩個前提:
1)第3方CA機構簽發(fā)的SSL/TLS證書(這條是關鍵,不然瀏覽器因安全原因會阻止WebSocket連接的建立);
2)基于Netty的IM服務端已開啟SSL/TLS(見本章“7.2 基于Netty的IM服務端如何開啟SSL/TLS”)。
滿足以上兩點后,H5端什么代碼都不需改動,只需將請求url由“ws”改成“wss”:

8、參考資料
[1]?MobileIMSDK開源工程源碼
[2]?史上最通俗Netty框架入門長文:基本介紹、環(huán)境搭建、動手實戰(zhàn)
[3]?基于Netty,從零開發(fā)IM
[4]?基于Netty的IM聊天加密技術學習:一文理清常見的加密概念、術語等
[5]?IM聊天系統(tǒng)安全手段之通信連接層加密技術
[6]?通俗易懂:一篇掌握即時通訊的消息傳輸安全原理
[7]?探討組合加密算法在IM中的應用
[8]?openssl安裝教程(windows7系統(tǒng),超詳細)
[9]?WebSocket從入門到精通,半小時就夠!
(本文已同步發(fā)布于:http://www.52im.net/thread-4142-1-1.html)