開源項目SMSS發開指南(五)——SSL/TLS加密通訊詳解(下)

繼上一篇介紹如何在多種語言之間使用SSL加密通訊,今天咱們關注Java端的證書建立以及支持SSL的NioSocket服務端開發。完整源碼

1、建立keystore文件

網上大多數是經過jdk命令建立祕鑰文件,可是有時候咱們須要將配套的祕鑰以及證書讓多個模塊來使用,他們極可能是由不一樣語言開發。在這樣的情形下,咱們一般會選擇openssl。html

生成服務端的祕鑰文件

openssl genrsa -out server.key 2048java

這個祕鑰文件是通過Base64編碼後生成的,你可使用文本工具打開,有時候這樣的編碼文件又稱爲pem文件。android

建立基於當前祕鑰的證書請求文件

openssl req -new -key server.key -out server.csrgit

生成證書請求文件會要求你輸入一些相關信息,這些信息會同祕鑰一塊兒被加密存儲在.csr文件中。它將被用來向正規的CA機構去申請證書。它也是通過Base64編碼後的。算法

申請X509證書

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crtspring

咱們申請自簽名的X509證書,有效期1年,證書包含了公鑰和相關信息。因爲自簽名證書不是由公認的CA機構簽發,所以使用它來做爲服務端證書的時候,瀏覽器會提示告警信息。不過這不妨礙咱們在內部環境中使用。apache

建立PKCS#12文件

openssl pkcs12 -export -clcerts -in server.crt -inkey server.key -out server.p12後端

PKCS#12是祕鑰交換的標準證書。在加密通訊的過程當中,若是全部的信息都使用非對稱加密,性能和時間損耗都很是大。所以,根據SSL握手規則,通訊雙方首先利用非對稱加密算法協商出一個臨時通訊祕鑰,而後在本次會話中僅使用基於當前祕鑰對信息進行對稱加密。會話結束即丟棄,不保存不復用。p12文件中包含了以前生成的私鑰信息和申請的公鑰信息及全部相關數據。瀏覽器

利用JDK生成keystore證書

keytool -importkeystore -srckeystore server.p12 -destkeystore server.jks -srcstoretype pkcs12 -deststoretype jkstomcat

這樣生成的證書因爲使用的是同一個私鑰文件,所以.jks文件與.crt文件是同源的。在多語言支持的大系統中它們能夠相互認證,也便於統一管理。

請注意,牽涉到加密通訊的系統每每都比較複雜,證書鏈都必須統一保存。不多會使用各自的工具在不一樣的場景下獨立使用,所以即便是Java開發者也依然應該掌握如何利用openssl生成完整證書的流程。

2、開發基於SSLEngine的非阻塞服務端

服務端的開發與客戶端區別不大,下面說明初始化和握手流程。其餘部分的介紹能夠參考個人上一篇博客。

服務端初始化

服務端初始化的過程除了須要監聽指定端口和處理客戶端鏈接之外,主要是須要初始化SSLContext。SSLContext是整個SSL通訊的基礎,也能夠認爲是生成SSLEngine和SSLSession的工廠方法。具體通訊的加解密過程又後者完成。

