Nginx https性能優化

1、影響HTTPS速度的主要原因:

      衆周所知網站啓用https後,會加劇服務器的負擔。傳統的http使用TCP三次握手建立連接,而SSL和TLS在這個基礎上還需要9個握手包,所以這個負擔顯而易見。

1.1 密鑰交換算法

常見的密鑰交換算法有 RSAECDHEDHDHE 等算法。它們的特性如下:

RSA:算法實現簡單,誕生於 1977 年,歷史悠久,經過了長時間的破解測試,安全性高。缺點就是需要比較大的素數(目前常用的是 2048 位)來保證安全強度,很消耗 CPU 運算資源。RSA 是目前唯一一個既能用於密鑰交換又能用於證書籤名的算法。

DH:diffie-hellman 密鑰交換算法,誕生時間比較早(1977 年),但是 1999 年才公開。缺點是比較消耗 CPU 性能。

ECDHE:使用橢圓曲線(ECC)的 DH 算法,優點是能用較小的素數(256 位)實現 RSA 相同的安全等級。缺點是算法實現複雜,用於密鑰交換的歷史不長,沒有經過長時間的安全攻擊測試。

ECDH:不支持 PFS,安全性低,同時無法實現 false start。

DHE:不支持 ECC。非常消耗 CPU 資源 。

1.2 建議優先支持 RSA 和 ECDH_RSA 密鑰交換算法。

原因是:

1, ECDHE 支持 ECC 加速,計算速度更快。支持 PFS,更加安全。支持 false start,用戶訪問速度更快。

2, 目前還有至少 20% 以上的客戶端不支持 ECDHE,我們推薦使用 RSA 而不是 DH 或者 DHE,因爲 DH 系列算法非常消耗 CPU(相當於要做兩次 RSA 計算)。

1.3 更改其配置如下

