標籤:安全 res 技術分享 部分 rtt 應用程式層協議 cpu lis 網域名稱解析
通過分析TLS握手過程的細節我們會發現HTTPS比HTTP會增加多個RTT網路傳輸時間,既增加了服務端開銷,又拖慢了用戶端回應時間。因此,效能最佳化是必不可少的工作。很多文章都集中在服務端的效能最佳化上,但對於電商行業而言,大部分的使用者流量源於App,因此用戶端的效能最佳化配合服務端才能使收益最大化。
1. HTTPS帶來的負擔
凡事都有兩面性。
1.1 增加的傳輸延時
使用HTTPS傳輸增加的開銷不僅僅是兩次TLS握手的過程。最佳化效能首先要知己知彼。瞭解效能損耗在哪裡,才能有針對性的進行部署。
對於使用者來說,使用HTTP請求,首次請求時只要和服務端TCP三向交握建立串連,便可以開始應用資料轉送了。
而對於HTTPS而言,事情就不那麼簡單了。
1. 使用者習慣於使用HTTP請求你的網站。要保護使用者的安全,首先要讓使用者強制302/301到HTTPS。這次跳轉至少增加1個RTT的延時;
2. 302跳轉後要再次TCP建連,增加1個RTT的延時;
3. 開始兩階段TLS握手,細節如所示,增加至少兩個RTT的延時。
- Client Hello: 用戶端開始新的握手,並將自身支援的功能提供給服務端;
- Server Hello:服務端選擇串連參數;
- Certificate*:服務端發送憑證鏈結;
- ServerKeyExchange*:服務端發送公開金鑰(public key)等產生主要金鑰(premaster secrect)的額外資訊給用戶端;
- ServerHelloDone:服務端通知完成協商過程;
- ClientKeyExchange:用戶端發送加密後的主要金鑰給服務端
- [ChangeChiperSpec]:用戶端如果要切換加密方式通知服務端
- Finished:用戶端完成
- [ChangeChiperSpec]:服務端如果要切換加密方式通知用戶端
- Finished:服務端完成
4. 另外用戶端如果第一次擷取服務端的憑證鏈結資訊,還需要通過Oscp來驗證認證的吊銷狀態,又需要至少1個RTT延時。
5. 最終,開始應用程式層資料的傳輸。
1.2 服務端額外開銷
TLS握手過程中金鑰交換和加密對CPU都會產生額外的計算開銷。選擇不同的演算法(身分識別驗證演算法、金鑰交換演算法、密碼編譯演算法)開銷不同。比如,2048位RSA作為金鑰交換演算法對CPU壓力就會很大,而ECDHE_RSA(橢圓曲線金鑰交換)開銷就小的多,RSA可以仍保留用於身分識別驗證。
當然,不管選用多最佳化的演算法,開銷是避免不了的,如所示。
2. 服務端效能最佳化
服務端效能最佳化,主要體現在Web伺服器配置的最佳化,我們以Ngnix 1.11.0版本為例。當然你也可以選擇Apache、H2O等。
2.1 HSTS的合理使用
HSTS(HTTP Strict Transport Security, HTTP嚴格傳輸安全性通訊協定)表明網站已經實現了TLS,要求瀏覽器對使用者明文訪問的Url重寫成HTTPS,避免了始終強制302重新導向的延時開銷。
HSTS的實現原理是:當瀏覽器第一次HTTP請求伺服器時,返回的回應標頭中增加Strict-Transport-Security,告訴瀏覽器在指定的時間內,這個網站必須通過HTTPS協議來訪問。也就是對於這個網站的HTTP地址,瀏覽器需要現在本地替換為HTTPS之後再發送請求。
其配置如下所示。max-age表明HSTS在瀏覽器中的緩衝時間,includeSubdomainscam參數指定應該在所有子域上啟用HSTS,preload參數表示預先載入,稍後會具體解釋。
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
在CanIUse上我們可以查詢HSTS協議的瀏覽器支援度:
在使用HSTS的過程中仍有一些值得注意的問題:
1. HSTS將全部的認證錯誤視為致命的。因此,一旦主域使用HSTS,瀏覽器將放棄對網域名稱所有無效認證網站的串連。
2. 首次訪問仍然使用HTTP,然後才能啟用HSTS。無法保障首次訪問的安全性如何解決?可以通過preloading預先載入的方式,與瀏覽器廠商約定好一份支援HSTS的網站清單來緩解。目前Google已經提供了線上註冊服務https://hstspreload.appspot.com/
3. 如何撤銷HSTS?通過Strict-Transport-Security: max-age=0將緩衝設定為0可以撤銷HSTS。但是只有當瀏覽器再次訪問網站並且得到響應更新配置時才會生效。
2.2 會話恢複的合理使用
會話恢複機制是指在一次完整協商的串連斷開時,用戶端和服務端會將會話的安全參數儲存一段時間。後續的串連,雙方使用簡單握手恢複之前協商的會話。大大減少了TLS握手的開銷。
會話恢複的方案可以分為兩種:會話ID(Session ID)和會話票證(Session Ticket)。會話ID通過服務端為會話指定唯一的標識,並緩衝工作階段狀態。在第一次完整協商的過程中,ServerHello訊息中將會話ID發回用戶端。希望恢複會話的用戶端在下一次握手中將會話ID放入ClientHello,服務端認可後接著使用之前協商的主要金鑰進行加密。而會話票證將所有工作階段狀態保持在用戶端(類似於HTTP Cookie)。
1. 配置會話票證較為簡單:
ssl_session_tickets on;ssl_session_ticket_key /usr/local/nginx/ssl_cert/session_ticket.key;
生產key的命令通過openssl產生:
openssl rand –out session_ticket.key 48
注意叢集情況下key值保持一致。另外注意使用會話票證前需要開啟支援前向性加密支援的密鑰套件。
2. 配置會話ID需要注意,工作階段狀態是儲存在伺服器上的,叢集狀態下如何保證會話ID的命中率?最簡單的方式是負載的輪詢策略使用IP_HASH,保證同一用戶端總是被分發到叢集中的相同節點,但這樣未免不夠靈活。因此需要採用分布式緩衝的方式,將工作階段狀態儲存在叢集共用的redis中。
如何操作Nginx中的TLS會話資訊,可以參考openresty中的ssl_session_fetch_by_lua_block 模組。具體見https://github.com/openresty/lua-nginx-module#ssl_session_store_by_lua_file。
2.3 Ocsp stapling的合理使用
OCSP(Online Certificate Status Protocol, 線上憑證狀態通訊協定)用於查詢認證的吊銷資訊。OCSP即時查詢會增加用戶端的效能開銷。因此,可以考慮通過OCSP stapling的方案來解決:OCSP stapling是一種允許在TLS握手中包含吊銷資訊的協議功能,啟用OCSP stapling後,服務端可以代替用戶端完成憑證撤銷狀態的檢測,並將全部資訊在握手過程中返回給用戶端。增加的握手資訊大小在1KB以內,但省去了使用者代理程式獨立驗證吊銷狀態的時間。
啟用OCSP stapling的方式有很多種,比如線上校正。此方式需要支援伺服器能夠主動訪問認證校正伺服器才會生效,並且在每次重啟nginx的時候會主動請求一次,如果網路不通會導致nginx啟動緩慢。
# 啟用OCSP staplingssl_stapling on;# valid表示緩衝5分鐘,resolver_timeout表示網路逾時時間resolver 8.8.8.8 8.8.4.4 223.5.5.5 valid=300s;resolver_timeout 5s; # 啟用OCSP響應驗證,OCSP資訊響應適用的認證 ssl_stapling_verify on; ssl_trusted_certificate /usr/local/nginx/ssl_cert/trustchain.crt;
為了更可靠,你也可以人工負責更新檔案內容,設定Nginx直接從檔案擷取OCSP響應而無需從服務商拉取。
# 啟用OCSP staplingssl_stapling on;ssl_stapling_file /usr/local/nginx/oscp/stapling_file.ocsp; # 啟用OCSP響應驗證,OCSP資訊響應適用的認證 ssl_stapling_verify on; ssl_trusted_certificate /usr/local/nginx/ssl_cert/trustchain.crt;
2.4 TLS協議的合理配置
首先要指定TLS協議的版本,不安全的SSL2和SSL3要廢棄掉
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
其次,建議啟用ssl_prefer_server_ciphers,用來告訴Nginx在TLS握手時啟用伺服器演算法優先,由伺服器選擇適配演算法而不是用戶端:
ssl_prefer_server_ciphers on
然後,選擇最優的加密套件以及優先順序,具體可參考Mozilla的https://wiki.mozilla.org/Security/Server_Side_TLS。優先選擇支援前向加密的演算法,且按照效能的優先順序排列:
ssl_ciphers ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 ECDHE-ECDSA-AES128-SHA ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES128-SHA ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA ECDHE-ECDSA-DES-CBC3-SHA ECDHE-RSA-DES-CBC3-SHA EDH-RSA-DES-CBC3-SHA AES128-GCM-SHA256 AES256-GCM-SHA384 AES128-SHA256 AES256-SHA256 AES128-SHA AES256-SHA DES-CBC3-SHA !DSS";
最後,如果有雙向驗證的需求,可以開啟Nginx的用戶端驗證。Nginx將只接受包含有效用戶端認證的請求。如果請求未包含認證或者認證校正失敗,Nginx會返回一個400錯誤響應。
# 要求用戶端驗證ssl_verify_client on;# 指定用戶端認證到根憑證的最大憑證路徑長度ssl_verify_depth 3;# 指定允許簽發用戶端認證的CA認證ssl_client_certificate trustchain.crt;# 完整憑證鏈結中需要包含的其他CA認證ssl_trusted_certificate root-ca.crt;# 憑證撤銷清單ssl_crl revoked-certificates.crl;
2.5 False Start的合理使用
TLS False Start是指用戶端在發送ChangeCipherSpec Finished 同時發送應用資料(如HTTP請求),服務端在 TLS 握手完成時直接返回應用資料(如HTTP響應)。這樣,應用資料的發送實際上並未等到握手全部完成,故謂之False Start。
要實現False Start,服務端必須滿足兩個條件:
1. 服務端必須支援NPN(Next protocol negotiation, ALPN的前身)或者ALPN(Application layer protocol negotiation, 應用程式層協議協商);
2. 服務端必須採用支援前向加密的演算法。
補充說明下什麼是前向加密(perfect forward secrecy)。前向加密要求一個密鑰只能訪問由它所保護的資料;用來產生密鑰的元素一次一換,不能再產生其他的密鑰;一個密鑰被破解,並不影響其他密鑰的安全性。
2.6 SNI功能的合理使用
SNI(Server Name Indicate)允許用戶端在發起SSL握手請求時(ClientHello階段),就提交請求的Host資訊,使得伺服器能夠切換到正確的域並返回相應的認證。通過這種方式解決了一個IP(虛擬機器)部署多個網域名稱服務 (DNS)的問題。
Nginx支援SNI的方式並自動開啟。當遇到不支援這一特性的用戶端使用者時,通常情況下,Nginx會返回預設網站的伺服器憑證。比如下面的情況下,不支援SNI的用戶端,Nginx返回serversuning.pem。這樣認證是否能正確匹配是無法保障的,會帶來不必要的麻煩和困擾。因此,移動端開發都應該要求啟用SNI擴充。
server { listen 443 ssl default_server; ssl_certificate /usr/local/nginx/cert/serversuning.pem; ssl_certificate_key /usr/local/nginx/cert/suning.key; ...}server { listen 443 ssl; server_name sit1.suning.com; ssl_certificate /usr/local/nginx/cert/serversuningcom.pem; ssl_certificate_key /usr/local/nginx/cert/suningcom.key; ...}server { listen 443 ssl default_server; server_name sit1.suning.cn; ssl_certificate /usr/local/nginx/waf/serversuningcn.pem; ssl_certificate_key /usr/local/nginx/waf/suningcn.key; ...}
2.7 HTTP 2.0的合理使用
HTTP 2筆者在《 HTTP 2.0 原理詳細分析》和《 Nginx實現HTTP/2——原理、實踐與資料分析》中都有詳細地介紹,這裡就不再展開。需要提示下,Nginx在1.9.x版本就開始嘗試支援http2協議,但每個版本都會有bugfix,仍需要謹慎開啟,具體可參考Nginx版本更新日誌。
2.8 SSL硬體加速卡合理使用
可以通過SSL硬體加速卡裝置來代替CPU進行TLS握手過程中的運算。推薦的有Cavium的加速卡,Cavium引擎可以整合到Nginx模組中,支援物理機和虛擬機器環境。同時,虛擬機器環境下的測試效果要比物理機好。建議開啟Nginx非同步請求Cavium引擎模式,更有效提高使用率。
下面是我們壓測的CPU到20%情況下,使用TLS 1.2協議、加密套件採用ECDHE-RSA-AES128-SHA256 、HTTPS 短串連的各種環境效能資料,可見使用Cavium,物理機效能提升比:325%,虛擬機器效能提升比:588%。
| 環境 |
流量類型 |
TPS |
延遲(s) |
| 虛擬機器 |
HTTPS |
172 |
0.066 |
| 虛擬機器 + Cavium加速卡 |
HTTPS |
1012 |
0.066 |
| 物理機 |
HTTPS |
832 |
0.066 |
| 物理機 + Cavium加速卡 |
HTTPS |
2708 |
0.059 |
另外,是否使用硬體加速見仁見智了,其在效能提升上肯定是有效果的,但由於裝置價格高昂,很難大規模化,對於大部分互連網公司是一件奢侈品。借用Facebook關於硬體加速的說法:
“我們發現當前基於軟體的TLS實現在普通CPU上已經啟動並執行足夠快,無需藉助專門的加密硬體就能夠處理大量的HTTPS請求。我們使用運行於普通硬體上的軟體提供全部HTTPS服務。”
最後,我們來總結下服務端Nginx的配置,一個配置模板僅供參考:
server { listen 443 ssl http2 default_server; server_name site1.suning.com; add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; ssl_certificate /usr/local/nginx/cert/serversuningcom.pem; ssl_certificate_key /usr/local/nginx/cert/suningcom.key; # 分配10MB的共用記憶體緩衝,不同背景工作處理序共用TLS會話資訊 ssl_session_cache shared:SSL:10m; # 設定會話緩衝到期時間24h ssl_session_timeout 1440m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 SSLv3; ssl_prefer_server_ciphers on; ssl_ciphers ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 ECDHE-ECDSA-AES128-SHA ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES128-SHA ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA ECDHE-ECDSA-DES-CBC3-SHA ECDHE-RSA-DES-CBC3-SHA EDH-RSA-DES-CBC3-SHA AES128-GCM-SHA256 AES256-GCM-SHA384 AES128-SHA256 AES256-SHA256 AES128-SHA AES256-SHA DES-CBC3-SHA !DSS"; ssl_session_tickets on; ssl_session_ticket_key /usr/local/nginx/ssl_cert/session_ticket.key; #設定TLS日誌格式 log_format ssl "$time_local $server_name $remote_addr $connection $connnection_requests $ssl_protocol $ssl_cipher $ssl_session_id $ssl_session_reused"; access_log /usr/local/nginx/logs/access.log ssl; ssl_stapling on; ssl_stapling_file /usr/local/nginx/oscp/stapling_file.ocsp; ssl_stapling_verify on; ssl_trusted_certificate /usr/local/nginx/ssl_cert/trustchain.crt; root html; index index.html index.htm; location / { ... } error_page 403 /403.html; location = /403.html { root /usr/local/nginx/waf/403/default; } error_page 500 502 503 504 /502.html; location = /502.html { root /usr/local/nginx/waf/403/default; }}
3. 用戶端效能最佳化
App中使用HTTPS請求,建議設計用戶端的代理層SDK。代理層的主要目的包括兩點:(1)統一以HTTP 2協議向服務端轉寄請求;(2)調用服務端HttpDns介面,擷取準確的位址解析資訊。
3.1 移動端HTTP2加速代理
比如Android使用組件OkHttp 3,IOS使用組件NSURLSession,都可以支援HTTP 2.0協議。我也曾翻譯介紹過《OkHttp, 安卓和Java應用的HTTP&HTTP2.0用戶端》。深入到具體代碼開發和使用層面不在本文中展開。只有當用戶端和服務端都採用HTTP 2.0進行通訊,才能達到加速的效果。通過資料也能印證HTTP 2.0的效果。
3.2 HttpsDns解決DNS攻擊劫持
Dns劫持通過篡改使用者的解析指向,將使用者的流量導向第三方,以實現惡性盈利的目的。另外還有一些電訊廠商為了避免網間結算費用,會在內網做網站鏡像,再通過Dns劫持的方式,使使用者直接存取鏡像。
當全站實現HTTPS後,由於缺乏認證和私密金鑰等必要資訊,能夠保證他人Dns劫持使用者後無法達到非法目的,但同時也無法正常響應使用者的請求。這就是一把雙刃劍,因為普通使用者只會認為是你的網站載入不出來。
所以,全站HTTPS只能避免Dns劫持帶來的損失,解決Dns劫持問題還需要另覓良法。從根本上而言Dns劫持的原因是我們無法控制本地的LocalDNS不被黑(畢竟是電訊廠商的東西你懂的),那麼有沒有可能繞開電訊廠商解析?PC端我們肯定是做不到的,而移動端App我們可以使用HttpsDns的方案。
HttpsDns方案:用Https協議(IP代替網域名稱)向HttpDns叢集(權威DNS)的443連接埠進行請求,代替傳統的DNS協議向DNS伺服器的53連接埠進行請求。也就是使用Https協議去進行dns解析請求,將伺服器返回的解析結果,也就是網域名稱對應的伺服器ip獲得,直接向該ip發起對應的api服務要求,代替使用網域名稱。備選情況下(HttpsDns解析失敗),再走傳統的LocalDNS解析方式。
HttpsDns的方案優勢在於:
1. 防止了LocalDNS劫持問題
2. 平均訪問延遲下降,由於後續請求直接通過IP訪問,省去了網域名稱解析時間。並且可以通過一些演算法計算出最優效能的服務端IP(Ping延時最小),緩衝在用戶端本地地址庫;
3. 使用者串連失敗率下降
目前提供HttpDns服務端能力的廠商有中網、dnspod等,基本上是以約定介面的形式供用戶端來調用,返回解析結果。比如:
用戶端的實現並沒有想象的簡單,用ip替換網域名稱訪問,要考慮很多問題,比如:
1. Https情境下ip直連出現的認證校正問題
2. 代理情境下的HttpDns問題
3. ip訪問的時候Cookie的問題
在這裡我們不再展開,有興趣可以參考:
《Android 使用OkHttp支援HttpDNS》 http://blog.csdn.net/sbsujjbcy/article/details/50532797
《Android OkHttp實現HttpDns的最佳實務(非攔截器)》 http://blog.csdn.net/sbsujjbcy/article/details/51612832
CNSRE/HTTPDNSLib https://github.com/CNSRE/HTTPDNSLib
以上,針對TLS層的效能最佳化就已經完結了。然而,這就結束了嗎?效能提升就到此為止了嗎?當然不是。一方面,我們還應該關注與最大限制地去提升TCP層的效能,來配合TLS的最佳化。包括初始擁塞視窗調優、防止空閑時慢啟動、keep-alive等;
另一方面,關注更新的技術成果和動態,比如追求0RTT損耗的TLS 1.3、QUIC協議等
電商網站HTTPS實踐之路(三)——效能最佳化篇