Tengine TLSv1.3最佳實踐

摘要: HTTPS 時代已經來臨,TLSv1.3 已經標準化,將來各大瀏覽器會逐漸支持 TLSv1.3,本主題將分享 TLSv1.3 核心原理,以及如何結合 Tengine 讓 HTTPS 更快、更安全。nginx

TLSv1.3 概述
背景
圖片描述
SSL 是1994年網景公司提出,主要解決安全傳輸從0到1的過程,真正被大規模應用是1996年發佈的 SSLv3,通過了幾年的發展,在1999年被IETF歸入標準化,更名叫 TLS,其實本質是同樣的,TLSv1.0 跟 SSLv3 沒有太多差別,TLSv1.1 作了一些 bug 修復和支持更多的參數,TLSv1.2 基於 TLSv1.1 作了更多的擴展和算法上改進,從2008年到今年近10年的時間,TLSv1.3 在今年3月份被 IETF 討論組評正式歸入標準化,8月初出了 RFC8446,可是 TLSv1.3 在2014年已經被提出來了,經歷了4年時間的討論和優化,可是由於 TLSv1.2 已經被大量應用,一些網絡設備並不兼容以前提出的TLSv1.3草案,因此後來作了不少次優化,在第28個草案時才肯定沒有問題,正式歸入標準化。算法

趨勢
圖片描述
TLS 最大的應用就是 HTTPS,咱們來看看 chrome 統計的 HTTPS 網頁趨勢,2015年的時候,大多數國家的HTTPS網頁加載次數只佔了不到 50%,2016年美國這個佔比到了將近 60%,去年就已經超過70%, 目前美國的統計數據是 85%,可見,離 100% 已經很近了,從圖上能夠看出,日本目前接近 70%,這裏面沒有統計到中國的數據,我估計也就 40% 左右,落後其餘國家三年左右時間,空間還有很大,並且將來HTTPS趨勢是很是明顯的。至於國內大多數用戶不肯意使用HTTPS的緣由,無非幾個:安全意識、技術難度、性能和用戶體驗、成本等等緣由,這方面阿里雲 CDN 也在努力幫助用戶解決這些技術問題,在這裏不就展開了。chrome

握手原理
TLSv1.2 握手原理
完成握手
圖片描述
先來看看 TLSv1.2 的完整握手流程:
SSL 握手以前是 TCP 握手,這裏面沒畫出來,SSL 握手老是以 ClientHello 消息開始,就跟 TCP 握手老是以 SYN 包開始同樣。
整個握手過程分爲兩個過程:瀏覽器

參數協商
主要經過 Client Hello 和 Server Hello消息來協商,Client Hello 提供客戶端支持的參數(好比:密鑰套件、簽名算法、應用層協議列表等等),另外還包含一些必要的參數(好比:客戶端隨機數、SNI、session id 等等),服務器從中選取本身支持的參數,而後在 Server Hello 消息中返回,告知客戶端本次會話要使用哪些參數。
密鑰交換
主要經過 Server Key Exchange 和 Client Key Exchange 來交換,其中 Server Key Exchange 是用來發送服務器橢圓曲線算法的參數、公鑰信息以及該信息的簽名,Client Key Exchange 用來發送客戶端橢圓曲線算法的公鑰信息,雙方獲得對方橢圓曲線算法公鑰以後,與本身本地生成的臨時私鑰,經過橢圓曲線算法生成預主密鑰,再導出主密鑰和會話密鑰,握手完成以後經過會話密鑰來加密通訊。
能夠看出,完整的握手須要2個 RTT,並且每次握手都用到了非對稱加密算法簽名或者解密的操做,比較耗時和耗 CPU。假如1個 RTT 須要 100ms 的話,TLSv1.2 的完整握手時間就須要 200ms,再加上 HTTP 請求時間,那首字節時間就是 300ms 左右了。緩存

圖片描述

SSL 的握手消息雖然比較多,但不少消息都是放在一個 TCP 包中發送的,從抓包能夠看出完整的 SSL 握手須要2個 RTT。安全

會話恢復
圖片描述
剛纔經過完整的 SSL 握手能夠看出幾個缺點:服務器

