天天看點

讓大象起舞:HTTPS 計算性能優化

作者: lancelot

大象為什麼跳不高跑不快?因為它很重。HTTPS為什麼通路比較慢為什麼消耗CPU資源呢?同樣也是因為它很重。HTTPS的重,展現在如下幾方面:

1、大量的計算。SSL的每一個位元組都涉及到較為複雜的計算。即使是clientHello,也需要在握手完成時做校驗。

2、TLS協定的封裝和解析。HTTPS所有資料都是按照TLS record格式進行封裝和解析的。

3、協定的網絡互動。從TLS的握手過程可以看出,即使不需要進行任何計算,TLS的握手也需要至少1個RTT(round trip time)以上的網絡互動。

其中第2和第3點不是本文重點。本文側重為大家分析HTTPS計算方面的原理,計算性能的測試和優化。

總體來說,HTTPS主要有如下計算環節:

1、非對稱密鑰交換。比如RSA, Diffie-Hellman, ECDHE.這類算法的主要作用就是根據用戶端和服務端不對稱的資訊,經過高強度的密鑰生成算法,生成對稱密鑰,用于加解密後續應用消息。

2、對稱加解密。服務端使用密鑰A對響應内容進行加密,用戶端使用相同的密鑰A對加密内容進行解密,反之亦然。

3、消息一緻性驗證。每一段加密的内容都會附加一個MAC消息,即消息認證碼。簡單地說就是對内容進行的安全哈希計算,接收方需要校驗MAC碼。

4、證書簽名校驗。這個階段主要發生在用戶端校驗服務端證書身份時,需要對證書簽名進行校驗,確定證書的真實性。

上述環節的簡單圖示如下:

讓大象起舞:HTTPS 計算性能優化

那上述階段為什麼需要消耗CPU呢?簡單介紹一下計算原理及算法分析。

上一節提到四個主要的計算環節,事實上每個環節都需要用到指定的算法。下面結合騰訊現在主要使用到的證書簽名和密碼套件分别介紹一下。

什麼是密碼套件(cipher suite)?它其實是一套算法的統稱,包括密鑰交換算法、消息認證碼算法、内容加密算法和僞随機數算法。統計線上的cipher suite可以得出一個大概的使用情況,如下表所示:

讓大象起舞:HTTPS 計算性能優化

證書簽名算法

證書簽名算法主要用于身份校驗,現在的簽名算法主要是SHA(安全哈希)系列,目前SHA1算法已經不安全,不推薦使用,但由于部分比較老的作業系統隻支援SHA1算法,是以目前線上使用到的簽名算法主要是SHA256和少量的SHA1。

SHA2和SHA1算法最主要的操作還是位之間的運算,包括AND, OR, XOR,然後進行最多不超過80輪的疊代。是以從算法原理來看,安全哈希的計算速度應該會比較快。

讓大象起舞:HTTPS 計算性能優化

密鑰交換算法

密鑰交換就是指用戶端和服務端通過交換各自的資訊完成共同密鑰的生成。目前線上使用到的主要密鑰交換算法主要是如下三類:

讓大象起舞:HTTPS 計算性能優化

三類算法最主要的資料運算公式如下:

RSA的安全性建立在大數因子分解很困難的基礎上。它的核心數學運算公式如下:

讓大象起舞:HTTPS 計算性能優化

Diffie-Hellman的安全性建立在離散對數求解比較困難的基礎上,它的核心數學運算公式如下:

讓大象起舞:HTTPS 計算性能優化

ECDHE是在橢圓曲線有限域上實作的Diffie-Hellman算法,具備Diffie-Hellman同樣的安全性,它的核心數學運算公式如下:

讓大象起舞:HTTPS 計算性能優化

分析上述運算方程有什麼意義呢?

1、可以看出RSA和DH的主要計算都是模幂計算。模幂計算通常進行的輪次比較多,計算量比較大,對于CPU是一個很大的負擔。

2、橢圓曲線實際上是一個集合,并且定義了一套計算規則。使用較小的數字就能實作RSA同樣的安全強度。如下表所示,ECC使用224位長度的密鑰就能實作RSA2048位長度的安全性。

讓大象起舞:HTTPS 計算性能優化

對稱加密算法

對稱加密算法就是使用相同的密鑰對資料進行加解密,常用的對稱加密算法如下:

讓大象起舞:HTTPS 計算性能優化

