HTTPS 效能優化學習筆記

yangxikun.com發表於2017-06-05

最近在學習https效能優化,雖然網上已經有許多的關於https效能優化的文章了,但還是想寫下這篇文章,作為學習總結=^_^=,文中對於一些概念性或實現細節上的東西並不會展開,但會給出相應的引用,有些圖片也來自網上資源。

章節規劃:

  • 認識SSL/TLS
  • 演算法選擇
  • 會話恢復
  • OCSP stapling
  • TLS 緩衝區優化
  • TLS false start
  • 其他優化

認識SSL/TLS

SSL和TLS都是用於保障端到端之間連線的安全性。SSL最初是由Netscape開發的,後來為了使得該安全協議更加開放和自由,更名為TLS,並被標準化到RFC中,現在主流的是TLS 1.2版本。

從上圖,可以看出SSL/TLS是介於應用層和傳輸層之間,並且分為握手層(Handshake Layer)和記錄層(Record Layer)。

  • 握手層:端與端之間協商密碼套件、連線狀態。
  • 記錄層:對資料的封裝,資料交給傳輸層之前,會經過分片-壓縮-認證-加密

從TLS 1.2 RFC可以瞭解更多:https://www.ietf.org/rfc/rfc5246.txt

演算法選擇

TLS中可被配置的演算法分類:

  1. 數字簽名:RSA、DSA
  2. 流加密:RC4
  3. 分組加密:DES、AES
  4. 認證加密:GCM
  5. 公鑰加密:RSA
  6. 訊息認證碼:SHA
  7. 金鑰交換:Diffie–Hellman

密碼套件決定了會使用到的演算法,例如執行openssl ciphers -v 'ALL' | grep ECDHE-RSA-AES128-GCM-SHA256

ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(128) Mac=AEAD

表明該演算法是在TLS 1.2中支援的,金鑰交換採用ECDH(EC是指採用橢圓曲線的DH),數字簽名採用RSA,加密採用128位金鑰長度的AESGCM,訊息認證碼採用AEAD(AEAD是一種新的加密形式,把加密和訊息認證碼結合到一起,而不是某個演算法,例如使用AES並採用GCM模式加密,就能夠為資料提供保密性、完整性的保障)。

如何理解完整性?

A 將明文M加密後為MC,發給B,B解密,得到明文。 如果此時有中間人C,將MC替換為CMC(雖然C不知道A怎麼加密的,但這沒關係),B將CMC解密,得到明文(那麼B拿到的其實是錯誤的明文)。 所以需要引入訊息認證碼,B才能夠判斷收到的密文是否被篡改過。 這裡你可能會問:那如果C同時偽造訊息認證碼呢? 這個就得看MAC和加密是如何配合的了,詳情可以檢視認證加密中的Approaches to Authenticated Encryption章節。

在TLS握手和資料傳輸的不同階段會採用相應的演算法:

  • 服務端身份驗證:數字簽名(RSA、ECDSA)
  • 金鑰交換:RSA/金鑰交換演算法(ECDH)
  • 加密/解密:流加密(RC4)和分組加密(3DES/AES/AESGCM)
  • 生成訊息認證碼:SHA/AEAD

不知是否有人發現並沒有提到壓縮演算法,如果google下TLS壓縮優化相關的內容,會發現沒有,因為目前在TLS 1.2 RFC中,關於壓縮方法的結構定義為enum { null(0), (255) } CompressionMethod;,即只有null方法(不進行壓縮)。目前存在對TLS壓縮的攻擊:http://www.freebuf.com/articles/web/5636.html,可能是基於此原因,TLS壓縮目前只是個概念性的東西,沒有被真正應用起來。

如何選擇演算法——安全性

通常加密演算法的安全性依賴於金鑰的長度,且不同加密演算法,即使金鑰長度相同,但提供的安全性也可能是不同的,相關資料:key size。所以並沒有一個標準的歸一化方法去衡量所有的加密演算法,但是有來自世界上各個組織/機構對不同型別演算法安全性的評估,可以看下這個網站:https://www.keylength.com/

執行openssl ciphers -v 'ALL' | wc -l會發現有100+個密碼套件(不同openssl版本提供的密碼套件有點差異),然而,實際只會使用到其中一部分,因為openssl提供的不少演算法是不安全的,需要排除掉。