2個 RTT,首包時間較長
每次都要作非對稱加密運算,比較耗 CPU,影響性能
每次都要傳證書,證書通常都比較大,浪費帶寬
SSL 握手的目的是協商參數和會話密鑰,若是能把這些會話參數緩存起來那就能夠沒有必要每次都傳證書和作非對稱加密運算,這樣就能夠提升握手性能,並且能夠將 RTT 減到1個,提升用戶體驗。
圖片描述
TLSv1.2 有兩種會話恢復的方式:Session ID 和 Session Ticketcookie

Session ID
每次完整握手以後都將協商好的會話參數緩存在客戶端和服務器中,客戶端下次握手時會將上次握手的 Session ID 帶上,服務器經過 Session ID 查詢是否有會話緩存,有的話就直接複用,沒有的話就從新走完整握手流程。
可是 Session ID 這種方式也有缺點,比較難支持分佈式緩存以及耗費服務器的內存。網絡

Session Ticket
Session Ticket 的原理能夠理解爲跟 HTTP cookie 同樣,服務器將協商好的會話參數和密鑰加密後發送給客戶端,客戶端下次握手時會將這個 Session Ticket 帶上,服務器解密成功就複用上次的會話參數和密鑰,不然就從新走完整握手流程。能夠看出 Session Ticket 這種方式不須要服務器緩存什麼,天生支持分佈式環境,有很大的優點。
這種方式有一個缺點是並非全部客戶端都支持,支持率比較低,但隨着客戶端版本的更新迭代,之後各類客戶端會都支持。由於 Session ID 客戶端支持率比較高,因此目前這兩種方式都在使用。session

圖片描述

這是咱們一個CDN節點上了分佈式 Session ID 複用的效果,Session ID 複用的比例在沒有開啓分佈式緩存時只佔了 3% 左右,複用率很低,上了分佈式 Session 緩存以後,這個比例提高到 20% 多。握手時間也從 75ms左右下降到 65ms 左右,性能提高效果仍是很明顯的。

TLSv1.3 握手原理
完整握手
圖片描述
剛纔講到 TLSv1.2 握手主要是兩步:參數協商和密鑰交換,TLSv1.3 作了優化,把這兩步合併成一步來完成了:
Client Hello 包含了客戶端支持的參數信息,以及橢圓曲線算法的公鑰信息,服務器經過 Server Hello 來響應選取的參數信息和服務器橢圓曲線公鑰信息,刪除了 TLSv1.2 中的 Server Key Exchange 和 Client Key Exchange,這樣經過1個 RTT 就能夠完成了參數協商和密鑰交換,雙方都知道了對方的橢圓曲線公鑰,而後用本身的臨時私鑰就能夠計算出預主密鑰(PMS),接着就能夠導出數據通訊的加密密鑰,但不是像 TLSv1.2 那樣的主密鑰和會話密鑰,TLSv1.3 有5個主要的密鑰,一下子咱們再細講。

這樣,假如1個 RTT 須要 100ms 的話,TLSv1.3 完整握手時間只須要 100ms,HTTP 的首字節時間只須要200ms,比 TLSv1.2 就少了1個 RTT。這個是一個很是重要的改進。

會話恢復
圖片描述
TLSv1.3 的會話恢復跟 TLSv1.2 不同,取消了 Sesion ID 的方式,採用 PSK 機制,相似 TLSv1.2 的 Session Ticket,並在 Session Ticket 中添加了過時時間。
TLSv1.2 是經過 Client Hello 的 SessionTicket 擴展選項來傳輸這個加密的會話緩存參數,但在 TLSv1.3 裏面是經過 PSK(pre_shared_key)擴展選項來傳輸。
服務器收到 PSK 以後解密成功就能夠複用會話了,不須要再從新傳輸證書和協商密鑰了,跟 TLSv1.2 的會話恢復同樣,只須要1個 RTT 就能夠完成握手,提升了握手的性能。

圖片描述

抓包截圖能夠看到:

上面是 TLSv1.3 完整握手,下面是 TLSv1.3 會話恢復握手,都是1個 RTT 就完成。
Server Hello 以後的數據包都是加密的。
連響應的證書是什麼證書都看不到,調試比較不方便。
0-RTT
圖片描述
咱們再來看看 TLSv1.3 另外一個重要特性 0-RTT:
所謂 0-RTT 是指在會話複用的基礎上作到的,在完整握手時無法作到 0-RTT,這是由於請求是用 PSK 導出的一個叫作 early_data 的密鑰加密的,服務器收到以後能夠用 PSK 導出的 ealy_data 密鑰解密,從而得出請求明文數據,這樣就能夠作到 0-RTT。
可是 TLSv1.3 的 0-RTT 是犧牲了必定的安全性的,無法作到徹底前向安全(PFS),由於知道了 SessionTicket 的加密 key 就能夠導出 early_data 密鑰,從而能夠解密出 0-RTT 數據。好比帶有鑑權信息的請求被中間網絡設備解密出來了可能就致使盜鏈這些問題;另外 0-RTT 也有重放攻擊的問題,因此 TLSv1.3 的 0-RTT 並非必須的,只是協議層面支持了 0-RTT 模式,對於請求信息不敏感的業務可使用 0-RTT 來提升性能。目前呢,Chrome 瀏覽器並不支持 0-RTT 模式,Firefox 瀏覽器支持,可是我配置了並無抓到 0-RTT 的包。服務器方面:tengine 已經支持了 0-RTT,nginx 和 openresty 尚未支持 0-RTT。

RTT 對比
圖片描述
目前,TLS 主要應用在 HTTPS 和 HTTP/2,這個表是一個完整 HTTP/2 請求從 TCP 開始所需的 RTT 對比,能夠看出,無論是 TLSv1.2 仍是 TLSv1.三、首次鏈接仍是會話複用,TCP 握手和 HTTP 請求各 1 個 RTT 是不可避免的,能優化的就只有 TLS 握手了,從剛纔講的 TLSv1.2 和 TLSv1.3 的握手原理能夠知道,在首次鏈接和會話複用狀況下,TLSv1.3 都比 TLSv1.2 少一個 RTT。

核心改進
主要差別
圖片描述
總結一下相比 TLSv1.2,TLSv1.3 主要的差別有哪些:
握手時間:同等狀況下,TLSv1.3 比 TLSv1.2 少一個 RTT
應用數據:在會話複用場景下,支持 0-RTT 發送應用數據
握手消息:從 ServerHello 以後都是密文。
TLSv1.3 協議版本的協商是從擴展選項裏面選的,不是從 ClientHello 消息的 version 字段協商的,這是由於中間有一些網絡設備對 version 字段作了識別和限制,若是對 version 進行升級,那 TLSv1.3的數據包可能在一些網絡設備中被當成異常包,不利於 TLSv1.3 的部署,因此 TLSv1.3 使用了 ClientHello 的一個新加的擴展字段 supported_versions 來協商協議版本。TLSv1.3 的記錄頭以及握手消息中 version 字段跟 TLSv1.2 同樣的。另一個握手層面的差別就是 TLSv1.3 禁止重協商。
會話複用機制:棄用了 Session ID 方式的會話複用,採用 PSK 機制的會話複用。
密鑰算法:TLSv1.3 只支持 PFS (即徹底前向安全)的密鑰交換算法,禁用 RSA 這種密鑰交換算法,這是由於使用 RSA 密鑰交換的話,若是拿到 SSL 私鑰就能夠解密抓包數據,不具有徹底前向安全性。對稱密鑰算法只採用 AEAD 類型的加密算法,像 CBC 模式的 AES、RC4 這些算法在 TLSv1.3 是禁用的。
密鑰導出算法:TLSv1.3 使用新設計的叫作 HKDF 的算法,而 TLSv1.2 是使用PRF算法,稍後咱們再來看看這兩種算法的差異。

密鑰套件
圖片描述
TLSv1.3 目前定義了這5個密鑰套件,從表面上看已經隱藏了密鑰交換算法了,由於都是使用橢圓曲線密鑰交換算法。