由于對稱加密算法最主要的數學運算是XOR,雖然不同的模式對速度有一定的影響,但是由于密鑰長度短,同時計算過程簡單,對稱加密的效率非常高。

消息認證碼算法

消息認證碼算法和證書簽名算法的核心操作有點類似,主要是基于安全哈希函數,比如SHA1或者SHA2。是以這裡就不做多餘介紹。

性能測試

從上一章的數學原理分析可以得出初步的性能結論,RSA類運算應該是最消耗CPU資源的。但是純數學分析還不能完全代表真正的軟體性能。本章我們使用三種方式測試一下HTTPS的性能。

openssl speed

Openssl提供了一個速度測試的工具:openssl speed。顧名思義,它的功能就是測試openssl支援的全部算法的速度。

Openssl speed測試的基本原理是在指定時間内(比如3S或者10S),循環調用指定的密碼算法,最後的運算次數就代表了該算法的性能。測試平台的資訊如下:

讓大象起舞:HTTPS 計算性能優化

由于線上業務單個請求的平均大小約為4K位元組,是以下表統計了openssl加密和安全雜湊演算法每秒能夠處理的位元組數以及處理4K位元組需要的使用者時間。

讓大象起舞:HTTPS 計算性能優化

可以看出,資料加解密和安全哈希的操作時間基本都是10微秒級别以下。其中耗時最多的是SHA256,需要20微秒。下表統計了RSA簽名、校驗及橢圓曲線的操作時間:

讓大象起舞:HTTPS 計算性能優化

可以看出RSA2048位的簽名操作速度很慢,單次操作需要耗時1.4毫秒,ECDSA的操作速度要快一些,大概需要0.5毫秒。橢圓曲線的操作時間統計如下:

讓大象起舞:HTTPS 計算性能優化

橢圓曲線的操作曲線基點選取,耗時大概0.4毫秒。

運作時間分析

Openssl speed隻能統計單個算法的性能和執行時間,但這個時間不能代表線上業務真實運作需要消耗的時間,原因是:

1、一次完整請求涉及到不同算法的組合,單個算法無法反映整體時間。

2、HTTPS協定互動時需要協定解析,網絡互動,異步異常處理,這裡肯定會有一些額外的性能損耗。

3、openssl實作TLS協定棧時需要調用多個應用層函數和系統調用,也會有一定的開銷。

是以需要進一步統計真實的運作時間,主要是統計openssl處理各個主要環節的消耗時間。限于篇幅,這裡隻介紹ECDHE_RSA密鑰交換算法的性能,具體的統計函數和時序如下:

讓大象起舞:HTTPS 計算性能優化

從上圖所示的資料能夠非常明顯地看到,ServerKeyExchange消耗了2.4毫秒,要顯著超過其他消息的耗時。那ServerKeyExchange消息為什麼消耗時間呢?進一步分析如下:

讓大象起舞:HTTPS 計算性能優化

Perf 事件及火焰圖分析

Perf是linux核心提供的一個性能分析工具。它的主要功能是能夠周期性地采集各個函數的CPU周期數,進而反映性能瓶頸及熱點代碼。火焰圖能更加形象直覺地展示perf record記錄下來的事件。通過perf record紀錄壓力測試nginx過程中的perf 事件,分别繪制ecdhe_rsa和rsa密鑰交換算法下的火焰圖,如下所示:

讓大象起舞:HTTPS 計算性能優化

火焰圖是SVG格式的向量圖,由于事件數太多,截圖無法反映詳細的事件和函數關系圖。

這裡簡單的概括一下: 和rsa計算相關的事件占全部采樣約75%,ECC相關的計算占比約10%。

讓大象起舞:HTTPS 計算性能優化

簡單概括一下:使用RSA密鑰交換時,RSA相關的事件采樣占整體采樣的比例約85%。

性能測試的結論

性能測試的最終目的是為了性能優化。根據之前的測試資料,HTTPS的計算性能優化思路可以總結如下:

1、完全握手對性能的影響非常大,性能降低至普通HTTP性能的 10%以下。應該盡量提升tcp reuse及session resume,減少完全握手的發生。

2、密鑰交換過程中的RSA算法對性能的影響非常大,一次正常HTTPS互動過程,RSA計算過程需要消耗整體性能的75%左右。也就是說,如果能夠提升RSA的性能,那麼整體性能将最多提升4倍。