執行openssl ciphers -v 'HIGH MEDIUM !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !CAMELLIA !IDEA !SEED !RC4' | wc -l,發現只剩下50+個密碼套件。

篩選後剩下的密碼套件還是挺多的,一個個做效能測試的話,會GG的= =。其實可以根據需要支援的客戶端,再篩選出主流的密碼套件。網址:https://www.ssllabs.com/ssltest/clients.html,提供了絕大部分客戶端對TLS的支援情況,點選相應的User agent可以檢視到其支援的密碼套件,並且各套件的安全性也被標註出來了。

網址:https://www.ssllabs.com/ssltest/,可以用於測試伺服器的SSL配置情況,並會給出得分,如下圖google的得分為A:

如何選擇演算法——效能

以下效能測試都是選取主流的演算法進行。

數字簽名:ECDSA vs RSA

需要先分別生成採用ECDSA和RSA的簽名證書。

生成ECDSA自簽名的證書:

openssl ecparam -name prime256v1 -genkey -out ec_key.pem
openssl req -new -x509 -key ec_key.pem -out cert.pem -days 365

-param_enc引數使用預設的named_curve就可以了,如果使用explicit,會發現生成的證書nginx能配置成功,但客戶端連線時會出現handshake error。

生成RSA簽名的證書:

openssl req -newkey rsa:2048 -nodes -keyout rsa_key.pem -x509 -days 365 -out cert.pem

執行openssl speed rsa2048 ecdsap256測試下:

                  sign    verify    sign/s verify/s
rsa 2048 bits 0.000834s 0.000024s   1198.9  41031.9
                              sign    verify   sign/s  verify/s
256 bit ecdsa (nistp256)   0.0000s   0.0001s  21302.5   9728.5

可以看到簽名效能ECDSA > RSA,而驗證效能RSA > ECDSA。

測試環境:

  • 服務端:1臺虛擬機器CentOS 4核 openresty 2個worker
  • 客戶端:4臺虛擬機器CentOS 4/2/2/2核(手頭只有這些虛擬機器= =), 用shell指令碼模擬併發的ab -c 800 -n 800(併發的ab例項數=2*CPU_NUM),使用time命令獲取消耗的時間
  • 測試頁面562位元組,目標是測試數字簽名的效能,所以頁面小點,避免加密/解密、資料傳輸佔用太多時間

多臺客戶端如何同時啟動?ctrl+tab,命令+回車……

為什麼不用jmeter?我用了1Master3Slave的jmeter分散式壓測發現,jmeter對於在該場景(CPU bound)下的效能測試不行,服務端壓力上不去

在相同的請求量下,RSA簽名會使服務端CPU佔用更高,所以這次測試需要在兩種簽名的壓測下,服務端CPU都保持在90%以上(不然的話,對ECDSA就不公平了)。

為何openresty是2個worker?因為開4個的話,ECDSA的壓測沒法使openresty4個worker的CPU消耗達到90%

ECDHE-ECDSA-AES128-GCM-SHA256,服務端CPU佔比90%,結果:

客戶端(CPU核數標識) 4 2 2 2
第一次 11.988 17.334 9.161 7.748
第二次 12.524 13.750 12.129 7.582
第三次 11.836 17.991 9.195 10.023
第四次 11.617 7.081 9.168 8.919

ECDHE-RSA-AES128-GCM-SHA256,服務端CPU佔比100%,結果:

客戶端(CPU核數標識) 4 2 2 2
第一次 12.704 21.088 18.232 6.134
第二次 13.355 21.071 26.990 6.102
第三次 14.638 16.009 11.669 6.071
第四次 13.913 21.061 21.271 5.108

從表格中的資料可以看出ECDSA的效能要比RSA好點,這裡ECDSA的測試尚未壓榨完服務端呢。從openssl speed的結果也可以看出ECDSA的簽名效能是要遠超過RSA的,而且簽名是在服務端做的,所以面對海量的客戶端,服務端應該選擇使用ECDSA。

金鑰交換:RSA vs ECDHE

測試環境同上,但只使用了4/2核兩臺客戶端機器發請求。證書使用的是生成的RSA證書,ECDSA證書能用到的金鑰交換演算法只能是ECDHE。

AES256-GCM-SHA384,服務端CPU佔比100%,結果:

客戶端(CPU核數標識) 4 2
第一次 12.144 15.737
第二次 12.133 15.452
第三次 11.902 16.145
第四次 11.614 16.133