密鑰導出函數 PRF
圖片描述
咱們來看看 TLSv1.2 的密鑰導出算法 PRF,PRF 主要目的是用預主密鑰、客戶端隨機數、服務器隨機數導出主密鑰,再從主密鑰、雙方隨機數導出會話密鑰,最終用會話密鑰來加密通訊。
對於 RSA 來講,預主密鑰是客戶端生成,用服務器證書公鑰加密以後發給服務器,服務器用證書對應的私鑰來解密獲得。
對於橢圓曲線算法來講,預主密鑰是雙方經過橢圓曲線算法來生成的,雙方各自生成臨時公私鑰對,保留私鑰,將公鑰發給對方,而後就能夠用本身的私鑰以及對方的公鑰經過橢圓曲線算法來生成預主密鑰。
能夠看出,只要咱們知道預主密鑰或者主密鑰即可以解密抓包數據,因此 TLSv1.2 抓包解密調試只須要一個主密鑰便可,SSLKEYLOG 就是將主密鑰導出來,在 Wireshark 裏面導入就能夠解密相應的抓包數據。
另外,Session ID 緩存和 Session Ticket 裏面保存的也是主密鑰,而不是會話密鑰,這樣每次會話複用的時候再用雙方的隨機數和主密鑰導出會話密鑰,從而實現每次加密通訊的會話密鑰不同,即便一個會話被破解了也不會影響到另外一個會話。

HKDF
圖片描述
可是在 TLSv1.3裏面,再也不使用 PRF 這種算法了,而是採用更標準的 HKDF 算法來進行密鑰的推導。
並且在 TLSv1.3 中對密鑰進行了更細粒度的優化,每一個階段或者方向的加密都不是使用同一個密鑰,咱們知道TLSv1.3 在 ServerHello 消息以後的數據都是加密的,那握手期間服務器給客戶端發送的消息用 server_handshake_traffic_secret 經過 HKDF 算法導出的密鑰加密的,客戶端發送給服務器的握手消息是用 client_handshake_traffic_secret 經過 HKDF 算法導出的密鑰加密的。這兩個密鑰是經過 Handshake Secret 密鑰來導出的,而 Handshake Secret 密鑰又是由 PMS (預主密鑰)和 Early Secret 密鑰導出,而後經過 Handshake Secret 密鑰導出主密鑰 Master Secret。
圖片描述
再由主密鑰 Master Secret 導出這幾個密鑰:
client_application_traffic_secret:用來導出客戶端發送給服務器應用數據的對稱加密密鑰
server_application_traffic_secret:用來導出服務器發送給客戶端應用數據的對稱加密密鑰
resumption_master_secret:sessoin ticket 裏的主密鑰
圖片描述
在會話複用的時候略有差異,主要是要導出 0-RTT 時 early_data 要加密的密鑰 client_ealy_traffic_secret,這個是由 PSK 來導出的。

能夠看出要解密 TLSv1.3 須要5個密鑰才行。

應用實踐
支持 TLSv1.3 的服務器
圖片描述
服務器要使用 TLSv1.3 並不難,tengine-2.2.2 開始支持 TLSv1.3,nginx-1.13.8 開始支持 TLSv1.3,可是TLSv1.3 的核心實現是在 openssl 庫,但目前 openssl 穩定版本並不支持 TLSv1.3,只有 openssl-1.1.1 的預發版本或者開發版本上才支持 TLSv1.3,咱們下載支持 TLSv1.3 的 openssl 代碼以後,在編譯參數中須要加上 enable-tls1_3,編譯出 openssl 動態庫或者靜態庫,在 tengine 中依賴支持這個 openssl 庫就可使用 TLSv1.3 了。

須要注意的是 openssl 的一些接口作了改動,好比以前是一個函數,如今變成了宏,這個就致使 lua 中不能調用這個 ffi 接口了,若是本身的業務中使用了老版本 openssl 的 ffi 接口須要特別注意。

Tengine 配置 TLSv1.3
圖片描述
編譯出支持 TLSv1.3 的 tengine 後,只須要作一下配置就能夠了,在 ssl_protocols 指令中加了 TLSv1.3,同時在 ssl_ciphers 指令中加上 TLSv1.3 相關的密鑰套件。

可是這裏有一個坑:
ssl_protocols 這個指令只在默認 server 中生效,在同一個 IP 的其餘 server 塊並不生效,好比有這樣的需求:兩個域名一個開啓 TLSv1.3,另外一個不開啓 TLSv1.3,目前開源 tengine 和 nginx 是作不到的。