3、ECC曲線相關的計算約占整體計算量的7%,對整體性能的影響不大。但如果RSA計算優化好了,ECC的性能将逐漸成為瓶頸。

4、對稱加解密及MAC計算對性能影響很小(微秒級别),暫時不需要考慮優化。

RSA異步代理計算

從上一章的分析可以知道,HTTPS協定中最消耗CPU計算資源的就是密鑰交換過程中的RSA計算。也是我們優化的最主要對象。如何優化呢?思路如下:

1、算法分離。将最消耗CPU計算的過程分離出來,釋放本地CPU,提升整體吞吐性能。

2、并行計算。使用SSL硬體加速卡或者空閑CPU并行計算,提升計算效率。

3、異步代理。算法分離和計算的過程是異步的,不需要同步等待SSL加速計算的結果傳回。

下面詳細介紹一下上述步驟:

【算法分離】此的核心思想是:分析加密算法的完整過程,将最消耗CPU的計算過程剝離出來,避免在本地CPU上進行同步計算。

需要分離哪些算法呢?

由于我們現在的證書主要使用RSA簽名,暫時沒有ECDSA簽名證書,是以目前設計隻針對RSA簽名證書。

RSA簽名證書最常用的密鑰交換算法是ECDHE_RSA,DHE_RSA和RSA,是以我們需要重點解決的就是這三個算法,如下圖示:

讓大象起舞:HTTPS 計算性能優化

由于DHE_RSA算法性能較差,是以優先推薦使用ECDHE_RSA和RSA密鑰交換算法。下面較長的描述一下兩個算法的具體分離過程。

【ECDHE_RSA密鑰交換算法分離】SSL完全握手過程中,ECDHE_RSA涉及到的最消耗CPU的握手消息為ServerKeyExchange,它在整個握手過程中的位置如下:

讓大象起舞:HTTPS 計算性能優化

其中綠色框标示的Gen_master_key并不是一個握手消息,它表示的是生成Master key的過程,主要是調用SHA256完成計算。紅色标示的ServerKeyExchange消息需要大量的CPU計算。那ServerKeyExchange消息為什麼需要大量的CPU計算?它需要處理哪些内容呢?主要是如下兩步:

1、選擇ECC(橢圓曲線密碼)的曲線類型、基點、曲線系數等參數,并根據這些參數生成公鑰。

2、對曲線參數和公鑰進行RSA簽名。

由之前的分析得知,這裡的RSA簽名過程需要使用2048位長度的私鑰對資料進行加密,非常消耗CPU。

【RSA密鑰交換算法分離】RSA密鑰交換算法的過程相對簡單,因為沒有ECC參數及公鑰生成的過程。根據RFC5246描述,用戶端使用RSA公鑰對premaster内容進行加密,服務端需要使用私鑰解密premaster key,進而生成最終的master key。

上述過程圖示如下:

讓大象起舞:HTTPS 計算性能優化

其中Gen_master_key主要分為如下兩步:

1、私鑰RSA私鑰解密premaster key。

2、根據premaster key和其他參數生成master key。這一步同樣是調用SHA256完成計算。

同樣地根據之前的分析,RSA解密相比SHA256計算要消耗更多的CPU計算量。

并行計算

為了提升機關時間T内處理的性能,有兩個思路:

1、減少單個請求的計算時間。

2、提升請求并發計算能力。即能夠同時處理更多個請求。

減少單個請求的計算時間通過采用更高頻率和性能的CPU或者專用硬體加速卡的方案能夠解決,本文不多做介紹。提升請求并發計算能力是指同一時刻使用多個CPU或者多個硬體加速卡方案實作性能的提升。模型如下:

讓大象起舞:HTTPS 計算性能優化

顯然,如果使用更多個數的CPU和硬體加速單元,并行計算能力就得到了顯著提升。同樣機關時間T内,能夠處理的請求數變成了:4*T / T1。相比串行計算,性能提升了4倍。

異步請求

Nginx的目前程序必須等待openssl完成ServerKeyExchange或者premaster secret的處理後才能傳回進行其他工作。如下圖所示:

讓大象起舞:HTTPS 計算性能優化

上圖隻是簡單說明了RSA簽名的同步計算過程,事實上RSA解密的計算過程類似。

假設請求1需要進行RSA簽名(RSA_sign)操作,nginx必須等待上圖中2 到 7共6個步驟全部完成才能處理下一個請求2。