/** * 初始化 SSL安全層 */
private SSLContext sslContext;
private void initSSL() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException { sslContext = SSLContext.getInstance("SSL"); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509"); KeyStore ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream("server.jks"), keystorepass); kmf.init(ks, keypass); tmf.init(ks); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new java.security.SecureRandom()); }

server.jkd對應以前生成的證書文件,路徑根據本身項目的實際路徑指定。keystorepass和keypass是生成證書時輸入的祕鑰。

SSL握手和SSLEngine初始化

正如前文介紹的同樣,SSL握手協議中規定了交換祕鑰和協商對稱加密的過程。所以,實際上在JDK的抽象中,SSL的握手過程本質上是對SSLEngine的初始化。所以與客戶端不一樣的地方在於,服務端須要在有客戶端鏈接進入後再進行SSLEngine的初始化,並保證每個新鏈接對應一個SSLEngine對象。當客戶端會話關閉後,釋放對應的SSLEngine。

/** * 服務端握手操做 */
private SSLEngine sslHandshake(SocketChannel socket) throws IOException { SSLEngine sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(false); SSLSession sslSession = sslEngine.getSession(); ByteBuffer remoteAppData = ByteBuffer.allocate(sslSession.getApplicationBufferSize()); ByteBuffer localNetData = ByteBuffer.allocate(sslSession.getPacketBufferSize()); ByteBuffer remoteNetData = ByteBuffer.allocate(sslSession.getPacketBufferSize()); sslEngine.beginHandshake(); SSLEngineResult.HandshakeStatus hsStatus = sslEngine.getHandshakeStatus(); SSLEngineResult result; // 循環判斷指導握手完成
    while (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED) { switch (hsStatus) { case NEED_WRAP: localNetData.clear(); result = sslEngine.wrap(ByteBuffer.allocate(0), localNetData); // 第一個參數設置空包,SSLEngine會將握手數據寫入網絡包
            hsStatus = result.getHandshakeStatus(); if (handleResult(result)) { localNetData.flip(); // 確保數據所有發送完成
                while (localNetData.hasRemaining()) { socket.write(localNetData); } } break; case NEED_UNWRAP: int len = socket.read(remoteNetData); // 讀取網絡數據
            if (len == -1) { break; } remoteNetData.flip(); remoteAppData.clear(); do { result = sslEngine.unwrap(remoteNetData, remoteAppData); // 與握手相關的數據SSLEngine會自行處理,不會輸出至第二個參數
                hsStatus = result.getHandshakeStatus(); } while (handleResult(result) && hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP); // 一次性沒有完成處理的數據經過壓縮的方式處理,等待下一次數據寫入
 remoteNetData.compact(); break; case NEED_TASK: // SSLEngine後臺任務
 Runnable runnable; while ((runnable = sslEngine.getDelegatedTask()) != null) { runnable.run(); } hsStatus = sslEngine.getHandshakeStatus(); break; default: break; } } return sslEngine; }

其它與客戶端共性的部分,再也不贅述。

3、HTTPS相關配置

配置tomcat

對於Java開發者而言,對Tomcat應該不陌生。下面的配置基於tomcat7。

conf/server.xml

<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" maxThreads="150" SSLEnabled="true" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="server.jks" keystorePass="password"
    />

配置springboot

利用springboot開發微服務應用的時候,能夠直接部署jar包。下面的配置基於springboot 2.x以上版本。

application.yml

server: 
port: 8081
ssl:
key-store: classpath:server.jks
enabled: true
key-store-password: password
key-store-type: JKS

證書路徑爲resources下或在啓動配置中自由指定。若是配置成功在啓動日誌上會打印出8081(https)的相關消息,若是你須要讓容器同時支持http和https也能夠利用@Configuration經過代碼加載配置,網上的資料很全,再也不贅述。

不過這樣配置其實並不能完成先後端分離的訪問請求,由於瀏覽器轉發的時候會默認對證書進行驗證。因爲咱們的證書不是經過公認的CA機構簽發,所以會被默認阻止。固然你也能夠經過設置讓瀏覽器放行,不過對於實際項目而言意義不大。

Android 6.0以上版本因爲默認使用TLS通訊,所以上面的配置能夠應對移動端的訪問限制。下面的配置是針對後端僅支持http,android端的配置:

<application /*其它配置*/ android:usesCleartextTraffic="true">

總結

以我我的對將來的展望,因爲微服務和高可用的部署會被應用到愈來愈多的場景中,各類語言間的相互調用會成爲常態。底層硬件和一些對性能要求較高的場景天然須要C/C++做爲支持,可是涉及業務層和互聯網方向的應用更多仍是會以Java和其餘高級語言爲主。相互驅動,互爲支持必不可少,而安全通訊也會更加被重視。固然在實際的項目中,爲了得到更加穩定的支撐,咱們可能會選擇使用框架。不過,可以深刻學習對開發者而言很是重要。我利用兩篇博客總結了在多語言支持下與安全通訊相關的主要知識體系,基本以應用爲主,少許結合了一些理論知識。但願可以爲你們的學習有所幫助。

 

相關博客:

開源項目SMSS發開指南(四)——SSL/TLS加密通訊詳解(上)

開源項目SMSS開發指南

原文出處:https://www.cnblogs.com/learnhow/p/12306755.html

相關文章
相關標籤/搜索