咱們來看看爲何作不到。
圖片描述
這是在 tengine 裏面處理一個 HTTPS 請求的大體流程,先通過握手,握手成功以後再處理 HTTP 請求。
在握手流程中,openssl 提升了幾個回調函數接口讓咱們介入,最主要的接口有這麼幾個:
client_hello_cb、get_session_cb、servername_cb、cert_cb。
client_hello_cb 是收到 ClientHello 消息後執行的第一個回調,而後選擇協議版本和選擇密鑰套件,接着調用 get_session_cb 來獲取緩存的 Session,在這個階段咱們能夠作分佈式緩存的獲取,解決分佈式環境中Session ID複用率低的問題。

openssl 解析完SNI後調用 servername_cb,這個回調主要用來切換 server 塊配置和 SSL_CTX,實現多個 server 塊不一樣 SSL 配置的問題,但能夠看出協議版本和密鑰套件早已經選擇好了,在這個階段作 SSL_CTX 切換對這兩個配置並無做用。

最後調用 cert_cb 來切換證書,在這個階段能夠用 lua 介入,實現證書的熱加載。

要想實現協議版本和密鑰套件域名定製的話,必需要在 client_hello_cb 這個階段進行切換,可是 client_hello_cb 這個回調是新版 openssl 才支持。

因此,咱們內部已經實現了這個接口,在這個階段切換 server 塊配置和 SSL_CTX,從而實現全部 SSL 配置的熱加載。這部分的實現後面我會提到開源 tengine 中,有須要的話到時關注一下。

支持 TLSv1.3 的瀏覽器
圖片描述
看一下目前支持 TLSv1.3 的瀏覽器,主要是 Chrome 和 Firefox,Chrome 是 63 以後開始支持,Firefox 是 61 版本以後支持,但每一個版本支持的 TLSv1.3 的 draft 版本可能不同,目前 Chrome 主要支持 draft23 和 draft28。Firefox 支持 draft28。IE 和 Safri 目前尚未支持 TLSv1.3,但將來也會支持的。

Chrome 開啓 TLSv1.3
圖片描述
在 chrome://flags 開關配置中選擇開啓的 TLSv1.3 哪一個草案版本,而後重啓 chrome 後生效。

圖片描述
訪問一個開啓 TLSv1.3 域名以後,打開 chrome 的開發者工具調試面板,即可以看出當前 SSL 鏈接是否已經採用了 TLSv1.3

Firefox 開啓 TLSv1.3
圖片描述
在 Firefox 的配置中心將 security.tls.version.max 設置成 4,就能夠開啓 TLS 1.3 了,目前 Firefox 只支持了最新的 TLS 1.3 Draft28

圖片描述
一樣的,在 Firefox 瀏覽器中訪問一個開啓 TLSv1.3 的域名以後,打開 Firefox 的調試工具,也能夠看出是否已經使用了 TLSv1.3

TLSv1.3 調試
圖片描述
最後來看看平時怎麼用 openssl 來調試 TLSv1.3
用 openssl 這兩個指令來啓動 server 端和客戶端,經過參數 tls1_3 參數來啓動 TLSv1.3,用 early_data 參數來實現 0-RTT 的調試。用 keylogfile 參數來記錄 TLSv1.3 裏面用到的密鑰。

圖片描述
這是TLSv1.3 的 keylog,能夠看出主要有這5個密鑰,其中:
CLIENT_EARLY_TRAFFIC_SECRET 是 0-RTT 數據的加密密鑰;
SERVER_HANDSHAKE_TRAFFIC_SECRET 是 Server 端加密握手消息用的密鑰;
SERVER_TRAFFIC_SECRET_0 是 Server 端加密應用數據的密鑰;
CLIENT_HANDSHAKE_TRAFFIC_SECRET 是 Client 端加密握手消息用的密鑰;
CLIENT_TRAFFIC_SECRET_0 是 Client 端加密應用數據的密鑰。

圖片描述

而後咱們用 wireshark 導入這個 keylog 文件,就能夠實現 TLSv1.3 數據包的解密了。

圖片描述
這個是 TLSv1.3 0-RTT 解密後的截圖,能夠看出在 TCP 握手以後,0-RTT 的數據在 Client Hello 以後就發送了。

有興趣的話,能夠本身動手調試一下。

(全文完)

原文連接

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索