飄紅标示的BN_mod_exp是指大數的模幂計算,是RSA的核心運算過程,由于指數非常大,是以它是個非常消耗CPU的運算。

同步請求的弊端是:

1、由于該過程需要消耗大量的CPU,nginx整體性能受到嚴重制約。

2、除非同步計算的能力非常強,否則即使将過程分離到其他硬體或者CPU完成,由于程序間或者網絡間的開銷,同步過程也會嚴重制約nginx的整體性能。

是以上述計算過程需要異步進行,即在openssl進行高強度CPU計算時,比如處理serverKeyExchange或者premaster secret消息,nginx目前程序無需等待計算結果的傳回,可以馬上執行其他工作。異步過程如下圖所示:

1、Nginx接收到請求1後,調用RSA_sign。

2、RSA_sign此時會調用RSA_private_encrypt,然後直接傳回,不需等待RSA的簽名結果。

3、Nginx此時可以處理其他請求。

4、RSA_private_encrypt是RSA簽名的核心函數,主要是使用RSA私鑰對哈希值進行加密。它的最主要計算過程還是大數的模幂計算。

5、最消耗CPU的計算由于已經被分離到其他CPU或者硬體加速卡,是以不會消耗本地CPU,同時由于這個過程是異步的,也不會阻塞上層的NGINX。

讓大象起舞:HTTPS 計算性能優化

難點

工程實作的難點主要展現在對openssl和nginx核心代碼的掌控上。可以概括成如下幾點:

1、需要學習和了解的知識量大。包含 ssl3.0到tls1.2協定,pki體系,pkcs标準,x509标準,ECC标準,光RFC的閱讀涉及至少30個以上,常用的比如5246,5280,4492等。

2、openssl代碼量大、舊、亂、深。

a)大。代碼行數超過50萬行。因為要實作不同協定版本,不同算法組合,還要跨平台,支援各種硬體,是以代碼量非常龐大。

b)舊。openssl有很多曆史遺留的無用代碼,比如一些過時的算法、系統及加速硬體。

c)亂。風格不良不統一,充斥着大量宏定義,宏開關,缺少注釋等。

d)深。由于涉及到版本和算法很多,本身就比較難懂,又進行了一系列的高層抽象和封裝,比如EVP,ssl23,ssl3系列等。

提到了這麼多openssl不好的地方,網上甚至有一些文章公開嘲笑甚至辱罵openssl,但是在我的心裡卻一直認為,一份開源免費卻守護着虛拟世界安全的代碼,值得每一個人尊敬和崇拜。

3、openssl雖然非常重要,但是網際網路上關于openssl和HTTPS代碼工程方面有深度有價值的參考資料幾乎為零。

4、需要修改nginx事件架構實作SSL完全握手的優化。nginx雖然代碼優良,參考資料也多,但是代碼有很多細節設計得比較巧妙,修改事件架構很容易踩坑。

計算架構的變化

RSA計算方式的變化必然會導緻計算架構的變化。其中現在預設的廣泛使用的方式又叫本機CPU同步計算架構。

【本機同步計算架構】這裡的同步是指上層應用比如nginx必須等待CPU執行完RSA計算後才能傳回執行其他工作。這裡需要注意的是,即使将同步模型的CPU換成SSL硬體加速卡,對性能的提升也非常有限,不到30%。

讓大象起舞:HTTPS 計算性能優化

【異步代理計算架構】改造後的架構如下:

讓大象起舞:HTTPS 計算性能優化

異步代理計算架構的特點将最消耗性能的RSA計算分離出來,使用并行計算能力更強的方案替代本機CPU完成計算,同時整個過程是異步的,上層應用程式(NGINX)不需要等待RSA計算結果的傳回就能接收其他請求。

RSA異步代理性能優化結論

最終通過RSA異步代理計算,nginx ecdhe_rsa完全握手性能提升了3.5倍,由18000qps提升到了65000qps。

雖然之前性能分析裡提到了相比非對稱密鑰交換算法來講,對稱加密算法的性能非常卓越(好1到2個數量級),但是如果應用層傳輸内容較大的話,特别是移動端的CPU計算能力較弱,對稱加密算法對性能的影響也不容忽視。如何優化呢?通過異步代理的方式顯然不可能。原因是:會極大降低使用者通路速度。由于應用層的每一個位元組都需要對稱加解密,使用異步的方式實作會嚴重降低加解密的實時性。