ECDHE-RSA-AES256-GCM-SHA384,服務端CPU佔比100%,結果:

客戶端(CPU核數標識) 4 2
第一次 11.950 16.213
第二次 12.488 16.666
第三次 12.167 16.378
第四次 13.784 16.484

從表格中的資料可以看出ECDHE與RSA的效能差不多。ECDHE比RSA要多了一次端到端的傳輸,還會用到RSA對DH引數進行簽名和驗證;而RSA金鑰交換則會使用到RSA的加密/解密,具體可看如下CloudFlare的兩張圖,圖片來自Keyless SSL: The Nitty Gritty Technical Details

ECDHE支援前向保密(Forward Secrecy),簡單理解:中間人可以儲存下來客戶端和服務端之間的所有通訊資料,如果使用RSA握手,那麼未來某一天,中間人如果獲取到了服務端的私鑰,就可以解密所有之前採集的通訊資料了;如果採用ECDHE握手的話,就可以避免這個問題。而且使用ECDHE握手的話,還有可能開啟TLS false start的特性(下文中會提到)。

RSA握手:

ECDHE握手:

所以金鑰交換演算法ECDHE會更好些。

對稱加密:AES256-GCM vs AES256 vs AES128-GCM vs 3DES

測試環境同上,但只使用了4核一臺客戶端機器發請求,ab引數為ab -n 2000 -c 10,ab例項4個,測試頁面153K。因為是要壓測對應用層資料的加密解密效能,所以連線數少,但每個連線的請求數多。

ECDHE-RSA-AES256-GCM-SHA384,服務端CPU佔比94%,結果:

客戶端(CPU核數標識) 4
第一次 17.972
第二次 18.863
第三次 18.761
第四次 19.345

ECDHE-RSA-AES256-SHA384,服務端CPU佔比98%,結果:

客戶端(CPU核數標識) 4
第一次 20.490
第二次 19.575
第三次 19.725
第四次 20.262

ECDHE-RSA-AES128-GCM-SHA256,服務端CPU佔比92%,結果:

客戶端(CPU核數標識) 4
第一次 17.886
第二次 18.449
第三次 17.897
第四次 18.371

DES-CBC3-SHA,服務端CPU佔比100%,結果(太慢了,就測了兩個=。=):

客戶端(CPU核數標識) 4
第一次 52.262
第二次 51.476

從表格中的資料可以看出AES128GCM > AES256GCM > AES256 > 3DES。

訊息認證碼:SHA256 vs SHA1 vs AEAD

測試環境同上。

AES256-SHA256,服務端CPU佔比100%,結果:

客戶端(CPU核數標識) 4
第一次 18.544
第二次 18.309
第三次 18.594
第四次 18.670

AES256-SHA,服務端CPU佔比98%,結果:

客戶端(CPU核數標識) 4
第一次 15.418
第二次 15.071
第三次 16.614
第四次 16.146

AES256-GCM-SHA384,服務端CPU佔比95%,結果:

客戶端(CPU核數標識) 4
第一次 14.443
第二次 15.669
第三次 15.880
第四次 15.960

從結果中可以看出AES256-GCM-SHA384 > AES256-SHA > AES256-SHA256。

會話恢復

Session Cache

客戶端希望恢復先前的session,或者複製一個存在的session,可以在ClientHello中帶上Session ID,如果服務端能夠在它的Session Cache中找到相應的Session ID的session-state(儲存協商好的密碼套件等資訊),並且願意使用該Session ID重建連線,那麼服務端會傳送一個帶有相同Session ID的ServerHello。

目前Nginx 只支援單機Session Cache,Openresty 支援分散式Session Cache,但處於實驗階段。

Session Ticket

Session Cache需要服務端快取Session相關的資訊,對服務端存在存取壓力,而且還有分散式Session Cache問題。 對於支援Session Ticket的客戶端,服務端可以通過某種機制將session-state加密後作為ticket發給客戶端。客戶端憑藉該ticket就可以恢復先前的會話了。

類似於HTTP中用Json Web TOken作為cookie-session的另一種選擇。

OCSP(線上證書狀態協議) stapling

當客戶端在握手環節接受到服務端的證書時,除了對證書進行簽名驗證,還需要知道證書是否被吊銷了,那麼需要向證書中指定的OCSP url傳送OCSP查詢請求。