(參照 Nginx Performance Tuning for SSL( http://dojo.techsamurais.com/?p=1384 ):

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!AESGCM;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

或者

ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-RC4-SHA:!ECDHE-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDHE-RSA-AES256-SHA:!RC4-SHA:HIGH:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!CBC:!EDH:!kEDH:!PSK:!SRP:!kECDH;

(禁用了RC4,sha1,MD5等算法)

2 輔助加速:

2.1. 啓用SPDY

SPDY 是 Google 推出的優化 HTTP 傳輸效率的協議( https://www.chromium.org/spdy ), 它基本上沿用了 HTTP 協議的語義, 但是通過使用幀控制實現了多個特性,顯著提升了 HTTP 協議的傳輸效率。
SPDY 最大的特性就是多路複用,能將多個 HTTP 請求在同一個連接上一起發出去,不像目前的 HTTP 協議一樣,只能串行地逐個發送請求。
可以 在編譯Nginx帶上參數 –with-http_spdy_module 支持SPDY協議,然後可在配置中啓用:

listen 443 ssl spdy;

檢測是否使用SPDY的網址( https://spdycheck.org/ )

2.2. HSTS

HSTS(HTTP Strict Transport Security)。服務端返回一個 HSTS 的 http header,瀏覽器獲取到 HSTS 頭部之後,在一段時間內,不管用戶輸入 www.baidu.com 還是 http://www.baidu.com ,都會默認將請求內部跳轉成 https://www.baidu.com;
將下述行添加到你的 HTTPS 配置的 server 塊中:

add_header Strict-Transport-Security "max-age=31536000";

2.3. Session cache

Session cache 的原理是使用 client hello 中的 session id 查詢服務端的 session cache, 如果服務端有對應的緩存,則直接使用已有的 session 信息提前完成握手,稱爲簡化握手。

Session cache 有兩個缺點:

1, 需要消耗服務端內存來存儲 session 內容。

2, 目前的開源軟件包括 nginx, apache 只支持單機多進程間共享緩存,不支持多機間分佈式緩存,對於百度或者其他大型互聯網公司而言,單機 session cache 幾乎沒有作用。

Session cache 也有一個非常大的優點:session id 是 TLS 協議的標準字段,市面上的瀏覽器全部都支持 session cache

ssl_session_cache shared:SSL:20m;
ssl_session_timeout 20m;

參照Nginx的官方文檔1MB內存大約可以存儲4000個session,按例配置20M大約可以存儲80000。根據需求合理設置。

-----------------

https存在一個缺點:每次新的TLS連續都需要握手,以便創建共享的加密密鑰,在TCP三次握手之上還需要兩個來回。

但是TLS有幾個特點可以抵消額外的握手:重用一個Session。有兩個標準會話重用機制:session IDs (RFC 5246) 和 session tickets (RFC 5077),使用其中一個技術,一個客戶端可以重用之前創建的會話,這個會話是之前和服務器進行握手成功的,這樣可以減少一次來回過程。基於SessionID的會話重用適合現代所有瀏覽器,FireFox和Chrome甚至還支持 session tickets。

Nginx之ssl_session_cache詳解:

引用一下Nginx官網對ssl_session_cache選項的說明

ssl_session_cache

翻譯過來:

Syntax:ssl_session_cache off | none | [builtin[:size]] [shared:name:size];

默認:

ssl_session_cache none;

Context:http, server

設置存儲session參數的緩存的類型和大小。緩存可以是以下任何一種類型:

off

嚴禁使用session緩存:nginx明確告訴客戶端session可能不會被重用。

none

session緩存的使用被禁止:nginx告訴客戶端session可能會被重用,但實際上並不會將session參數存儲在緩存中。

builtin

在OpenSSL中構建的緩存;僅由一個工作進程使用。緩存大小在session中指定。如果沒有給出大小,則等於20480個會話。使用內置高速緩存可能導致內存碎片。

shared

所有工作進程之間共享緩存。緩存大小以字節爲單位指定;一兆字節可以存儲大約4000個session。每個共享緩存都應該有一個任意名稱。具有相同名稱的緩存可以用於多個虛擬服務器。

兩種類型的緩存可以同時使用:配置案例:

ssl_session_cache builtin:1000 shared:SSL:10m;

但是隻使用shared緩存,而不使用built-in緩存性能應該會更高。

Nginx配置ssl_session_cache:

目前使用較多的配置是built-in和shared同時使用:

ssl_session_cache builtin:1000 shared:SSL:10m;

但是Nginx官方說只使用shared,性能會更高,配置方法爲:

ssl_session_cache shared:SSL:10m;

 

2.4 Ocsp stapling

        Ocsp 全稱在線證書狀態檢查協議 (rfc6960),用來向 CA 站點查詢證書狀態,比如是否撤銷。通常情況下,瀏覽器使用 OCSP 協議發起查詢請求,CA 返回證書狀態內容,然後瀏覽器接受證書是否可信的狀態。 將證書保存下來,瀏覽器請求時候直接通過自己的服務器發送回去,防止驗證服務器出問題,還能加快訪問速度。

查看OSCP驗證服務器地址:

openssl x509 -in 1_test.qupeiyin.net_bundle.crt  -text

在輸出的文字中找到 OCSP - URI: ,後面的 URL 就是 OSCP 的驗證服務器地址。
如圖:

請求OSCP證書

openssl ocsp -noverify \             
-issuer /certificate-path/trustchain.crt \             
-cert /certificate-path/trustchain.crt \             
-url http://ocsp1.wosign.com/ca6/server1

不出意外會收到如下的結果

trustchain.crt: good        
    This Update: Oct 18 17:59:10 2014 GMT        
    Next Update: Oct 18 23:59:10 2014 GMT

如果出現 403 錯誤,那就需要在 Header 請求頭加上域名參數 -header “HOST” “ocsp2.globalsign.com” ,沒問題後就可以直接保存下來證書文件,完整的命令如下:

openssl
ocsp -noverify  -issuer 1_root_bundle.crt
-cert 1_root_bundle.crt -url http://ocsp1.wosign.com/ca6/server1  -header "HOST"
"ocsp1.wosign.com" -text -respout ./stapling_file.ocsp

將保存下來的 stapling_file.ocsp 證書添加到 nginx 的配置中,如下,Nginx 中配置變成了這樣子:

ssl_stapling on;
ssl_stapling_verify on;
ssl_stapling_file /stapling_file.ocsp;
ssl_trusted_certificate /certificate-path/trustchain.crt;

這樣子重啓 Nginx 後就會生效,可以使用下面的命令測試生效結果:

echo QUIT | openssl s_client -connect blog.alphatr.com:443 -status 2> /dev/null | grep -A 17 'OCSP response:' | grep -B 17 'Next Update'

看到 OCSP Response Status: successful 這樣的字樣就是成功了。
ocsp證書有效期很短,大概不到一個月,所以過段時間要更新ocsp證書,不然還是會驗證失敗。需要用腳本定時更新OCSP證書。

3. 總結:(全部優化參數)

ssl on;
ssl_certificate /data/www/ssl/ssl.crt;
ssl_certificate_key /data/www/ssl/ssl.key;
ssl_trusted_certificate /data/www/ssl/trustchain.crt;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!AESGCM;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
resolver 223.5.5.5 223.6.6.6 valid=300s;
resolver_timeout 10s;

error_page 497 https://$host$request_uri;

參數詳解:

ssl on  開啓SSL
ssl_certificate  對應單張證書
ssl_certificate_key  對應私鑰
ssl_trusted_certificate  對應信任鏈(即Startcom SSL或者Positive SSL中需要附加到單張證書後面的那兩張證書,可以獨立出來)
ssl_protocols  支持的SSL協議標準(nginx默認參數爲:ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;)
ssl_ciphers  (nginx默認參數爲:ssl_ciphers HIGH:!aNULL:!MD5;)

ssl_prefer_server_ciphers On; #指定服務器密碼算法在優先於客戶端密碼算法時,使用SSLv3和TLS協議。

error_page 497 https://$host$request_uri;  通過497錯誤將http轉跳到https

 

4. 其他文章:這裏記錄一下,方便自己學習回顧

轉自https://zhuanlan.zhihu.com/p/25290538

這次分享的內容主要是來自於我們HTTPS上具體的工作實踐,主要內容分以下三部分:

  1. 計算性能的分析和優化;
  2. 無密鑰加載;
  3. 證書優化

4.1 爲什麼66%的網站不支持HTTPS?

談優化之前我們先看背景和趨勢,大家也很清楚HTTPS是大勢所趨,Google、Facebook和國內諸多大型互聯網公司也已經支持HTTPS,然而這裏有兩點大家需要注意:

  1. iOS10的ATS政策(App Transport Security)要求2017年1月1日後所有在iOS App Store上架的App都需要支持HTTPS,否則無法上架;
  2. Google的Chrome瀏覽器54版本已經將HTTP的域名輸入框前增加“!”的提示,如下圖,所有的HTTP站點都會有這個標識。同樣在2017年1月1日後開始,Chrome瀏覽器會在用戶點擊“!”的提示符後將該網站不安全的信息顯示出來,只要涉及到登錄和蒐集用戶數據的頁面,只要是HTTP的都會標註不安全,相信這也會加速HTTPS的推進。

 

HTTPS很安全,很古老也很成熟,爲什麼一直到今天我們還有66%的網站不支持HTTPS呢?原因有兩點:

1. 慢,HTTPS未經任何優化的情況下要比HTTP慢幾百毫秒以上,特別在移動端可能要慢500毫秒以上,關於HTTPS慢和如何優化已經是一個非常系統和複雜的話題,由於時間的關係,本次分享就不做介紹了。但有一點可以肯定的是,HTTPS的訪問速度在經過優化之後是不會比HTTP慢;

2. 貴,特別在計算性能和服務器成本方面。HTTPS爲什麼會增加服務器的成本?相信大家也都清楚HTTPS要額外計算,要頻繁地做加密和解密操作,幾乎每一個字節都需要做加解密,這就產生了服務器成本,但也有兩點大家可能並不清楚:

  • HTTPS有哪些主要的計算環節,是不是每個計算環節計算量都一樣?
  • 知道這些計算環節對CPU的影響,我們如何優化這些計算環節?

接下來我將介紹我們在這兩個問題上的探討。

4.2 HTTPS主要的計算環節

首先看HTTPS主要的計算環節,下圖是一個協議交互的簡要介紹圖,它的四種顏色分別代表4種不同的主要計算環節:

  1. 紅色環節是非對稱密鑰交換,通過客戶端和服務端不一致的信息協商出對稱的密鑰;
  2. 藍色環節是證書校驗,對證書的簽名進行校驗,確認網站的身份;
  3. 深綠色環節是對稱加解密,通過非對稱密鑰交換協商出對稱密鑰來進行加解密;
  4. 淺綠色環節是完整性校驗,不僅要加密還要防止內容被篡改,所以要進行自身的完整性校驗。

知道這些主要的計算環節之後,每一個計算環節對計算性能的影響分別是多少以及如何分析?這裏和大家分享我們計算性能的分析維度,主要分爲三部分:算法、協議和系統。

  1. 算法,所謂的算法其實是HTTPS所用到密碼學裏最基本的算法,包括對稱加密、非對稱密鑰交換、簽名算法、一致性校驗算法等,對應的分析手段也很簡單:openssl speed;
  2. 協議,因爲不同的協議版本和消息所對應使用的算法是不一樣的,雖然算法的性能很確定,但是和協議關聯起來它就不確定了。由於性能和協議相關,我們重點分析的是協議裏完全握手的階段,我們會對完全握手的每個消息和每個函數進行時間的分析;
  3. 系統,比如我們使用Nginx和OpenSSL,我們會對它進行壓力測試,然後在高併發壓力環境下對熱點事件進行分析和優化。

      接下來詳細介紹以上的分析維度,首先是對稱加密和一致性校驗算法的測試分析,這個手段和工具(openssl speed)很簡單,我就不多介紹了。這裏總結一下:下圖中柱狀圖越高表示性能越好,可以看出性能最好的是AES-128-GCM,性能最差的是AES-256-CBC,但即使它性能最差,它也只需要47微秒就能處理4000個字節,性能相比來說也還能接受。

      接下來我們看密鑰交換和簽名算法的測試。

      下表中Sign代表服務端進行的簽名,Verify指的是客戶端對簽名進行的校驗。我們關注一下紅色數字809,這代表使用RSA-2048位時,我們服務端1秒鐘只能處理809次,這已經是我們使用線上非常好的一款CPU進行測試的結果,事實上大部分機器1秒鐘只能處理三四百次,可以說性能非常差。

     接下來是Verify校驗,能看出來我們使用Ecdsa(nistp256)時一秒鐘能處理7000多次,同樣這也是我們使用線上比較好的服務器所測試的結果,由於Verify發生在客戶端,考慮到移動端手機的CPU是非常弱的,因此這裏一秒鐘可能只就能處理幾百次。

      接下來看協議耗時的分析,這裏用ECDHE_RSA非對稱密鑰交換握手進行舉例,大家注意紅色ServerKeyExchange部分,它用了2400微秒(2.4毫秒),這是一個非常恐怖的概念,如果我們HTTPS請求每一次都需要進行完全握手處理,這意味着我們CPU一個核每秒最多隻能處理400次多一點。

      最後我們看熱點事件的分析,它也比較簡單,我們對系統進行壓力測試,用perf record對事件進行記錄,然後使用flame graph將它們可視化出來,最後看到一些相關數據和結果。

總結以上計算性能分析:

  • HTTPS完全握手的性能不到普通HTTP性能的10%,如果說HTTP的性能是QPS 1萬,HTTPS可能只有幾百;
  • 爲什麼會這麼低呢?主要是RSA算法,它對性能的影響佔了75%左右;
  • ECC橢圓曲線如果使用最常用的ECDHE算法,這部分約佔整體計算量的7%;
  • 對稱加解密和MAC計算,它們對性能影響比較小,是微秒級別的。

4.3 優化

有了這些分析結論,如何優化呢?我們總結了三個步驟:

  1. 首先第一步也是最簡單的一個優化策略,就是減少完全握手的發生,因爲完全握手它非常消耗時間;
  2. 對於不能減少的完全握手,對於必須要發生的完全握手,對於需要直接消耗CPU進行的握手,我們使用代理計算;
  3. 對稱加密的優化;

 

4.3.1 簡化握手的原理以及實現

我們首先來看完全握手和簡化握手,這是TLS層的概念,我簡單說下簡化握手的兩個好處:

  1. 首先簡化握手相比完全握手要少一個RTT(網絡交互),從完全握手大家可以看出來,它需要兩個握手交互才能進行第三步應用層的傳輸,而簡化握手只需要一個RTT就能進行應用層的數據傳輸;
  2. 完全握手有ServerKeyExchange的消息(紅色框部分),這個消息之前提過需要2.4毫秒,另外完全握手有Certificate證書的消息,而簡化握手並不需要。這也就是簡化握手第二個好處,它減少了計算量,它不需要CPU消耗太多時間。

 

      既然簡化握手這麼好,我們如何實現?首先看協議層如何支持。TLS協議層有兩個策略可以實現:

  • 第一個是Session ID,Session ID由服務器生成並返回給客戶端,客戶端再次發起SSL握手時會攜帶上Session ID,服務端拿到後會從自己的內存查找,如果找到便意味着客戶端之前已經發生過完全握手,是可以信任的,然後可以直接進行簡化握手。
  • 第二個策略是Session Ticket,同樣它也是客戶端發起握手時會攜帶上的擴展,服務器拿到Session Ticket後會對它進行解密,如果解密成功了就意味着它是值得信任的,從而可以進行簡化握手,直接傳輸應用層數據。

工程實現上會有什麼問題呢?現在最常用的Nginx+OpenSSL有兩個侷限:

  1. Nginx只支持單機多進程間共享的Session Cache,假如我們所有接入用的是一臺服務器、一臺Nginx的話,那ID生成和查找都在一起,肯定是可以命中的,但是我們大部分特別是流量比較大的接入環境都是多臺機器接入。比如我們同一個TGW或者說LVS下面有多臺Nginx,那麼第一臺Nginx產生的ID返回給用戶,用戶可能隔了一個小時候之後再發起SSL握手,它攜帶上的Session ID肯定會隨機地落到某一臺Nginx上面(比如落在第三臺Nginx上),這樣肯定無法查找到之前的Session ID,無法進行簡化握手,這是第一個侷限,即命中率會比較低;
  2. OpenSSL提供了一個Session Cache的callback可以回調,但是這個回調函數是同步的,而Nginx是完全異步事件驅動的框架,如果Nginx調用這個callback進行網絡查找,假如這個網絡查找需要1毫秒,這意味着整體性能不會超過一千次。

我們如何進行改進?我們看第一個問題(Session Cache)的兩個改進方案:

1. IP Hash

    這是最簡單的根據IP做Hash的負載均衡策略,相信大家對此都很清楚,這方案的好處是可以保證相同的IP用戶永遠都在同一臺Nginx上面,Session Cache的命中率會提升,但是它有兩個缺點:

  • 容易導致熱點,我們有很多Net網關出口IP用戶的訪問量非常大,也就是說有一些IP請求非常大導致某一臺機器負載不均衡;
  • 用戶IP可能會經常變化,特別在移動端上,在Wi-Fi和4G環境下切換導致的IP變化同樣會使Session Cache的命中率降低。

2. 分佈式緩存(分佈式Session Cache)

    這是更優的方案,假如用戶開始發起握手,我們第一臺Nginx生成ID會寫入到一個全局的比如redis緩存裏,然後返回給用戶。用戶下一次發起握手的時候,假如他落到第三臺Nginx上面,由於我們都是全局的Session Cache查找,這命中率一下就提升上來了。我們實現了這個方案,但暫時還沒有開源,在這裏可以給大家推薦兩個開源方案,大家有興趣可以瞭解一下。

  • OpenResty,它提供了SSL Cache全局查找的指令;
  • BoringSSL,這是Google fork OpenSSL的版本,它也在SSL層面上實現了異步的Session Cache查找。

3 Session Ticket

      接下來我們看Session Ticket,由於Session Cache有個缺點是必須在服務端做緩存,會浪費很大內存,而Session Ticket有個好處是它不需要服務端做緩存,但同樣它也有個缺點:默認情況下比如三臺Nginx各自的Session Ticket加解密密鑰是不同(這裏的密鑰是指Session Ticket的對稱加解密的密鑰而不是指證書對應的私鑰)

      舉個例子,比如第一臺Nginx的Session Ticket用密鑰加密返回給用戶,用戶下一次再訪問落到第三臺機器,你用第一臺機器加密產生的密鑰用第三臺的密鑰去解密肯定會失敗。這個問題很好解決,我們將所有的Nginx機器配置成同一個加解密的密鑰就可以了,這樣也能實現簡化握手

4 Self Session Ticket

      我們看一下第三個方案:Self Session Ticket。Session ID和Session Cache都有一個共同點:Session基於內存,如果我們的App、瀏覽器或操作系統如果第一次啓動或重啓(或者瀏覽器的Tab關閉後又打開)都有可能導致Session ID和Session Ticket丟失,出於安全角度考慮,這情況下就必須要發起完全握手,怎麼解決呢?

如果這個App是我們完全自主、獨立自主開發,我們可以實現Self Session Ticket,我們將Ticket存儲在硬盤裏面,而不是在內存裏。這顯然會帶來一定的安全風險,但是我們會做一些限制:

  1. Ticket存儲在App的私有路徑裏,對非Root的手機是很難讀取到私有路徑的數據;
  2. 我們可以選擇對一些安全係數要求不是很高的業務開啓這個功能;
  3. 這個密鑰開關我們做到可以隨時控制。

綜上,即使這方案出現最危險的情況,其實是和HTTP方案是一樣的,它不會比HTTP方案安全性要差。

 

4.4 異步代理計算的原理和實現

      剛纔提到的都是關於如何減少完全握手,提升簡化握手,但對很多的請求,比如說瀏覽器第一次啓動必須要經過完全握手,而且不是我們能夠自主控制的。對於這部分的內容,完全握手比例佔了至少30%以上,也就是說我們還有30%以上的請求必須要觸發CPU進行大量計算,對這部分怎麼解決呢?

我們的方案是異步代理計算,主要是分三個步驟:

  1. 算法分離,把最消耗CPU資源的算法剝離出來,不讓它消耗本機的CPU資源;
  2. 代理計算,既然不消耗本機的CPU資源,我們可以使用硬件加速卡或者空閒的CPU資源來完成計算;
  3. 異步執行,我把算法分離出來交給計算集羣去計算的時候,這個過程是異步的,我不需要同步等待計算結果返回,這樣對我們性能提升也是非常有幫助的。

4.4.1 算法分離

要分離哪些算法?最主要是密鑰交換算法(非對稱密鑰交換算法),密鑰交換算法最常用的是三類:

  1. RSA
  2. ECDHE_RSA
  3. DHE_RSA

由於DHE_RSA是非常消耗性能的,這個方法也不安全,所以我們線上並沒有採用。目前使用最多的是ECDHE_RSA,考慮到兼容性的問題時使用了RSA。

RSA和ECDHE_RSA爲什麼會消耗CPU資源?RSA主要是對客戶端發回來的pre_master_secret進行解密,它消耗CPU資源的過程是私鑰解密的計算;而ECDHE_RSA則有兩個步驟:

  1. 生成ECC橢圓曲線的公鑰和幾個重要的參數;
  2. 對這幾個參數進行簽名,客戶端要確保參數是我服務端發過來的,就是通過RSA的簽名來保證。

RSA簽名爲什麼消耗CPU呢?RSA簽名同樣有兩個步驟:

  1. 首先它通過SHA1進行Hash計算;
  2. 對Hash結果進行私鑰加密,也就是最終消耗CPU的過程是私鑰解密和私鑰加密的計算。這兩個計算爲什麼消耗CPU?看下圖公式,如果e或者d這個指數是一個接近2的2048次方的天文數字,那就非常消耗CPU。這也就是RSA算法爲什麼消耗CPU的最直接的數學解釋。

我們再看一下協議層面我們該如何實現分離。同樣以ECDHE_RSA爲例,由於使用了ECC參數和RSA簽名,之前提到的ServerKeyExchange這個消息用了2.4毫秒,我們需要對這個消息進行分離,將一步操作拆成多個步驟。

我們再看一下RSA密鑰交換的分離,RSA非對稱密鑰交換算法不需要ServerKeyExchange的消息,但需要對Client發過來的ClientKeyExchange進行解密的操作,也就是最消耗算法的過程是RSA解密的地方,我們需要對這個步驟進行分離。

4.4.2 異步代理計算——架構

     以上是算法層面包括協議層面進行的分離。接下來解釋工程實現上的架構,下圖左部分是最常用最簡單的配置,比如我們配置好Nginx+OpenSSL,然後把證書和私鑰放上去,把443端口打開,就能用上HTTPS了,但這裏消耗的都是本機的CPU資源,這裏面就會性能很差。

      我們看異步硬件加速卡代理計算的模型,用戶發起HTTPS握手,涉及到私鑰計算(比如ServerKeyExchange和對ClientKeyExchange解密)的時候,我們會把ECC的參數剝離出來發送給我們的計算集羣,發送出去同時立馬異步返回,又可以接受其他用戶的請求,不需要等待。

      計算集羣計算完了以後,我們會把計算結果返回給比如Nginx,Nginx又觸發最初的用戶場景,從而完成HTTPS的握手,這是一個大概的交互流程。

      這裏面需要注意的是,我們不僅僅可以使用SSL硬件加速卡,還可以使用線上的空閒CPU資源,比如CPU比較空閒的存儲集羣,如果硬件加速卡出現故障,我們可以直接把卡拔掉,可以用硬件加速卡集羣上的CPU資源來做計算。我們對算法的解耦是非常純粹的,和協議、證書沒有任何關係,我們只做RSA的計算,不管哪一邊進行升級,只要RSA算法還是安全的,我們的協議就十分穩定,我們的維護成本也非常低。

4.4.3 異步代理計算——工程實現

      我們看一下工程實現,像Nginx需要事件框架進行修改來實現OpenSSL Nginx計算集羣交互,通過模塊是無法實現的,儘管Nginx模塊機制非常強大豐富,但是它在HTTP頭部數據解析完成之後才能介入處理,但SSL握手只有進行SSL握手完之後才能進行應用層的數據傳輸,也就是說在進行SSL握手的時候,它沒有任何HTTP的數據,這時候模塊是沒辦法介入處理的,必須對事件框架進行修改。

      關於OpenSSL,需要對OpenSSL的協議棧進行修改,主要涉及到s3_srvr.c這個文件,這個文件主要是實現OpenSSL握手的協議棧,這裏介紹一個開源方案:OpenSSL1.1.0,它已經支持了異步事件,同樣還是需要修改Nginx才能實現異步。性能也很強大,純ECDHE_RSA性能相比本機能夠提升3.5倍,而且解耦做得非常好。

4.5 其他優化

 4.5.1 ECC橢圓曲線的優化

      剛纔提到的一系列都是針對完全握手、簡化握手的介紹。接下來看對ECC橢圓曲線的優化,這裏說的優化不是針對算法本身進行的優化,而是使用OpenSSL的官方版本過程中需要注意的地方。

      儘量使用NIST P-256這條曲線,這條曲線是Intel幾個工程師在2013年進行的優化,性能提升了4倍。通常來講密鑰越大,性能肯定越差,比如RSA-2048肯定比1024要慢4倍左右,P-256曲線看上去應該比P-224要慢,但是實際上P-256曲線比P-224性能高了4倍,正是因爲經過了一系列的優化。

      另外需要注意的是OpenSSL的版本,這個優化特性只是在1.0.1L之後才加入的,下圖表格中OpenSSL的1.0.1e版本ecdh(nistp256)的性能只有2548,而OpenSSL的1.1.0b版本ecdh(nistp256)性能能達到10271,提高了4倍。

4.5.2 對稱加密算法的優化

我們再看一下對稱加密算法的優化,對稱加密主要分兩塊:

  • 塊式對稱加密算法,根據剛纔OpenSpeed跑的結果也能發現AES-GCM性能最高,建議大家使用。AES-NI同樣是Intel CPU提供的硬件加速指令,這個指令相比不開啓的CPU性能要提升5倍左右,現在市面上2010年之後的Intel CPU都是支持的,但需要注意如果要直接使用AES對稱加解密,一定要使用OpenSSL封裝的EVP_EncryptInit_ex函數,而不是用最底層的AES_encrypt(儘管它很好記也很好用),默認的AES_encrypt函數是不會用硬件加速指令的,因此性能會很差;

 

  • 流式對稱加密算法。Chacha20-Poly1305是由Google專門針對移動端CPU優化的流式對稱加密算法,它的性能相比普通算法要提高3倍。但Chacha20算法只適用於稍微低端、且不支持AES-NI指令的手機才能提高3倍,舉個例子,例如iPhone支持AES-NI,但它的性能還是會比AES-GCM要差的。

RC4、SSL3.0已經不安全了,也徹底退出了歷史舞臺,但我們還有很多客戶端只支持SSL3.0,比如IE6,比如WindowsXP的一些客戶端和瀏覽器,我們的業務方仍需要支持這些業務,因此我們就必須支持SSL3.0,那如何支持呢?

 

首先SSL3.0爲什麼不安全?主要是兩點:

  1. SSL3.0存在AES-CBC的緩存Poodle漏洞;
  2. RC4存在Bios偏移的漏洞;

這兩個漏洞哪個更嚴重?Poodle漏洞會更嚴重一些,於是開啓SSL3.0的話我們只支持RC4,這是第一點安全性的考慮,其次RC4的算法性能要比AES-CBC要高,比如業務方一定要支持WindowsXP和IE6的話,優先使用RC4。

4.5.3 無密鑰加載

      我們再看無密鑰加載,無密鑰加載的背景是這樣的,HTTPS證書和私鑰,私鑰是HTTPS安全的根本,如果私鑰被泄露了,那麼意味着你的HTTPS是沒有安全性可言,如果泄露了,只能撤銷證書,重新生成私鑰。

      而且我們大部分使用HTTPS的場景都是將私鑰和證書同機部署在比如Nginx上面,然後私鑰保存在硬盤,這其實有安全風險的。比如說我們有很多兼容的大客戶,他使用了證書和私鑰,但是他的業務可能分散在幾個雲,或者幾個CDN上,他就會擔心私鑰如果泄露了怎麼辦?

     我們因此設計了一個無密鑰加載的方案,我們先看普通的HTTPS流程,用戶發起HTTPS,到達騰訊雲,到達STGW,我們直接調用私鑰完成計算,完成HTTPS的握手,然後卸載,然後將HTTPS傳遞給業務。這是最普通的流程,會有一定的安全風險。

      再看一下無密鑰加載流程,什麼是無密鑰呢?比如騰訊雲,我們接收的服務器不需要部署私鑰,我們私鑰是完全放在客戶的物理服務器上面,爲了保證私鑰的安全,客戶甚至可以把這個物理服務器放在家裏。

      正常的接收流程是這樣的,我們用戶到達HTTPS握手,到達STGW,然後涉及到私鑰計算的時候,我們會把請求以及和私鑰相關的參數,封裝成一個異步請求發給騰訊雲客戶的物理服務器,然後物理服務器會調用私鑰進行相關計算,再把計算結果返回給騰訊雲服務器,完成HTTPS卸載。

      整個交互流程中,STGW或者說騰訊雲和私鑰沒有任何的接觸,私鑰是客戶自己才擁有。這就是我們無密鑰加載的一個簡單的介紹。當然具體的流程或者實現,其實跟我剛纔提到的異步代理計算有點類似,包括協議方面。

 

4.5.4 證書優化

      接下來看證書的優化。如果是個人用戶,向大家推薦Let's Encrypt,它是免費開源的CA證書頒發機構,它的優點就是免費開源,開源最大的好處就是可以對協議對整個交互流程都很清楚,支持Debug。它還有一個優點是能支持自動部署,它提供幾個工具運行幾分鐘就可以把證書申請下來。

      但它也有缺點,第一,它只是低級別即域名級別的證書申請,它只對域名的有效性進行校驗,你能擁有這個域名就可以給你頒發證書,這就有安全風險了,假如我對域名進行劫持,我就可以冒名申請別人的證書。

      另外兼容性也會比較差,像PC端Chrome訪問沒有任何問題,但我們用Android7.0去訪問就有可能出現問題,因爲CA支持和更新並不及時,因此就有兼容性的風險,所以推薦個人用戶使用。

      對於企業用戶,建議大家使用EV和OV級別的,因爲它會需要你證書的機構、你的地址、法人信息、營業執照、在工商局的認證等信息,才確保只有你才能申請這個證書。

      這是雲的優勢,申請很簡單,一鍵式申請,不需要你自己生成私鑰,也不需要生成證書的申請文件。這是騰訊雲證書簡單的說明。我們還有一個動作,在跟最大的證書廠商進行合作接觸,我們會實現自己更低成本的自主品牌證書的頒發,這樣的話證書會更加便宜,下圖是騰訊雲上面的介紹。

      我們看一下證書籤名的選擇,我們最後使用了RSA,還使用ECDSA,這兩個是最主流最常用的簽名類型。

      RSA的好處是兼容性好、歷史悠久,所有的客戶端都支持,因爲它算法已經存在40年了,缺點就是之前提到的代理計算,RSA需要私鑰進行計算,服務端需要加密解密性能會比較差。

      ECDSA,優點就是服務端的性能好一點,P-256只需要256位的輸出域就能實現和2048位長度同樣的安全性,它的安全性更高,服務端計算性能要好,但是客戶端的性能要差。使用ECDSA P-256客戶端的計算量要比RSA要大,RSA的公鑰的數字非常小,只有65537,而ECDSA的客戶端公鑰長度很大,計算性要差很多,特別是手機計算性能要弱。

      ECDSA的缺點就是兼容性差,比如說WindowsXP不支持。具體可以看下圖的系統支持情況,這裏面其實由於RSA和ECDSA這兩個完全不同類型的證書,服務端其實可以同時支持,當然成本可能會稍微增加一些。目前使用ECDSA的還是多一些的。

      最後看一下SHA1和SHA256,SHA1不安全,像Google和Microsoft已經宣佈放棄SHA1,如果你使用SHA1證書就訪問不了。

      SHA2兼容性比較差,比如WindowsXP不支持SHA2證書,但對於需要支持WindowsXP的業務來說如何兼容呢?我們觀察到一個特性:不支持SNI的客戶端也不支持SHA2。

      SNI是TLS的一個特性和擴展,發起client hello的時候,它會攜帶上一個域名的信息,在握手的時候告訴服務端要訪問哪一個域名(Server Name Indicator),雖然從協議或者從原理的角度來講沒有任何的必然的關係,但這個現象給我們一個啓發:我們通過服務端配置能夠解決這個兼容性的問題。

      同樣以Nginx和OpenSSL舉例,假如說客戶端1不支持SNI,只支持SHA1,客戶端2是新的系統支持SNI。我們配置配兩張證書,第一張證書我們同樣都是接403端口和兩個不同的Server,證書1放在前面,證書2支持SHA2放在後面。

      當客戶端1發起請求的時候,由於它不支持SNI,所以默認會返回第一張證書(第一張證書是支持SHA1的),這樣不支持SNI也不支持SHA2的客戶端1兼容性沒有問題。

      客戶端2發起請求的時候,攜帶了SNI,由於有SNI和域名信息,服務端會挑選有域名的信息SHA2返回給客戶端,實現SHA2的兼容。

4.6HTTPS發展

最後是我對於HTTPS發展的簡單的概括和理解:

  1. 更廣。它會越來越流行,以後會成爲互聯網的標配。HTTP2作爲下一代的協議,主流實現像Chrome\FireFox都是強制使用HTTPS的。ATS也強制使用HTTPS,Chrome2017年也會將HTTP標識爲不安全;
  2. 更快。這裏涉及到訪問速度的優化,TLS1.3是革命性飛躍式的TLS協議,可以認爲是TLS2.0,它相比1.2有非常大的變化,最大的變化有完全握手,它只需要一個RTT,而簡化握手不需要RTT,也就是應用層數據不需要握手直接可以完成數據的傳輸,另外QUIC解決HTTPS協議的性能問題,TLS協議以後也會越來越快,大家不用擔心訪問速度的問題;
  3. 更強。密鑰長度越來越大,加密強度會越來越強,隨着客戶端和硬件的發展,CPU加解密的性能也會越來越強;
  4. 更開放。之前提到類似Let's Encrypt的免費開源方案,以後也會成爲越來越流行的方案。

以上是HTTPS的性能優化,謝謝大家!

參考:

https://zhuanlan.zhihu.com/p/25290538

https://www.ioperat.com/news/operation/43.html

https://segmentfault.com/a/1190000017270510

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章