那有沒有同步的優化方式呢?有。類似SSL硬體加速卡,intel針對AES算法實作硬體加速,并将它內建到了CPU指令裡。

AES-NI指令

AES-NI是intel推出的針對AES對稱加密算法進行優化的一系列指令,通過硬體計算實作計算速度的提升。如何測試AES-NI的性能呢?通過環境變量

讓大象起舞:HTTPS 計算性能優化

aesni對性能的提升約20%, 由4.3W提升到5.1W。這裡需要注意的是,如果需要單獨使用openssl的API進行AES對稱加解密,最好使用aes evp API,這樣才會預設開啟AES-NI指令。

1、chacha20-poly1305

chacha20-poly1305是由Dan Bernstein發明,并且由google推出的一種帶身份認證的對稱加密算法。其中chacha20是指對稱加密算法,poly1305指身份認證算法。這個算法是對沒有AES硬體加速功能的移動平台的補充,比如ARM晶片。從google公布的資料來看,chacha20-poly1305能夠提升30%以上的加解密性能,節省移動端耗電量。當然,如果手機端支援AES-NI指令的話,chacha20就沒有優勢了。

我們最開始選用libressl的一個重要原因也是它支援chacha20-poly1305,openssl雖然暫時不支援,不過最近釋出的版本應該馬上就會支援了。

2、session resume

HTTPS最消耗性能的階段就是完全握手,不管是對使用者的通路速度還是CPU資源消耗,避免完全握手的發生都能夠極大地提升性能。

SSL協定目前提供兩種機制來實作簡化握手,避免完全握手的發生:

a)session cache

SSL2.0引入了session identifier機制,如果用戶端使用的SSL協定版本大于2.0(全部浏覽器都支援,包括IE6),那麼server端在收到client hello消息時會生成一個32位元組長度的ID(SSL2.0以後ID是0到48位元組長度),儲存在緩存并且将生成的session id通過server hello消息發送給使用者。

用戶端在後續的SSL握手請求中通過client hello消息發送session id,server端擷取到ID後會從本地或者叢集緩存中查找,如果ID查找命中,表明這個session 是可以信任的,能夠複用。SSL握手提前完成,不需要繼續處理完全握手需要的密鑰交換等消耗CPU資源的步驟,同時節省了一個RTT。

【分布式session cache的應用】Session identifier支援得非常廣泛。但nginx目前隻支援内置緩存及單機程序間共享的session緩存,在多伺服器的接入架構下,單機的session緩存幾乎是無效的。

針對這種場景,TGW支援四層會話保持,這樣在會話保持期間内的client都會落到相同的機器,顯著地提升了session cache的命中率。

b)session ticket

session tickets (RFC5077)是一種不需要server端儲存session狀态資訊的session恢複機制。用戶端在client hello消息裡發送empty session ticket extension表示支援session ticket機制,服務端的nginx在server hello裡也會發送一條empty sesson ticket 消息表示支援。這樣在完全握手快要結束時,nginx會發送new session ticket消息生成一個新的ticket。

用戶端在後續的請求過程中會在client hello包裡攜帶這個ticket,如果nginx能夠正确解密這個ticket,标明session能夠複用。握手完成,同時發送new session ticket更新ticket。即每次發送請求的ticket都不同。

【分布式session ticket的應用】在多個STGW接入的環境下,同樣存在不同使用者的session ticket無法被正确處理的問題。為了解決這個問題,STGW配置了全局的session ticket key,即針對全部STGW的nginx,使用相同的key來進行加解密。相同用戶端的session ticket,不管下次落到哪台nginx,都能被正确處理,實作簡化握手。

讓大象起舞:HTTPS 計算性能優化

1、提升session resume比率,盡量實作分布式session cache及session ticket,減少SSL完全握手的發生。不僅節省網絡RTT,提升使用者通路速度,也避免了非對稱密鑰交換的發生,減少了CPU的消耗。

2、通過異步代理完成RSA的私鑰計算。ssl完全握手性能由18000qps提升到了63000qps,提升了~3.5倍。節省了接入機器成本,提升了業務的活動營運及防攻擊能力。

3、使用性能更高,更安全的對稱加密算法,AES-GCM,CHACHA20-POLY1305.

4、開啟對稱算法加速指令AES-NI。