對於同一份服務端證書,如果每個客戶端都自己去查詢一次證書狀態就浪費了。所以,OCSP stapling就是為了解決這一問題,由服務端查詢到證書狀態(通常會快取一段時間),並返回給客戶端(客戶端會在本地校驗這個證書狀態是否真實)。

在nginx的配置中,可以選擇性的配置是否對OCSP response做校驗,防止將非法的證書狀態傳送給客戶端。如果設定了校驗,ssl_trusted_certificate引數需要為包含所有中間證書+根證書的檔案。

如下圖是對nginx請求OCSP Server的抓包,可以看到發了個http的ocsp請求:

下圖是對nginx在傳送證書給客戶端時,帶上的證書狀態的抓包:

TLS緩衝區調優

nginx預設的ssl_buffer_size是16K(TLS Record Layer最大的分片),即一個TLS Record的大小,如果HTTP的資料是160K,那麼就會被拆分為10個TLS Record(每個TLS Record會被TCP層拆分為多個TCP包傳輸)傳送給客戶端。

如果TLS Record Size過大的話,拆分的TCP包也會較多,傳輸時,如果出現TCP丟包,整個TLS Record到達客戶端的時間就會加長,客戶端必須等待完整的TLS Record收到才能進行解密。

如果TLS Record Size小一些的話,TCP丟包影響的TLS Record佔比就會小很多,到達客戶端的TLS Record就會多些,客戶端乾等著的時間就相對少了。但是,TLS Record Head的負載就增加了,可能還會降低連線的吞吐量。

假設ssl_buffer_size設定為1460byte:

可以看下這篇文章關於:Nginx TLS 首位元組的優化

通常,在TCP慢啟動的過程中,TLS Record Size小點好,因為這個時候TCP連線的擁塞視窗cwnd較小,TCP連線吞吐量也小。而在TCP連線結束慢啟動之後,TLS Record Size就可以增大一些了,因為這個時候吞吐量上來了。所以更希望能夠動態的調整nginx中ssl_buffer_size的大小,目前官方nginx還不支援,不過cloudflare為nginx打了個patch,以支援動態的調整TLS Record Size:Optimizing TLS over TCP to reduce latency

TLS False Start

某一端在傳送 Change Cipher Spec、Finished 之後,可以立即傳送應用資料,無需等待另一端的 Change Cipher Spec、Finished 。這樣,應用資料的傳送實際上並未等到握手全部完成,從而節省出一個RTT時間。

完整握手時,Client Side False Start:

簡短握手時,Server Side False Start:

可以看下這篇文章:TLS False Start究竟是如何加速網站的 和 Transport Layer Security (TLS) False Start

RFC7918中並沒有對Server Side False Start進行定義(其之前的草案中就有提到,draft-bmoeller-tls-falsestart-00/01),文中的說明:However, if the server sends application data first, the abbreviated handshake adds two round-trip times, and this could be reduced to just one added round-trip time by doing a server-side False Start. There is little need for this in practice, so this document does not consider server-side False Starts further.

可能是在之前的HTTP 1場景下,對Server Side False Start的需求並不強烈,或者說實踐不多(當然其他應用層協議可能會有,例如websocket)。

Client Side False Start需要的條件:

  • 客戶端和服務端都需要支援NPN/ALPN(瀏覽器要求)
  • 需要採用支援前向保密的密碼套件,即使用ECDHE進行金鑰交換(RFC7918中有規定)

其他優化

  • TCP優化,畢竟SSL資料也是基於TCP進行傳輸的
  • 證書優化,採用ECDSA證書、伺服器傳送給客戶端的證書鏈包含所有中間證書
  • 硬體配置優化,例如使用SSL加速器

總結

本文是個人近段時間學習到的關於HTTPS效能優化的總結,推薦閱讀HTTPS權威指南High Performance Browser Networking以瞭解更多內容。

推薦的密碼套件列表:

openssl ciphers -v 'ECDHE+ECDSA ECDHE AESGCM AES HIGH MEDIUM !kDH !kECDH !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !CAMELLIA !IDEA !SEED !RC4 !3DES'

其他額外的密碼套件,比如需要支援IE6,可以放在密碼套件列表末尾。

自己寫了個go程式用於檢測密碼套件列表支援/不支援的客戶端:sslciphersuitescheck

相關文章