TLS的密碼學套件
來自專欄 TLS與OpenSSL
TLS協議有三個作用:驗證,防篡改,加密。這三個作用也基本上是密碼學相關的三個應用。驗證是可以同時支持客戶端驗證服務端和服務端驗證客戶端兩個需求的,只是在大部分的cs應用場景下,都是客戶端驗證服務端即可,主要目的是為了防止網站偽造,防釣魚網站的目的。防篡改的主要密碼學方法是哈希演算法,各個版本的SSL/TLS握手應用了大量的不同的哈希演算法。加密在TLS中有兩個主要的體現,一個是握手的過程中的非對稱加密用以建立信道,一個信道建立之後的對稱加密用於實際的通信。之所以分為兩個是因為對稱加密在功能上不同完成非對稱加密的密鑰協商的功能,而非對稱加密在性能上達不到對稱加密的數據要求。通常一個加密信道建立之後,這個信道都有一個存活的時間,如果存活的時間太長,理論上就會有被破解的風險,因為對稱加密的信道相關的參數在協商確定後是基本不變的。所以即使是長連接也需要隔一段時間重新進行TLS握手,這個過程叫做重協商。
但是重協商更多的是一個理論上的功能。我們做工程要時刻的區分理論和實踐的區別。通常一個TLS握手之後的連接工程上可以持續的時間遠比理論值要長很多,因為不是所有的流量都有必要要防止攻擊成本非常高的攻擊的。工程是一個折中的過程,可能只有支付等極其敏感的業務需要高嚴格的安全性參數考慮。所以重協商功能在工程上通常是不啟用的,nginx就默認關閉了這個功能(還有其他安全上的考慮)。即使我們需要重協商,在客戶端編程的時候,也可以直接的採用重寫發起一個連接來完成,業務簡單清爽,帶來的性能開銷也並不會多太多。因為信道過期並不是一個非常頻繁的操作,更多的是信道持續不到信道過期的時間。TLS1.3已經取消了重協商的機制。理論和工程的區別在調查技術的時候要時刻的保持清醒,不然很容易變成「書獃子」。
密碼學套件
密碼學套件是TLS發展了一段時間積累了很多密碼學使用的經驗之後提出的一整套的解決方案。一個套件中包含了應用於整個握手和傳輸使用到的所有非對稱加密,對稱加密和哈希演算法,甚至包括證書的類型。最早期的SSL雖然也許要一系列的加密演算法,但是這些演算法並不是像現在的稱為密碼學套件(Cipher Suite),而是被稱作密碼選擇(Cipher Choice)。密碼學套件是SSLv3開始提出的概念,從此,零散的密碼學選擇問題變成了一個整體的密碼學套件選擇的問題。後續的版本在升級的時候會產生新的安全強度更高的密碼學套件,同時拋棄比較弱的密碼學套件。但是這種密碼學套件的方式沿用了下來。TLS1.3的定義和TLS1.2的定義略有不同,這裡著重介紹1.2的定義。
一個密碼學套件是完成整個TLS握手的關鍵。在TLS握手的時候ClientHello裡面攜帶了客戶端支持的密碼學套件列表,ServerHello中攜帶了Server根據Client提供的密碼學套件列表中選擇的本地也支持的密碼學套件。也就是說選擇使用什麼密碼學套件的選擇權在Server的手裡(在使用nginx的時候把ssl_prefer_server_ciphers配置項打開),而Server通常可以通過配置文件指定Server要支持的密碼學套件的列表和順序。為此需求,OpenSSL專門定義了一套比較難懂的定義方法,Nginx的密碼學套件的配置方法也只是對OpenSSL定義的定義形式的透傳。也就是說使用Nginx定義的ssl_prefer_server_ciphers配置項是原封不動的傳輸給OpenSSL的。使用OpenSSL的工具程序就可以驗證分析一個密碼學套件字元串:openssl -v cipher RC4:HIGH:!aNULL:!MD5 ,就可以看到RC4:HIGH:!aNULL:!MD5 這個密碼學套件配置的詳細內容。這個命令是專門用於解析OpenSSL的密碼學套件的配置的工具,由於字元串格式的配置比較難學習,使用這個命令可以起到有效的幫助。
密碼套件分為三大部分:密鑰交換演算法,數據加密演算法,消息驗證演算法(MAC,message authentication code)。密鑰交換演算法用於握手過程中建立信道,一般採用非對稱加密演算法。數據加密演算法用於信道建立之後的加密傳輸數據,一般採用對稱加密演算法。MAC顧名思義是一種哈希,用於驗證消息的完整性,包括整個握手流程的完整性(例如TLS握手的最後一步就是一個對已有的握手消息的全盤哈希計算的過程)。
在OpenSSL的配置文件里寫密碼學套件的寫法和實際在OpenSSL代碼里通過解析配置生成的宏套件代碼是不同的。OpenSSL的配置文件的寫法是一個生成密碼學套件序列的文本引擎,而實際的密碼學的套件名字卻是OpenSSL代碼里的宏定義,這個宏定義的名字是和RFC中規定的命名方式是一樣的。
TLS_DHE_RSA_WITH_AES_256_CBC_SHA是一個密碼學套件的標準名字。這裡的TLS代表的是TLS協議,如果未來TLS改名,這個名字可能會變,否則會一直是這個名字。WITH是一個分隔單次,WITH前面的表示的是握手過程所使用的非對稱加密方法,WITH後面的表示的是加密信道的對稱加密方法和用於數據完整性檢查的哈希方法。WITH前面通常有兩個單次,第一個單次是約定密鑰交換的協議,第二個單次是約定證書的驗證演算法。要區別這兩個域,必須要首先明白,兩個節點之間交換信息和證書本身是兩個不同的獨立的功能。兩個功能都需要使用非對稱加密演算法。交換信息使用的非對稱加密演算法是第一個單詞,證書使用的非對稱加密演算法是第二個。有的證書套件,例如TLS_RSA_WITH_AES_256_CBC_SHA,WITH單詞前面只有一個RSA單詞,這時就表示交換演算法和證書演算法都是使用的RSA,所以只指定一次即可。可選的主要的密鑰交換演算法包括: RSA, DH, ECDH, ECDHE。可選的主要的證書演算法包括:RSA, DSA, ECDSA。兩者可以獨立選擇,並不衝突。AES_256_CBC指的是AES這種對稱加密演算法的256位演算法的CBC模式,AES本身是一類對稱加密演算法的統稱,實際的使用時要指定位數和計算模式,CBC就是一種基於塊的計算模式。最後一個SHA就是代碼計算一個消息完整性的哈希演算法。
對於OpenSSL中對密碼學套件的定義,需要從一個設計者的角度來考慮和理解,如果我來設計這個字元串引擎,我該如何的讓這一串字元串能代表我要指定的一系列密碼學套件?
一個最顯然的方法,就是我直接指定我要使用的密碼學套件的列表。由於一個密碼學套件就有五六個變數,這種制定方法會非常的長,這麼長的配置是對配置者非常不友好的,而且並不是所有要使用Nginx的都明白密碼學套件是什麼,每一種之間的區別在哪。但是這種逐個指定的模式也必須支持,這就是配置引擎的第一個符號冒號,使用冒號來形成列表,逐個的指定密碼學套件是一個最顯著的需求。同時,由於密碼學套件的五六個可變的域,所以使用正則表達式或許可以成為一個選擇,但是密碼學套件是一個從有效的選項中選擇的過程,顯然正則匹配又不合適。所以,另外一個思路就產生了,除了可以指定完整的密碼學套件的名字,還可以在列表中直接用排除法寫明支持某一種哈希,或者排除某一種哈希,這樣所有使用這種哈希,或者不使用這種哈希的密碼學套件就能夠被一次性的選擇或者排除。顯然選擇和排除可以同時出現,排除肯定是比選擇有更高的優先順序的。這也就是OpenSSL密碼學套件定義字元串的感嘆號,感嘆號表示永久性的排除某一個密碼學演算法。並且冒號分割的列表中,不但可以指定完整的密碼學套件,還可以指定某一種對稱加密,或者是哈希演算法等單個獨立的密碼學演算法,表示我願意使用包含這種密碼學演算法的所有套件。如果給它加個感嘆號,就表示我不願意使用。
使用了感嘆號,問題又出現了。我要給什麼東西加感嘆號?任何一種密碼學演算法是一個方面,但是有很多類別是否可以一次性定義?為此OpenSSL定義了一系列的配置變數,只使用這一系列的變數就可以指定特定的密碼學演算法或者某個常用的密碼學演算法的集合。
完整的支持的變數列表可以在man文件中找到,例如/docs/man1.0.2/apps/ciphers.html網址中定義的一系列變數。比如DEFAULT變數,這個變數相當於套件序列:ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2 。這個序列裡面又包含了其他變數的使用,例如ALL變數就表示出了eNULL之外的所有密碼學套件,而eNULL就表示所有沒有加密演算法的密碼學套件,也就是所有明文傳輸的密碼學套件。EXPORT表示的所有的出口強度的密碼學。這裡的感嘆號就表示了從ALL指定的所有密碼套件中排除掉出口強度的密碼學。出口強度是一個非常低的強度,出口的密碼學強度都是有關部門確定可以直接破解的加密方法(否則為什麼要出口?)。美國國內就嚴格要求了可以出口的密碼學的強度,OpenPGP就是因為密碼學的強度過大所以以出口高強度密碼學在美國被起訴了,不過作者最後把整個代碼出版成一本書,並沒有出口密碼學,而是出口書籍知識,用這種方法完美的繞過了美國的密碼學限制法律。LOW表示的所有已經被認為的可以破解的弱強度密碼,這部門密碼和EXPORT密碼是不重合的,但是兩部分加起來就幾乎是極度不可靠密碼套件的全體了。在OpenSSL中都是默認根本不編譯啟用的。aNULL表示的是所有的不包含認證的密碼學套件。類似的變數還有很多,就不一一列舉。
在密碼學套件的字元串裡面,還有一個加號和一個減號兩個操作符號,也是用於操作變數的,但是使用的比較少。在man中也有相關的介紹。由於現在瀏覽器的寡頭現象特別嚴重,每一種瀏覽器支持的加密套件基本都是固定的通用的一些,所以伺服器在選擇自己支持什麼加密套件的時候,應當參考不同的客戶端發來ClientHello時攜帶的客戶端支持的套件集。雖然總體的種類很多,但是一段時間實際通用的卻很少。例如在當前TLS1.2的環境下,證書也就是RSA和ECDSA兩種選擇,握手也基本上只有RSA和ECDHE兩種選擇,加密演算法也一般只會選擇AES-GCM(TLS1.2廢棄了IDEA和DES相關的加密套件),哈希演算法也大部分情況下時SHA256。所以實際的變化並不太多,可能使用一個完整密碼學套件的冒號列表就可以完全表達了。指定的各個密碼學套件的先後順序就是伺服器選擇使用哪個密碼學套件的先後順序。
這是谷歌Chrome瀏覽器的58.0.3029.110 (64-bit)版本在發送ClientHello消息的時候攜帶的客戶端支持的Cipher Suites列表。這種流行程度,Chrome可以代表大部分的流行度。如果Chrome都不支持的套件,那麼這個套件也很快就會被從服務端不支持。無論在哪裡,用戶才是上帝。
由於對於TLS1.2來說,服務端主要支持的密碼學就那麼幾種,所以一個常見的寫法是固定的寫:
ssl_ciphers ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:RSA+AES128:!aNULL:!eNULL:!LOW:!ADH:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS;
這一行寫到nginx的配置里就會原封不動的傳遞給OpenSSL的內部。我們可以看到,優先選擇ECC證書和ECDHE握手的方式,如果客戶端不支持或者只有RSA證書,就會選擇RSA證書和橢圓曲線的握手演算法,而一直沒有選擇RSA的握手演算法。只有在橢圓曲線的握手演算法完全不支持之後才會去可能使用RSA來握手,RSA握手在實際的應用相對在減少。ECDHE逐漸成為主流。
推薦閱讀:
※蘭花協議 能超越tor嗎?
※什麼是AEAD加密
※如何評價蘋果於北京時間 2016 年 2 月 17 日晚發布的關於 iOS 安全的公開信?
※加密手機真的需要加密晶元嗎?
※幾維安全攜KiwiVM虛擬機亮相看雪峰會