使用 BoringSSL 優化 HTTPS 加密演算法選擇
前不久,一位朋友在我部落格評論中,問到:類似於 Google 那樣電腦訪問使用 AES,手機訪問使用 CHACHA20 的演算法是怎麼實現的。最近我研究了一下這個問題,現在我的部落格也支援這個特性了。今天抽空介紹一下我的實現步驟,供喜歡折騰的朋友們參考。
對稱內容加密
我們知道,每個 TLS 會話都是在握手階段通過非對稱加密得出對稱加密金鑰,而本次會話雙方一直會用這個金鑰進行流量的對稱加密。這樣做是出於效能考慮,畢竟對稱加密速度要快得多,更適合全流量使用。
對稱加密演算法有流式、分組兩種。RC4 就是一個常見的流式加密演算法,不過已被證實不再安全,應該停止使用。Google 推出了一種名為 ChaCha20-Poly1305 的流式加密新演算法,已經內建於各大平臺的 Chrome 之中。ChaCha20 除了更安全,還針對 ARM 做了優化,在移動裝置上使用速度更快、更省電。以下是它與 AES-GCM 在加密速度上的對比:
AES-GCM 是目前推薦使用的分組加密模式,它的缺點是計算量大,導致效能和電量開銷比較大。為此,Intel 推出了一個名為 AES NI(Advanced Encryption Standard new instructions)的 x86 指令集擴充套件,從硬體上提供對 AES 的支援。Intel 自家 CPU 從 Westmere 平臺開始支援 AES-NI,目前在 PC 端 AES-NI 的普及率顯然很高。對於支援 AES-NI 的裝置來說,使用 AES-GCM 加密演算法無疑是最優選擇,以下是一份對比(測試使用支援 AES-NI 的 Intel Xeon E3-1220 V2 @ 3.10GHz):
Did 20341000 AES-128-GCM (16 bytes) seal operations in 3000099us (6780109.6 ops/sec): 108.5 MB/s Did 2356000 AES-128-GCM (1350 bytes) seal operations in 3000761us (785134.2 ops/sec): 1059.9 MB/s Did 438000 AES-128-GCM (8192 bytes) seal operations in 3002910us (145858.5 ops/sec): 1194.9 MB/s Did 17839000 AES-256-GCM (16 bytes) seal operations in 3000160us (5946016.2 ops/sec): 95.1 MB/s Did 2092000 AES-256-GCM (1350 bytes) seal operations in 3000884us (697127.9 ops/sec): 941.1 MB/s Did 388000 AES-256-GCM (8192 bytes) seal operations in 3004207us (129152.2 ops/sec): 1058.0 MB/s Did 7779000 ChaCha20-Poly1305 (16 bytes) seal operations in 3000332us (2592713.1 ops/sec): 41.5 MB/s Did 1139000 ChaCha20-Poly1305 (1350 bytes) seal operations in 3001412us (379488.1 ops/sec): 512.3 MB/s Did 220000 ChaCha20-Poly1305 (8192 bytes) seal operations in 3006395us (73177.3 ops/sec): 599.5 MB/s
可以看到,儘管純軟體實現的 ChaCha20 演算法已經十分優秀,但跟有 AES-NI 加持的 AES-GCM 比起來還是差距明顯。
綜上,我們很容易想到「僅針對支援 AES-NI 的終端使用 AES-GCM 演算法,否則使用 ChaCha20」無疑是一個非常完美的方案。
BoringSSL
之前文章介紹過,基於 LibreSSL 編譯 Nginx,可以輕鬆地使用 ChaCha20。但問題是一旦配置了 ChaCha20,只要終端支援,無論桌面裝置還是移動裝置都會使用它。
BoringSSL 是 Google 從 OpenSSL 拉出來的一個獨立發展的分支,目前跟 OpenSSL 相比已經有很多不同之處了。BoringSSL 支援了一種名為「等價加密演算法組(Equal preference cipher groups)」的配置,正好可以滿足我們這個需求。
基於 BoringSSL 編譯 Nginx 之後,可以像下面這樣配置 ssl_ciphers:
ssl_ciphers [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305]:[ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305]:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:DES-CBC3-SHA;
方括號之中的配置就是「等價加密演算法組」,用豎線隔開的兩種演算法,會被自動應用於最合適的場景(支援 AES-NI 優先使用 AES-GCM,否則優先使用 ChaCha20)。最後的 DES-CBC3-SHA 是為了支援 Windows XP 上的 IE8 而加上去的。
下圖中,同樣是訪問本部落格,左側是 Mac Chrome 的截圖,右側是 iPhone Chrome 的截圖:
可以看到,只有移動端才使用了 ChaCha20。
這裡簡單介紹一下「等價加密演算法組」的原理:我們知道客戶端建立 TLS 連線時,在傳送的 Client Hello 中會帶上自己支援的加密演算法,供服務端從中挑選。由於老舊客戶端會支援一些不安全的加密演算法,為了提高傳輸安全,通常會在服務端指定一個可用演算法列表,最終使用的加密型別取決於二者的交集,並按服務端優先順序取第一個;如果沒有交集,直接終止會話。在 Nginx 中這個功能通過將 ssl_prefer_server_ciphers 設定為 on 開啟。
那麼問題來了,對於同時支援 AES-GCM 和 ChaCha20 的 Chrome 來說,服務端列表無論把哪個放前面都會導致另外一個完全沒機會被選中。而「等價加密演算法組」的意義在於,等價組內的演算法具有相同優先順序。這樣,客戶端可以把想要優先使用的加密演算法放在前面。舉例說明二者的區別(開啟ssl_prefer_server_ciphers 條件下):
- 不支援等價組時,如果服務端列表是:A、B、C,瀏覽器 1 支援:A、B,最終使用 A;瀏覽器 2 支援 B、A,最終使用 A,瀏覽器 3 支援 C、A,最終使用 A;
- 支援等價組時,如果服務端列表是:[A|B]、C,瀏覽器 1 支援:A、B,最終使用 A;瀏覽器 2 支援 B、A,最終使用 B,瀏覽器 3 支援 C、A,最終使用 A;
可以看到,開啟 ssl_prefer_server_ciphers 可以讓會話使用最安全的加密演算法(前提是服務端配置正確),而「等價加密演算法組」還可以讓瀏覽器有區域性調整的許可權。
補充一下通過 Mac Chrome 和 iPhone Chrome 分別訪問我的部落格傳送的加密演算法列表(使用 wireshark 抓包,在 Client Hello 握手中可獲得):
Mac Chrome:
Cipher Suites (17 suites)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcc14)
Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcc13)
Cipher Suite: TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcc15)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
Cipher Suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0×0039)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0×0033)
Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0×0035)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)
Cipher Suite: TLS_EMPTY_RENEGOTIATION_INFO_SCSV (0x00ff)iPhone Chrome:
Cipher Suites (15 suites)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcc14)
Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcc13)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0×0033)
Cipher Suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0×0039)
Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0×0035)
Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)
可以看到,Chrome 瀏覽器確實是會在不同平臺上傳送不同順序的演算法列表,只要服務端配置了「等價加密演算法組」就可以實現本文所描述的功能。
最後提醒大家:現階段 BoringSSL 不支援 OCSP Stapling。改用 BoringSSL 後,如果在 ssllabs 測試中發現這一項變成 off 不要吃驚。
詳細配置步驟
之所以把這部分內容放在最後,是因為折騰起來有點費勁,嫌麻煩的同學可以直接忽略之後所有內容。
以下步驟在我兩臺系統為 Ubuntu 14.04.3 的 VPS 上都能正常執行。如果你遇到了問題,請留言指出。
首先,獲取編譯所需的 Nginx 和 BoringSSL 原始碼,Nginx 從 1.7.4 開始支援 BoringSSL,這裡我直接使用最新版:
SHELLwget http://nginx.org/download/nginx-1.9.5.tar.gz tar xzf nginx-1.9.5.tar.gz git clone https://boringssl.googlesource.com/boringssl
現在,當前目錄下應該有這兩個子目錄:
boringssl/ nginx-1.9.5/
確認無誤後,還要做一些準備工作:
SHELL# 安裝編譯 BoringSSL 所需的 Golang sudo apt-get install golang # 忽略編譯過程中的 Warning(不加這個,編到一半會因為 Warning 太多無法繼續) export CFLAGS="-Wno-error"
編譯 BoringSSL:
SHELL# 進入 BoringSSL 原始碼根目錄 cd boringssl # 建立 build 目錄並編譯,完成後回到 BoringSSL 原始碼根目錄 mkdir build && cd build && cmake ../ && make && cd ../ # 建立 .openssl 目錄,並將庫檔案和編譯後的檔案放進去 mkdir -p .openssl/lib && cd .openssl && ln -s ../include . && cd ../ cp build/crypto/libcrypto.a build/ssl/libssl.a .openssl/lib
現在可以編譯 Nginx 了:
SHELL# 進入 Nginx 原始碼根目錄 cd nginx-1.9.5 # 修改時間,避免 Nginx 再次編譯 BoringSSL touch ../boringssl/.openssl/include/openssl/ssl.h # 指定使用 BoringSSL 作為 SSL 庫 ./configure --with-openssl=../boringssl --with-http_v2_module --with-http_ssl_module
一切無誤後可以開始 make 和 make install 了。這期間可能還會遇到這樣一個報錯:
‘SSL_R_BLOCK_CIPHER_PAD_IS_WRONG’ undeclared
這是因為 BoringSSL 刪掉了這個變數。找到報錯檔案中對應的位置,例如:
|| n == SSL_R_BLOCK_CIPHER_PAD_IS_WRONG /* 129 */
刪掉這一行,或者加個判斷都可以解決問題:
SHELL#ifdef SSL_R_BLOCK_CIPHER_PAD_IS_WRONG || n == SSL_R_BLOCK_CIPHER_PAD_IS_WRONG /* 129 */ #endif
其他應該沒什麼問題了。make install 之前記得先停掉 nginx 服務,不然很可能需要手動殺死之前的 nginx 程式。一切妥當後,參考前文修改ssl_ciphers 並啟動服務,搞定收工!
相關文章
- 選擇排序-演算法及優化排序演算法優化
- 【智慧優化演算法】遺傳演算法的精英選擇策略、期望選擇策略優化演算法
- 加密演算法介紹及加密演算法的選擇加密演算法
- 選擇優化選項的方案優化
- CSS 選擇器效能優化CSS優化
- IT優化級別的選擇優化
- AES演算法:加密通訊的新選擇演算法加密
- 圖解選擇排序及演算法優化(Java實現)圖解排序演算法優化Java
- SQL Server 2008的選擇加密演算法SQLServer加密演算法
- jQuery程式碼優化:選擇符篇jQuery優化
- 機器學習 – 特徵選擇演算法流程、分類、優化與發展綜述機器學習特徵演算法優化
- 機器學習 - 特徵選擇演算法流程、分類、優化與發展綜述機器學習特徵演算法優化
- HTTPS詳解-加密演算法(一)HTTP加密演算法
- 前端效能JQuery篇之選擇器優化前端jQuery優化
- MySQL 效能優化之儲存引擎選擇MySql優化儲存引擎
- 為什麼區域網需要https加密?如何選擇內網SSL證書?HTTP加密內網
- Https中公私鑰加密演算法和其使用的RSA演算法分析HTTP加密演算法
- 選擇HTTPS代理還是SOCKS代理?HTTP
- HTTPS詳解-加密演算法(二)完HTTP加密演算法
- HTTPS 加密演算法原理機制解析HTTP加密演算法
- 【http】https加速優化HTTP優化
- HTTPS中的加密演算法相關概念HTTP加密演算法
- MySQL學習之影響優化器選擇索引因素MySql優化索引
- SQL 優化 概念篇 之 選擇性和基數SQL優化
- 機器學習演算法優缺點對比及選擇(彙總篇)機器學習演算法
- 排序演算法__選擇排序排序演算法
- 常用演算法-選擇排序演算法排序
- 排序演算法:選擇排序排序演算法
- java選擇排序演算法Java排序演算法
- 大型網站的HTTPS實踐(二)——HTTPS加密演算法介紹網站HTTP加密演算法
- 商城商品3層選項演算法再優化演算法優化
- VueJs開發筆記—IDE選擇和優化、框架特性、資料呼叫、路由選項及使用VueJS筆記IDE優化框架路由
- 如何為資料庫選擇最佳加密方法資料庫加密
- 必讀:常見場景的加密選擇加密
- Prefab 優化:預製體中的各種細節選擇優化
- 【OPTIMIZATION】Oracle影響優化器選擇的相關技術Oracle優化
- 加密演算法的使用加密演算法
- 使用Golang編寫優化演算法 (1)Golang優化演算法