Netty SSL性能調優

TLS算法組合

在TLS中,5類算法組合在一塊兒,稱爲一個CipherSuite:java

  • 認證算法linux

  • 加密算法算法

  • 消息認證碼算法 簡稱MAC安全

  • 密鑰交換算法服務器

  • 密鑰衍生算法session

比較常見的算法組合是 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 和  TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 都是ECDHE 作密鑰交換,使用RSA作認證,SHA256作PRF算法。框架

一個使用AES128-CBC作加密算法,用HMAC作MAC。dom

一個使用AES128-GCM作加密算法,MAC因爲GCM做爲一種AEAD模式並不須要。ide

SSL協議的握手過程

第一步,愛麗絲給出協議版本號、一個客戶端生成的隨機數(Client random),以及客戶端支持的加密方法。性能

第二步,鮑勃確認雙方使用的加密方法,並給出數字證書、以及一個服務器生成的隨機數(Server random)。

第三步,愛麗絲確認數字證書(對證書信息進行md5或者hash後的編號==用證書機構的公鑰對加密的證書編號解密後的證書編號)有效,而後生成一個新的隨機數(Premaster secret),並使用數字證書中的公鑰(鮑勃的公鑰),加密這個隨機數,發給鮑勃。

第四步,鮑勃使用本身的私鑰,獲取愛麗絲髮來的隨機數(即Premaster secret)。

第五步,愛麗絲和鮑勃根據約定的加密方法,使用前面的三個隨機數,生成"對話密鑰"(session key),用來加密接下來的整個對話過程。

https要使客戶端與服務器端的通訊過程獲得安全保證,必須使用對稱加密算法而且每一個客戶端的算法都不同,須要一個協商過程,可是協商對稱加密算法的過程,須要使用非對稱加密算法來保證安全,直接使用非對稱加密的過程自己也不安全,會有中間人篡改公鑰的可能性,因此客戶端與服務器不直接使用公鑰,而是使用數字證書籤發機構頒發的證書來保證非對稱加密過程自己的安全。這樣經過這些機制協商出一個對稱加密算法,就此雙方使用該算法進行加密解密。從而解決了客戶端與服務器端之間的通訊安全問題。

Java 對SSL的支持

JDK7的client端只支持TLS1.0,服務端則支持TLS1.2。 

JDK8徹底支持TLS1.2。

JDK7不支持GCM算法。

JDK8支持GCM算法,但性能極差極差極差,按Netty的說法: 

  •  Java 8u60之前多版本,只能處理1 MB/s。

  •  Java 8u60 開始,10倍的性能提高,10-20 MB/s。

  •  但比起 OpenSSL的 ~200 MB/s,還差着一個量級。

Netty 對SSL的支持

Netty既支持JDK SSL,也支持Google的boringssl, 這是OpenSSL 的一個fork,更少的代碼,更多的功能。

依賴netty-tcnative-boringssl-static-linux-x86_64.jar便可,它裏面已包含了相關的so文件,不再用管Linux裏裝沒裝OpenSSL,OpenSSL啥版本了。

性能問題的出現

JDK7的JMeter HTTPS客戶端,鏈接JDK8的Netty服務端時,速度還能夠。

JDK8的JMeter HTTPS客戶端,則很是慢,很是慢,很是吃客戶端的CPU。

按套路,在JMeter端增長啓動參數 -Djavax.net.debug=ssl,handshake  debug 握手過程

(OpenSSL那邊這個參數加了沒用) 

*** ClientHello, TLSv1.2,能夠看到,Client端先發起協商,帶了一堆可選協議

Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256…]

*** ServerHello, TLSv1.2 而後服務端回選定一個

Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

還能夠看到,傳輸一樣的數據,不一樣客戶端/服務端組合下有不一樣的紀錄: 

Client: JDK7 JDK SSL + Server: JDK7/8 JDK SSL

**TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

WRITE: TLSv1 Application Data, length = 32 

WRITE: TLSv1 Application Data, length = 304

READ:  TLSv1 Application Data, length = 32

READ:  TLSv1 Application Data, length = 96 

READ:  TLSv1 Application Data, length = 32 

READ:  TLSv1 Application Data, length = 10336

Client: JDK8 JDK SSL + Server: JDK8 Open SSL

** TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 

Thread Group 1-1, WRITE: TLSv1.2 Application Data, length = 300 

Thread Group 1-1, READ: TLSv1.2 Application Data, length = 92

Thread Group 1-1, READ: TLSv1.2 Application Data, length = 10337

緣由分析

JMeter Https 用的是JDK8 SSL,很不幸的和服務端的OpenSSL協商出一個JDK8實現超慢的TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

對於服務端/客戶端都是基於Netty + boringssl的RPC框架,使用TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 仍然是好的,畢竟更安全。

但Https接口,若是不肯定對端的是什麼,JDK7 SSL or JDK8 SSL or OpenSSL,爲免協商出一個超慢的GCM算法,Server端須要經過配置,才決定要不要把GCM放進可選列表裏。

解決方法

平時是這樣寫的:

SslContext sslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())  .sslProvider( SslProvider. OPENSSL).build();

若是不要開GCM,那把ReferenceCountedOpenSslContext裏面的DEFAULT_CIPHERS抄出來,刪掉兩個GCM的。

List<String> ciphers = Lists.newArrayList(「ECDHE-RSA-AES128-SHA」, 「ECDHE-RSA-AES256-SHA」, 「AES128-SHA」, 「AES256-SHA」, 「DES-CBC3-SHA」);

SslContext sslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).sslProvider( SslProvider.OPENSSL).ciphers(ciphers).build();

總結

  • OpenSSL(boringssl)比JDK SSL 快10倍,10倍!!! 因此Netty下儘可能都要使用OpenSSL。

  • 在肯定兩端都使用OpenSSL時,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 仍然是好的,畢竟更安全,也是主流。

  • 對端若是是JDK8 SSL時,Server端要把GCM算法從可選列表裏拿掉。

相關文章
相關標籤/搜索