簡介
在之前的文章中,我們講到了怎麼使用netty建立聊天室,但是這樣的簡單的聊天室太容易被竊聽了,如果想要在裡面說點悄悄話是很不安全的,怎麼辦呢?學過密碼學的朋友可能就想到了一個解決辦法,聊天的時候對消息加密,處理的時候再對消息解密即可。
當然在netty中上述的工作都不需要我們手動來實作,netty已經提供了支援SSL的channel供我們選擇,一起來看看吧。
PKI标準
在講netty的具體支援之前,我們需要先了解一下公鑰和私鑰的加密标準體系PKI。PKI的全稱是Public Key Infrastructure,也就是公鑰體系。用于規範公鑰私募進行加密解密的規則,進而便于不同系統的對接。
事實上PKI标準已經有兩代協定了。
第一代的PKI标準主要是由美國RSA公司的公鑰加密标準PKCS,國際電信聯盟的ITU-T X.509,IETF的X.509,WAP和WPKI等标準組成。但是因為第一代PKI标準是基于抽象文法符号ASN.1進行編碼的,實作起來比較複雜和困難,是以産生了第二代PKI标準。
第二代PKI标準是由微軟、VeriSign和webMethods三家公司在2001年釋出的基于XML的密鑰管理規範也叫做XKMS。
事實上現在CA中心使用的最普遍的規範還是X.509系列和PKCS系列。
X.509系列主要由X.209、X.500和X.509組成,其中X.509是由國際電信聯盟(ITU-T)制定的數字證書标準。在X.500基礎上進行了功能增強,
X.509是在1988年釋出的。X.509證書由使用者公共密鑰和使用者辨別符組成。此外還包括版本号、證書序列号、CA辨別符、簽名算法辨別、簽發者名稱、證書有效期等資訊。
而PKCS是美國RSA公司的公鑰加密标準,包括了證書申請、證書更新、證書廢棄表釋出、擴充證書内容以及數字簽名、數字信封的格式等方面的一系列相關協定。它定義了一系列從PKCS#1到PKCS#15的标準。
其中最常用的是PKCS#7、PKCS#12和PKCS#10。PKCS#7 是消息請求文法,常用于數字簽名與加密,PKCS#12是個人消息交換與打包文法主要用來生成公鑰和私鑰。PKCS#10是證書請求文法。
各類證書的字尾和轉換
操作過證書的朋友可能會對證書的字尾眼花缭亂,一般來說會有DER、CRT、CER、PEM這幾種證書的字尾。
DER表示證書的内容是用二進制進行編碼的。
PEM檔案是一個文本檔案,其内容是以“ – BEGIN -” 開頭的,Base64編碼的字元。
CRT和CER基本上是等價的,他們都是證書的擴充,也是文本檔案,不同的是CRT通常用在liunx和unix系統中,而CER通常用在windows系統中。并且在windows系統中,CER檔案會被MS cryptoAPI指令識别,可以直接顯示導入和/或檢視證書内容的對話框。
KEY檔案,主要用來儲存PKCS#8标準的公鑰和私鑰。
下面的指令可以用來檢視文本證書内容:
openssl x509 -in cert.pem -text -noout
openssl x509 -in cert.cer -text -noout
openssl x509 -in cert.crt -text -noout
下面的指令可以用來檢視二進制證書内容:
openssl x509 -in cert.der -inform der -text -noout
下面是常見的PEM和DER互相轉換:
PEM到DER
openssl x509 -in cert.crt -outform der-out cert.der
DER到PEM
openssl x509 -in cert.crt -inform der -outform pem -out cert.pem
netty中啟動SSL server
事實上這個标題是不對的,netty中啟動的server還是原來那個server,隻是對發送的消息進行了加密解密處理。也就是說添加了一個專門進行SSL操作的Handler。
netty中代表ssl處理器的類叫做SslHandler,它是SslContext工程類的一個内部類,是以我們隻需要建立好SslContext即可通過調用newHandler方法來傳回SslHandler。
讓伺服器端支援SSL的代碼:
ChannelPipeline p = channel.pipeline();
SslContext sslCtx = SslContextBuilder.forServer(...).build();
p.addLast("ssl", sslCtx.newHandler(channel.alloc()));
讓用戶端支援SSL的代碼:
ChannelPipeline p = channel.pipeline();
SslContext sslCtx = SslContextBuilder.forClient().build();
p.addLast("ssl", sslCtx.newHandler(channel.alloc(), host, port));
netty中SSL的實作有兩種方式,預設情況下使用的是OpenSSL,如果OpenSSL不可以,那麼将會使用JDK的實作。
要建立SslContext,可以調用SslContextBuilder.forServer或者SslContextBuilder.forClient方法。
這裡以server為例,看下建立流程。SslContextBuilder有多種forServer的方法,這裡取最簡單的一個進行分析:
public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) {
return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile);
}
該方法接收兩個參數,keyCertChainFile是一個PEM格式的X.509證書檔案,keyFile是一個PKCS#8的私鑰檔案。
熟悉OpenSSL的童鞋應該知道使用openssl指令可以生成私鑰檔案和對應的自簽名證書檔案。
具體openssl的操作可以檢視我的其他文章,這裡就不詳細講解了。
除了手動建立證書檔案和私鑰檔案之外,如果是在開發環境中,大家可能希望有一個非常簡單的方法來建立證書和私鑰檔案,netty為大家提供了SelfSignedCertificate類。
看這個類的名字就是知道它是一個自簽名的證書類,并且會自動将證書檔案和私鑰檔案生成在系統的temp檔案夾中,是以這個類在生産環境中是不推薦使用的。預設情況下該類會使用OpenJDK’s X.509來生成證書的私鑰,如果不可以,則使用 Bouncy Castle作為替代。
netty中啟動SSL client
同樣的在client中支援SSL也需要建立一個handler。用戶端的SslContext建立代碼如下:
// 配置 SSL.
final SslContext sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
上面的代碼我們使用了一個InsecureTrustManagerFactory.INSTANCE作為trustManager。什麼是trustManager呢?
當用戶端和伺服器端進行SSL連接配接的時候,用戶端需要驗證伺服器端發過來證書的正确性,通常情況下,這個驗證是到CA伺服器中進行驗證的,不過這樣需要一個真實的CA憑證環境,是以在測試中,我們使用InsecureTrustManagerFactory,這個類會預設接受所有的證書,忽略所有的證書異常。
當然,CA伺服器也不是必須的,用戶端校驗的目的是檢視證書中的公鑰和發送方的公鑰是不是一緻的,那麼對于不能聯網的環境,或者自簽名的環境中,我們隻需要在用戶端校驗證書中的指紋是否一緻即可。
netty中提供了一個FingerprintTrustManagerFactory類,可以對證書中的指紋進行校驗。
該類中有個fingerprints數組,用來存儲安全的授權過的指紋資訊。通過對比傳入的證書和指紋,如果一緻則校驗通過。
使用openssl從證書中提取指紋的步驟如下:
openssl x509 -fingerprint -sha256 -in my_certificate.crt
總結
通過設定client和server端的SSL handler,就可以實作用戶端和伺服器端的加密消息傳輸。
本文的例子可以參考:
learn-netty4本文已收錄于 http://www.flydean.com/12-netty-securechat/最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!
歡迎關注我的公衆号:「程式那些事」,懂技術,更懂你!