本文目標:php
- 學習鑑賞TLS協議的設計,透徹理解原理和重點細節
- 跟進一下密碼學應用領域的歷史和進展
- 整理現代加密通訊協議設計的通常思路
本文有門檻,讀者須要對現代密碼學有清晰而系統的理解,建議花精力補足背景知識再讀。本文最後的參考文獻裏有一些很不錯的學習資料。html
目錄 :html5
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
TLS協議分析 與 現代加密通訊協議設計 一 . TLS協議的設計目標: 1. 密碼學的方法論 2. TLS的設計目標 3. TLS的歷史 二. TLS協議的原理 1. 自頂向下,分層抽象 3. TLS CipherSuite 4. 協議分層 5. record 協議 1. SecurityParameters 2. record層分段 3. record層的密碼學保護 4. record層的密碼學保護--MAC 5. record層的密碼學保護--stream cipher 6. record層的密碼學保護-- CBC block cipher 7. record層的密碼學保護-- AEAD cipher 8. record層的密碼學保護-- Key擴展 5. handshake 協議 1.handshake的整體流程 3. handshake 協議外層結構 4. handshake -- ClientHello,ServerHello,HelloRequest 4.1 Client Hello 4.2 Server Hello 4.3 Hello Extensions 4.4 Hello Request 5. handshake -- Server Certificate 6. handshake -- Server Key Exchange 7. handshake -- Certificate Request 8. handshake -- Server Hello Done 9. handshake -- Client Certificate 10. handshake -- Client Key Exchange (1). RSA 加密的 Premaster Secret 消息 (2). 客戶端 Diffie-Hellman 公鑰 (3). 客戶端 EC Diffie-Hellman 公鑰 11. handshake -- Cerificate Verify 12. handshake -- Finished 13. handshake -- NewSessionTicket 6. ChangeCipherSpec 協議 7. Alert 協議 8. application data協議 8. TLS協議的安全分析 1. 認證和密鑰交換 的安全性 1. 匿名密鑰交換 2. RSA 密鑰交換和認證 3. Diffie-Hellman 密鑰交換和認證 2. 版本回退攻擊 3. 針對握手過程的攻擊 4. 針對 Resuming Sessions 的攻擊 5. 針對應用數據保護的攻擊 6. 顯式 IV的安全性 7. 加密和MAC組合模式的安全性 8. DOS 攻擊下的安全性 9.Session Ticket 的安全分析 1. 無效的Session 2. 竊取 Tickets 3. 僞造 Tickets 4. DoS 攻擊 5. 加密 Ticket 的key 的管理 6. Ticket 的有效期 7. 其餘的 Ticket 格式和分發方法 8. Identity Privacy, Anonymity, and Unlinkability 9. TLS擴展: 10. TLS的配套:PKI體系 1. X.509 證書 2.現有PKI體系暴露出的問題 1. public key pin 2. HSTS 11. TLS協議歷史上出現過的漏洞,密碼學常見陷阱 1. TLS的漏洞 2. 密碼學常見陷阱 13. 下一代TLS: TLS 1.3 1. record層的密碼學保護的改動 2.handshake協議的改動 3.1-RTT 握手 4. 有反作用的 0-RTT握手 5. Resumption 和 PSK 6. Key Schedule 過程的改動 三. TLS協議的代碼實現 四. TLS協議的部署與優化 五. 更多的加密通訊協議case:QUIC,iMessage,TextSecure, otr, ios HomeKit,libsodium 1. QUIC 2. apple ios iMessage 3. apple ios HomeKit 4. TextSecure 5. otr 協議 6. libsodium/NaCL 7. Tox.im 8. CurveCP 9. tcpcrypt 10.noise 11.tcpcrypt 12. netflix MSL 12.Amazon KMS 密鑰管理服務 白皮書 六. TLS協議給咱們的啓發 -- 現代加密通訊協議設計 七. 附錄:密碼學基礎概念 1. 塊加密算法 block cipher 2. 流加密算法 stream cipher 3. Hash函數 hash funtion 4. 消息驗證碼函數 message authentication code 5. 密鑰交換 key exchange 6. 公鑰加密 public-key encryption 7. 數字簽名算法 signature algorithm 8. 密碼衍生函數 key derivation function 9. 隨機數生成器 random number generators 八. 參考文獻: TLS/SSL 相關RFC及標準 協議分析文章 實際部署調優相關 密碼學相關 相關開源項目 |
[TOC]java
一 . TLS協議的設計目標:
1. 密碼學的方法論
密碼學和軟件開發不一樣,軟件開發是工程,是手藝,造輪子是寫代碼的一大樂趣。軟件開發中經常有各類權衡,通常難有明確的對錯,通常還用建築來比擬軟件的結構,設計的優雅被高度重視。python
密碼學就不同了。密碼學是科學,不是工程,有嚴格的技術規範,嚴禁沒有通過學術訓練者隨意創造。要求嚴謹的理論建模,嚴密的數學證實。不多有須要權衡的地方,正確就是正確,錯誤就是錯誤。又因爲密碼學過去在軍事上的重要價值,各國政府一直投入大量人力物力財力,不斷深刻強化己方的算法,破解對手的算法,因此密碼學就是一種殘酷的軍備競賽。linux
-
密碼學有不少的陷阱(下文會介紹幾個),設計使用密碼學的協議或者軟件,是極其容易出錯,高風險的專業活動,單純的碼農背景是作不了的。本着不做死就不會死的偉大理念,首先推薦讀者儘量使用 TLS 這種標準化,開源,普遍使用,久經考驗,高性能的協議。本文也只是整理一點粗淺的科普常識,讀完這篇文章,並不能使讀者具備設計足夠安全的密碼學協議的能力。android
-
密碼學通過幾十年的軍備競賽式發展,已經發展出大量巧妙而狡猾的攻擊方法,咱們使用的算法,都是在全部已知的攻擊方法下都沒法攻破的,因爲咱們大多數碼農並無精力去了解最前沿的攻擊方法,因此咱們其實並無能力去評價一個加密算法,更沒有能力本身發明算法。因此最好跟着業界的主流技術走,確定不會有大錯。ios
-
現代密碼學近20年進展迅猛,如今搞現代密碼學研究的主要都是數學家,在這個領域裏面以一個碼農的知識背景,已經很難理解最前沿的東西,連正確使用加密算法都是要謹慎謹慎再謹慎的。一個碼農,能瞭解密碼學基本概念,跟進密碼學的最新應用趨勢,並正確配置部署TLS這種協議,就很不錯了。nginx
-
密碼學算法很難被正確地使用,各類細節很是容易出錯。 例如:git
- 1.大多數碼農都據說過aes,但是大多數都不瞭解細節,好比:aes應該用哪一種模式?應該用哪一種padding?IV/nonce應該取多少bit?IV/nonce應該怎麼生成? key size應該選多大?key應該怎麼生成?應不該該加MAC?MAC算法的選擇?MAC和加密應該怎麼組合?
- 2.大多數知道RSA的碼農分不清 RSASSA-PKCS1-v1_5 ,RSAES-OAEP 和 RSASSA-PSS
- 3.更多錯誤參見 這個stackoverflow問答,強烈推薦仔細閱讀
-
密碼學算法很難被正確地實現(代碼實現過程當中會引入不少漏洞,好比HeartBleed,好比各類隨機數生成器的bug,時間側通道攻擊漏洞)
-
不能只知其一;不知其二,絕對不能在只知其一;不知其二的狀況下就動手設計密碼學協議。猶如「盲人騎瞎馬,夜班臨深池」。
-
不能閉門造車,密碼學相關協議和代碼必定要開源,採用大集市式的開發,接受peer review,被越多的人review,出漏洞的可能越小(因此應該儘量使用開源組件)
2. TLS的設計目標
TLS的設計目標是構建一個安全傳輸層(Transport Layer Security ),在基於鏈接的傳輸層(如tcp)之上提供:
- 密碼學安全 (1). 保密, message privacy (保密經過加密encryption實現,全部信息都加密傳輸,第三方沒法竊聽 ) (2). 完整性, message integrity( 經過MAC校驗機制,一旦被篡改,通訊雙方會馬上發現 ) (3). 認證, mutual authentication (雙方認證,雙方均可以配備證書,防止身份被冒充 )
- 互操做,通用性 ( 根據公開的rfc,任何符合rfc的軟件實現均可以互操做,不受限於任何專利技術)
- 可擴展性 ( 經過擴展機制 tls_ext能夠添加功能,有大量的新功能,都是經過擴展添加的)
- 高效率 (經過session cache,恰當部署cache以後,tls的效率很高)
請認準這幾個目標,在後文中,會逐一實現。
3. TLS的歷史
- 1995: SSL 2.0, 由Netscape提出,這個版本因爲設計缺陷,並不安全,很快被發現有嚴重漏洞,已經廢棄。
- 1996: SSL 3.0. 寫成RFC,開始流行。目前(2015年)已經不安全,必須禁用。
- 1999: TLS 1.0. 互聯網標準化組織ISOC接替NetScape公司,發佈了SSL的升級版TLS 1.0版.
- 2006: TLS 1.1. 做爲 RFC 4346 發佈。主要fix了CBC模式相關的如BEAST攻擊等漏洞
- 2008: TLS 1.2. 做爲RFC 5246 發佈 。增進安全性。目前(2015年)應該主要部署的版本,請確保你使用的是這個版本
- 2015以後: TLS 1.3,還在制訂中,支持0-rtt,大幅增進安全性,砍掉了aead以外的加密方式
因爲SSL的2個版本都已經退出歷史舞臺了,因此本文後面只用TLS這個名字。 讀者應該明白,通常所說的SSL就是TLS。
二. TLS協議的原理
1. 自頂向下,分層抽象
構建軟件的經常使用方式是分層,把問題域抽象爲多層,每一層的概念定義爲一組原語,上一層利用下一層的組件構造實現,並被上一層使用,層層疊疊即成軟件。 * 例如在編程語言領域中,彙編語言爲一層,在彙編上面是C/C++等靜態編譯語言,C/C++之上是python/php/lua等動態類型腳本語言層,之上經常還會構造領域特定的DSL * 在網絡架構中,以太網是一層,其上是ip協議的網絡層,ip之上是tcp等傳輸層,tcp之上是http等應用層
密碼學通訊協議也是分層構造獲得。大體能夠這麼分層:
-
最底層是基礎算法原語的實現,例如: aes , rsa, md5, sha256,ecdsa, ecdh 等(舉的例子都是目前的主流選擇,下同)
-
其上是選定參數後,符合密碼學裏標準分類的算法,包括塊加密算法,簽名算法,非對稱加密算法,MAC算法等,例如: aes-128-cbc-pkcs7,rsaes-oaep ,rsassa-pkcs1-v1_5, hmac-sha256,ecdsa-p256,curve25519 等
-
再其上,是把多種標準算法組合而成的半成品組件,例如:對稱傳輸組件例如 aes-128-cbc + hmac-sha256,aes-128-gcm,認證密鑰協商算法: rsassa-OAEP + ecdh-secp256r1,數字信封:rsaes-oaep + aes-cbc-128 + hmac-sha256 ,文件密碼加密存儲組件:pbkdf2+aes-128-cbc-hmac-sha256,密鑰擴展算法 PRF-sha256 等
-
再其上,是用各類組件拼裝而成的各類成品密碼學協議/軟件,例如:tls協議,ssh協議,srp協議,gnupg文件格式,iMessage協議,bitcoin協議等等
第1層,通常程序員都有所瞭解,例如rsa,簡直路人皆知; md5 被普遍使用(固然,也有普遍的誤用) 第2層,各類莫名其妙的參數,通常很讓程序員摸不着頭腦,須要深刻學習才能理清。 第3層,不少程序員本身造的輪子,每每說白了就是想重複實現第3層的某個組件而已。 第4層,正確地理解,使用,部署這類成熟的開放協議,並非那麼容易。不少的誤用來源於不理解,須要密碼學背景知識,才能搞懂是什麼,爲何,怎麼用。
最難的是理論聯繫實際。面對一個一團亂麻的實際業務問題,最難的是從中抽象分析出其本質密碼學問題,而後用密碼學概念體系給業務建模。在分析建模過程當中,要求必須有嚴密的,體系化的思考方式。不體系化的思考方式會致使疏漏,或者誤用。
第2層中,密碼學算法,常見的有下面幾類:
- 塊加密算法 block cipher: AES, Serpent, 等
- 流加密算法 stream cipher: RC4,ChaCha20 等
- Hash函數 hash funtion:MD5,sha1,sha256,sha512 , ripemd 160,poly1305 等
- 消息驗證碼函數 message authentication code: HMAC-sha256,AEAD 等
- 密鑰交換 key exchange: DH,ECDH,RSA,PFS方式的(DHE,ECDHE)等
- 公鑰加密 public-key encryption: RSA,rabin-williams 等
- 數字簽名算法 signature algorithm:RSA,DSA,ECDSA (secp256r1 , ed25519) 等
- 密碼衍生函數 key derivation function: TLS-12-PRF(SHA-256) , bcrypto,scrypto,pbkdf2 等
- 隨機數生成器 random number generators: /dev/urandom 等
每一個類別裏面的都有幾個算法不斷競爭,優勝劣汰,近幾十年不斷有老的算法被攻破被淘汰,新的算法被提出被推廣。這一塊話題廣,水很深,內容多,陷阱也多,後續byron會翻譯整理一系列文章,分享一下每一類裏面我的收集的資料。 在此推薦一下 開源電子書crypto101,講的很透徹,並且很易讀)
設計一個加密通訊協議的過程,就是自頂向下,逐步細化,挑選各種組件,拼裝成完整協議的過程
3. TLS CipherSuite
從上述分層的角度看,TLS大體是由3個組件拼成的: – 1.對稱加密傳輸組件,例如aes-128-gcm(這幾個例子都是當前2015年最主流的選擇); – 2.認證密鑰協商組件,例如rsa-ecdhe; – 3.密鑰擴展組件,例如TLS-PRF-sha256
這些組件能夠再拆分爲5類算法,在TLS中,這5類算法組合在一塊兒,稱爲一個CipherSuite: authentication (認證算法) encryption (加密算法 ) message authentication code (消息認證碼算法 簡稱MAC) key exchange (密鑰交換算法) key derivation function (密鑰衍生算法)
TLS協議設計之初就考慮到了這每一類算法的演變,因此沒有定死算法,而是設計了一個算法協商過程,來容許加入新的算法( 簡直是軟件可擴展性設計的典範!),協商出的一個算法組合即一個CipherSuite TLS CipherSuite 在 iana 集中註冊,每個CipherSuite分配有 一個2字節的數字用來標識 ,能夠在 iana的註冊頁面 查看
iana註冊頁面截圖:
在瀏覽器中,就能夠查看當前使用了什麼 CipherSuite,在地址欄上,點擊一個小鎖的標誌,就能夠看到了。
服務器端支持的CipherSuite列表,若是是用的openssl,能夠用 openssl ciphers -V | column -t 命令查看,輸出如:
例如其中這一行(這個是目前的主流配置):
1
|
0xC0,0x2F - ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD |
表示: 名字爲ECDHE-RSA-AES128-GCM-SHA256
的CipherSuite ,用於 TLSv1.2版本,使用 ECDHE 作密鑰交換,使用RSA作認證,使用AES-128-gcm作加密算法,MAC因爲gcm做爲一種aead模式並不須要,因此顯示爲aead,使用SHA256作PRF算法。
能夠參考 man 1 ciphers
要注意的是,因爲歷史兼容緣由,tls標準,和openssl的tls實現中,有一些極度不安全的CipherSuite,必定要禁用,好比:
EXP , EXPORT : 必定要禁用。EXPORT表示上世紀美國出口限制弱化過的算法,早已經被攻破,TLS的FREAK 攻擊就是利用了這類坑爹的算法。 eNULL, NULL : 必定要禁用。NULL表示不加密!默認是禁用的。 aNULL : 必定要禁用。表示不作認證(authentication) ,也就是說能夠隨意作中間人攻擊。
ADH : 必定要禁用。表示不作認證的 DH 密鑰協商。
上面是舉個例子,讀者不要本身去研究怎麼配置,這太容易搞錯。 請按照mozilla官方給出的這個權威文檔,複製粘貼就行了。
CipherSuite的更多解釋,配置方法等,能夠參考byron以前寫的一篇文章 SSL/TLS CipherSuite 介紹
4. 協議分層
TLS是用來作加密數據傳輸的,所以它的主體固然是一個對稱加密傳輸組件。爲了給這個組件生成雙方共享的密鑰,所以就須要先搞一個認證密鑰協商組件,故,TLS協議天然分爲:
- 作對稱加密傳輸的record協議 ,the record protocol
- 作認證密鑰協商的handshake協議,the handshake protocol
還有3個很簡單的輔助協議:
- changecipher spec 協議,the changecipher spec protocol, 用來通知對端從handshake切換到record協議(有點冗餘,在TLS1.3裏面已經被刪掉了)
- alert協議,the alert protocol, 用來通知各類返回碼,
- application data協議, The application data protocol,就是把http,smtp等的數據流傳入record層作處理並傳輸。
這種 認證密鑰協商 + 對稱加密傳輸 的結構,是絕大多數加密通訊協議的通用結構,在後文的更多協議案例中,咱們能夠看到該結構一再出現。
這5個協議中: record協議在tcp流上提供分包, 圖片來自網絡:
其它的: handshake protocol, alert protocol, changeCipherSpec protocol, application data protocol都封裝在record protocol的包裏,而後在tcp上傳輸(此處以tcp舉例,也有多是udp,或者隨便什麼ipc機制等)
下文分別介紹,內容主要是翻譯自 RFC5246,RFC5077,RFC4492
5. record 協議
record協議作應用數據的對稱加密傳輸,佔據一個TLS鏈接的絕大多數流量,所以,先看看record協議 圖片來自網絡:
Record 協議 — 從應用層接受數據,而且作:
- 分片,逆向是重組
- 生成序列號,爲每一個數據塊生成惟一編號,防止被重放或被重排序
- 壓縮,可選步驟,使用握手協議協商出的壓縮算法作壓縮
- 加密,使用握手協議協商出來的key作加密/解密
- 算HMAC,對數據計算HMAC,而且驗證收到的數據包的HMAC正確性
- 發給tcp/ip,把數據發送給 TCP/IP 作傳輸(或其它ipc機制)。
1. SecurityParameters
record層的上述處理,徹底依據下面這個SecurityParameters裏面的參數進行:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
struct { ConnectionEnd entity; PRFAlgorithm prf_algorithm; BulkCipherAlgorithm bulk_cipher_algorithm; CipherType cipher_type; uint8 enc_key_length; uint8 block_length; uint8 fixed_iv_length; uint8 record_iv_length; MACAlgorithm mac_algorithm; uint8 mac_length; uint8 mac_key_length; CompressionMethod compression_algorithm; opaque master_secret[48]; opaque client_random[32]; opaque server_random[32]; } SecurityParameters; |
record 層使用上面的SecurityParameters生成下面的6個參數(不是全部的CipherSuite都須要所有6個,若是不須要,那就是空):
1
2 3 4 5 6 |
client write MAC key server write MAC key client write encryption key server write encryption key client write IV server write IV |
當handshake完成,上述6個參數生成完成以後,就能夠創建鏈接狀態,鏈接狀態除了上面的SecurityParameters,還有下面幾個參數,而且隨着數據的發送/接收,更新下面的參數:
-
compression state : 當前壓縮算法的狀態。
-
cipher state : 加密算法的當前狀態,對塊加密算法好比aes,包含密碼預處理生成的輪密鑰(感謝溫博士指出) 「round key」,還有IV等;對於流加密,包含能讓流加密持續進行加解密的狀態信息
-
sequence number : 每一個鏈接狀態都包含一個sequence number,而且讀和寫狀態有不一樣的sequence number。當鏈接開始傳輸數據時,sequence number必須置爲0. sequence number 是uint64類型的,而且不得超過 $ 2^{64}-1$ 。s. Sequence number不得迴繞。若是一個TLS實現沒法避開回繞一個sequence number,必須進行重協商。sequence number在每一個record被髮送時都增長1。而且傳輸的第1個Record必須使用0做爲sequence number。
此處有幾個問題值得思考:
(1). 爲何MAC key , encryption key, IV 要分別不一樣?
在密碼學中,對稱加密算法通常須要encryption key,IV兩個參數,MAC算法須要MAC key參數,所以這3個key用於不一樣的用途。 固然,不是全部的算法都必定會用到這3個參數,例如新的aead型算法,就不須要MAC key。
(2). 爲何client和server要使用不一樣的key 若是TLS的雙方使用相同的key,那麼當使用stream cipher加密應用數據的時候,stream cipher的字節流在兩個方向是同樣的,若是攻擊者知道TLS數據流一個方向的部分明文(好比協議裏面的固定值),那麼對2個方向的密文作一下xor,就能獲得另外一個方向對應部分的明文了。
還有,當使用 aead 好比 aes-gcm 作加密的時候,aead標準嚴格要求,絕對不能用相同的 key+nonce 加密不一樣的明文,故若是TLS雙方使用相同的key,又從相同的數字開始給nonce遞增,那就不符合規定,會直接致使 aes-gcm 被攻破。
參考: http://crypto.stackexchange.com/questions/2878/separate-read-and-write-keys-in-tls-key-material
2. record層分段
如上圖所示,對要發送的數據流,首先分段,分段成以下格式:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct { uint8 major; uint8 minor; } ProtocolVersion; enum { change_cipher_spec(20), alert(21), handshake(22), application_data(23), (255) } ContentType; struct { ContentType type; ProtocolVersion version; uint16 length; opaque fragment[TLSPlaintext.length]; } TLSPlaintext; |
-
version字段 : ,定義當前協商出來的TLS協議版本,例如 TLS 1.2 version 是 { 3, 3 }
-
length字段 : 即長度,tls協議規定length必須小於 $2^{14}$,通常咱們不但願length過長,由於解密方須要收完整個record,才能解密,length過長會致使解密方須要等待更多的rtt,增大latency,破壞用戶體驗,參考 Web性能權威指南 TLS那一章。
-
type字段 : ,用來標識當前record是4種協議中的哪種,
record壓縮 : TLS協議定義了可選的壓縮,可是,因爲壓縮致使了 2012 年被爆出CRIME攻擊,BREACH攻擊,因此在實際部署中,必定要禁用壓縮。 http://www.unclekevin.org/?p=640http://www.freebuf.com/articles/web/5636.html
3. record層的密碼學保護
record層的密碼學保護:
通過處理後的包格式定義以下:
1
2 3 4 5 6 7 8 9 10 |
struct { ContentType type; ProtocolVersion version; uint16 length; select (SecurityParameters.cipher_type) { case stream: GenericStreamCipher; case block: GenericBlockCipher; case aead: GenericAEADCipher; } fragment; } TLSCiphertext; |
TLS協議設計目標中的 1.保密(encryption) 2.完整性(authentication) ,和防重放就在這裏實現。 實現方式有3類:
- Block Cipher (CBC mode of operation) + HMAC:例如 aes-128-cbc+hmac-sha256
- Stream Cipher (RC4) + HMAC
- Authenticated-Encryption using block cipher (GCM/CCM 模式):例如 aes-128-gcm
1.Block Cipher+HMAC 和 2.Stream Cipher + HMAC 的各種算法目前(2015年)都已經爆出各類漏洞(後文解釋),目前最可靠的是 3.Authenticated-Encryption 類的算法,主要就是aes-gcm,下一代的TLS v1.3乾脆只保留了3.Authenticated-Encryption,把1和2直接禁止了(因此。。。你真的還要繼續用aes-cbc嗎?)。
GCM模式是AEAD的,因此不須要MAC算法。 GCM模式是AEAD的一種,AEAD 的 做用相似於 Encrypt-then-HMAC ,例如 Sha256 + Salt + AES + IV
此處須要介紹一個陷阱。 在密碼學歷史上,出現過3種加密和認證的組合方式:
- Encrypt-and-MAC
- MAC-then-Encrypt
- Encrypt-then-MAC
在TLS協議初定的那個年代,人們還沒意識到這3種組合方式的安全性有什麼差異,因此TLS協議規定使用 2.MAC-then-Encrypt,即先計算MAC,而後把 「明文+MAC」 再加密(塊加密或者流加密)的方式,作流加密+MAC,和塊加密+MAC。 可是,悲劇的是,近些年,人們發現 MAC-then-Encrypt 這種結構致使了 很容易構造padding oracle 相關的攻擊,例如這在TLS中,間接造成被攻擊者利用,這間接致使了 BEAST 攻擊 , Lucky 13攻擊 (CVE-2013-0169), 和 POODLE 攻擊 (CVE-2014-3566).
目前所以,學術界已經一致贊成: Encrypt-then-MAC 纔是最安全的! tls使用的是 MAC-then-Encrypt 的模式,致使了一些問題。 具體比較,參見: http://cseweb.ucsd.edu/~mihir/papers/oem.pdf https://www.iacr.org/archive/crypto2001/21390309.pdf http://crypto.stackexchange.com/questions/202/should-we-mac-then-encrypt-or-encrypt-then-mac https://news.ycombinator.com/item?id=4779015 http://tozny.com/blog/encrypting-strings-in-android-lets-make-better-mistakes/
鑑於這個陷阱如此險惡,學術界有人就提出了,乾脆把Encrypt和MAC直接集成爲一個算法,在算法內部解決好安全問題,再也不讓碼農選擇,避免衆碼農再被這個陷阱坑害,這就是AEAD(Authenticated-Encryption With Addtional data)類的算法,GCM模式就是AEAD最重要的一種。
4. record層的密碼學保護—MAC
TLS record 層 MAC的計算方法:
1
2 3 4 5 |
MAC(MAC_write_key, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment); |
其中的seq_num是當前record的 sequence number,每條record都會++, 能夠看到把 seq_num,以及record header裏面的幾個字段也算進來了,這樣解決了防重放問題,而且保證record的任何字段都不能被篡改。
算完MAC,格式以下:
1
2 3 4 |
stream-ciphered struct { opaque content[TLSCompressed.length]; opaque MAC[SecurityParameters.mac_length]; } GenericStreamCipher; |
而後根據SecurityParameters.cipher_type,選擇對應的對稱加密算法進行加密,分類解說以下:
5. record層的密碼學保護—stream cipher
stream cipher: 算stream cipher,stream cipher的狀態在連續的record之間會複用。 stream cipher的主力是RC4,可是目前RC4已經爆出多個漏洞,因此實際中基本不使用流加密無法,詳情請見:
https://tools.ietf.org/html/rfc7457#section-2.5
http://www.imperva.com/docs/HII_Attacking_SSL_when_using_RC4.pdf
6. record層的密碼學保護— CBC block cipher
CBC模式塊加密 TLS目前靠得住的的塊加密cipher也很少,基本就是AES(最靠譜,最主流),Camellia,SEED,(3DES,IDEA之類已經顯得老舊,DES請禁用),加密完的格式以下:
1
2 3 4 5 6 7 8 9 |
struct { opaque IV[SecurityParameters.record_iv_length]; block-ciphered struct { opaque content[TLSCompressed.length]; opaque MAC[SecurityParameters.mac_length]; uint8 padding[GenericBlockCipher.padding_length]; uint8 padding_length; }; } GenericBlockCipher; |
這個值得說道說道,由於咱們碼農日常在業界還能看到不少用AES-CBC的地方,其中的幾個參數:
IV : : 要求必須用密碼學安全的僞隨機數生成器(CSPRNG)生成,而且必須是不可預測的,在Linux下,就是用用/dev/urandom,或者用 openssl 庫的 RAND_bytes()。
注意:TLS 在 1.1版本以前,沒有這個IV字段,前一個record的最後一個block被當成下一個record的IV來用,而後粗大事了,這致使了 BEAST攻擊。 因此,TLS1.2改爲了這樣。 (還在使用CBC的各位,建議關注一下本身的IV字段是怎麼生成出來的。若是要用,作好和TLS1.2的作法保持一致)。
其中 SecurityParameters.record_iv_length 必定等於 SecurityParameters.block_size. 例如 AES-256-CBC的 IV 必定是16字節長的,由於AES 128/192/256 的block size都是16字節。
padding : 使用CBC經常使用的PKCS 7 padding(在block size=16字節這種狀況下,和pkcs 5的算法是一回事,java代碼裏面就能夠這麼用這個case裏,和pkcs 5的結果是同樣的)
padding_length : 就是PKCS 7 padding的最後一個字節
注意2個險惡的陷阱: 1. 實現的代碼必須在收到所有明文以後才能傳輸密文,不然可能會有BEAST攻擊 2. 實現上,根據MAC計算的時間,可能進行時間側通道攻擊,所以必須確保—運行時間和padding是否正確無關。
7. record層的密碼學保護— AEAD cipher
AEAD 到了咱們重點關注的AEAD,AEAD是新興的主流加密模式,是目前最重要的模式,其中主流的AEAD模式是 aes-gcm-128/aes-gcm-256/chacha20-poly1305
AEAD加密完的格式是:
1
2 3 4 5 6 |
struct { opaque nonce_explicit[SecurityParameters.record_iv_length]; aead-ciphered struct { opaque content[TLSCompressed.length]; }; } GenericAEADCipher; |
AEAD ciphers的輸入是: key,nonce, 明文,和 「additional data」. key是 client_write_key 或者 the server_write_key. 不須要使用 MAC key.
每個AEAD算法都要指定不一樣的nonce構造算法,並指定 GenericAEADCipher.nonce_explicit 的長度. 在TLS 1.2中,規定不少狀況下,能夠按照rfc5116 section 3.2.1的技術來作。其中record_iv_length是nonce的顯式部分的長度,nonce的隱式部分從key_block做爲 client_write_iv和 and server_write_iv得出,而且把顯式部分放在 GenericAEAEDCipher.nonce_explicit 裏.
在TLS 1.3 draft中,作了更改:
- 規定 AEAD算法的 nonce的長度規定爲 max(8 bytes, N_MIN),即若是N_MIN比8大,就用N_MIN; 若是比8小,就用8。
- 而且規定 N_MAX小於8字節的AEAD不得用於TLS。
-
規定TLS AEAD中每條record的nonce經過下面的方法構造出來: 64bit的sequence number的右側填充0,直到長度達到iv_length。而後把填充過的sequence number和靜態的 client_write_iv或 server_write_iv (根據發送端選擇)作異或(XOR)。異或完成後,獲得的 iv_length 的nonce就能夠作每條record的nonce用了。
AEAD輸入的明文就是 TLSCompressed.fragment (記得上面的介紹嗎?AEAD是MAC和encrypt的集成,因此輸入數據不須要在算MAC了).
AEAD輸入的additional_data 是:
1
2 |
additional_data = seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length; |
「+」 表示字符串拼接。
能夠看到,此處相似上面的MAC計算,算入了seq_num來防重放,type,version,length等字段防止這些元數據被篡改。
1
2 |
AEADEncrypted = AEAD-Encrypt(write_key, nonce, plaintext, additional_data) |
解密+驗證完整性:
1
2 3 |
TLSCompressed.fragment = AEAD-Decrypt(write_key, nonce, AEADEncrypted, additional_data) |
若是解密/驗證完整性失敗,就回復一條 fatal bad_record_mac alert 消息.
aes-gcm的iv長度,nonce長度,nonce構成等,後續再深刻探討。
8. record層的密碼學保護— Key擴展
Key 擴展
TLS握手生成的master_secret只有48字節,2組encryption key, MAC key, IV加起來,長度通常都超過48,(例如 AES_256_CBC_SHA256 須要 128字節),因此,TLS裏面用1個函數,來把48字節延長到須要的長度,稱爲PRF:
1
2 3 4 |
key_block = PRF(SecurityParameters.master_secret, "key expansion", SecurityParameters.server_random + SecurityParameters.client_random); |
而後,key_block像下面這樣被分割:
1
2 3 4 5 6 |
client_write_MAC_key[SecurityParameters.mac_key_length] server_write_MAC_key[SecurityParameters.mac_key_length] client_write_key[SecurityParameters.enc_key_length] server_write_key[SecurityParameters.enc_key_length] client_write_IV[SecurityParameters.fixed_iv_length] server_write_IV[SecurityParameters.fixed_iv_length] |
TLS使用HMAC結構,和在CipherSuite中指定的hash函數(安全等級起碼是SHA256的水平) 來構造PRF,
首先定義P_hash,把(secret,seed)擴展成無限長的字節流:
1
2 3 |
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + HMAC_hash(secret, A(2) + seed) + HMAC_hash(secret, A(3) + seed) + ... |
其中」+「表示字符串拼接。 A() 定義爲:
1
2 |
A(0) = seed A(i) = HMAC_hash(secret, A(i-1)) |
TLS的 PRF 就是把 P_hash 應用在secret上:
1
|
PRF(secret, label, seed) = P_<hash>(secret, label + seed) |
其中 label 是一個協議規定的,固定的 ASCII string.
要注意的是,TLS 1.3裏面已經廢棄了這種方式,改成使用更靠譜的 HKDF,HKDF 也是 html5的WebCryptoAPI的標準算法之一。
5. handshake 協議
handshake protocol重要而繁瑣。
TLS 1.3對握手作了大修改,下面先講TLS 1.2,講完再介紹一下分析TLS 1.3.
1.handshake的整體流程
handshake protocol用於產生給record protocol使用的SecurityParameters。 在handshake中:
- 客戶端和服務器端協商TLS協議版本號和一個CipherSuite,
- 認證對端的身份(可選,通常如https是客戶端認證服務器端的身份),
- 而且使用密鑰協商算法生成共享的master secret。
步驟以下:
-
交換Hello消息,協商出算法,交換random值,檢查session resumption.
-
交換必要的密碼學參數,來容許client和server協商出premaster secret。
-
交換證書和密碼學參數,讓client和server作認證,證實本身的身份。
-
從premaster secret和交換的random值 ,生成出master secret。
-
把SecerityParameters提供被record層。
-
容許client和server確認對端得出了相同的SecurityParameters,而且握手過程的數據沒有被攻擊者篡改。
Handshake的結果是在雙方創建相同的Session,Session 包含下列字段:
- session identifier session id,用來惟一標識一個session,在session 恢復的時候,也要用到
- peer certificate 對端的 X509v3 格式證書. 若是不須要認證對端的身份,就爲空。
- compression method 壓縮算法,通常被禁用
- cipher spec CipherSuite,如上文介紹,包含: 用於生成key的pseudorandom function (PRF) , 塊加密算法例如AES, MAC算法 (例如 HMAC-SHA256). 還包括一個 mac_length字段,在後文的we握手協議介紹
- master secret 48字節的,client和server共享密鑰。
- is resumable 一個標誌位,用來標識當前session是否能被恢復。
以上字段,隨後被用於生成 record層的SecurityParameters,多個鏈接能夠經過握手協議的session恢復功能來複用同一個session。
握手協議使用 非對稱加密/密鑰協商/數字簽名 3類算法, 所以要求讀者對這3類算法概念清晰,能準確區分。 在此澄清一下,: 非對稱的算法分爲3類: , * 非對稱加密,有:RSAES-PKCS1-v1_5,RSAES-OAEP ,Rabin-Williams-OAEP, Rabin-Williams-PKCS1-v1_5等 * 非對稱密鑰協商,有:DH,DHE,ECDH,ECDHE 等 * 非對稱數字簽名:RSASSA-PKCS1-v1_5,RSASSA-PSS,ECDSA,DSA,ED25519 等
另外,非對稱加密算法,能夠看成密鑰協商算法來用,因此 RSAES-PKCS1-v1_5,RSAES-OAEP 也能夠看成密鑰協商算法來用。
插播一段 RSA:
RSA的實際工程應用,要遵循PKCS#1 標準,見 https://www.ietf.org/rfc/rfc3447
其中的 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 是使用RSA算法的兩種不一樣scheme(體制)。 RSAES表示 RSA Encryption schemes,即非對稱加密, RSAES有:RSAES-OAEP,RSAES-PKCS1-v1_5兩種,其中RSAES-OAEP更新更安全
RSASSA表示 Signature schemes with appendix,即appendix模式(appendix和recovery的區別請參看密碼學教材)的非對稱數字簽名算法。 RSASSA有: RSASSA-PSS, RSASSA-PKCS1-v1_5 兩種, 其中RSASSA-PSS更新更安全
RSA還有一個缺陷,就是很容易被時間側通道攻擊,因此如今的RSA實現都要加 blinding ,後文有介紹。
能夠看到,RSA是一種很特殊的算法,既能夠當非對稱加密算法使用,又能夠當非對稱數字簽名使用。這一點頗有迷惑性,其實不少用RSA的人都分不清本身用的是RSA的哪一種模式。
相比之下,ECC(橢圓曲線)這一塊的算法就很清晰,ECDSA只能用做數字簽名,ECDH只能用做密鑰交換。
分清楚 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 有什麼用涅?
PKCS#1規範解釋:
A generally good cryptographic practice is to employ a given RSA key pair in only one scheme. This avoids the risk that vulnerability in one scheme may compromise the security of the other, and may be essential to maintain provable security.
FIPS PUB 186-3 美國標準規定:
An RSA key pair used for digital signatures shall only be used for one digital signature scheme (e.g., ANS X9.31, RSASSA-PKCS1 v1.5 or RSASSA-PSS; see Sections 5.4 and 5.5). In addition, an RSA digital signature key pair shall not be used for other purposes (e.g., key establishment).
一對密鑰只作一個用途,要麼用做非對稱加解密,要麼用做簽名驗證,別混着用! 一對密鑰只作一個用途,要麼用做非對稱加解密,要麼用做簽名驗證,別混着用! 一對密鑰只作一個用途,要麼用做非對稱加解密,要麼用做簽名驗證,別混着用!
這個要求,決定了一個協議的 PFS(前向安全性),在斯諾登曝光NSA的「今日捕獲,明日破解」政策後,愈加重要。
https://news.ycombinator.com/item?id=5942534
http://news.netcraft.com/archives/2013/06/25/ssl-intercepted-today-decrypted-tomorrow.html
https://lwn.net/Articles/572926/
https://www.eff.org/deeplinks/2014/04/why-web-needs-perfect-forward-secrecy
http://www.wired.com/2013/10/lavabit_unsealed
PFS反映到密鑰協商過程當中,就是:
- 不要使用RSA作密鑰協商,必定只用RSA作數字簽名。
- 不要把ECDH的公鑰固定內置在客戶端作密鑰協商
後文能夠看到這一原則在 TLS 1.3, QUIC,Apple的iMessage等協議中一再貫徹。
非對稱RSA/ECC這個話題比較大了,後面有空再寫文章吧,讀者能夠先看一下參考資料,裏面有清晰的介紹。
插播結束,繼續TLS。
因爲設計的時候,就要考慮兼容性,並且實際歷史悠久,因此TLS協議90年代曾經使用的一些算法,如今已經被破解了,例若有的被發現漏洞(rc4),有的密鑰長度太短(例如曾經美帝有出口限制,限制RSA 在512比特如下,對稱加密密鑰限制40比特如下,後來2005年限制被取消),可是考慮到兼容,如今的TLS實現中,仍是包含了這種已經被破解的老算法的代碼。這樣,若是攻擊者能夠干擾握手過程,誘使client和server使用這種已經被破解的算法,就會威脅TLS協議的安全,這被稱爲「降級攻擊」。
爲了在握手協議解決降級攻擊的問題,TLS協議規定:client發送ClientHello消息,server必須回覆ServerHello消息,不然就是fatal error,當成鏈接失敗處理。ClientHello和ServerHello消息用於創建client和server之間的安全加強能力,ClientHello和ServerHello消息創建以下屬性:
- Protocol Version
- Session ID
- Cipher Suite
- Compression Method.
另外,產生並交換兩個random值 ClientHello.random 和 ServerHello.random
密鑰協商使用四條: server的Certificate,ServerKeyExchange,client的Certificate,ClientKeyExchange 。TLS規定之後若是要新增密鑰協商方法,能夠訂製這4條消息的數據格式,而且指定這4條消息的使用方法。密鑰協商得出的共享密鑰必須足夠長,當前定義的密鑰協商算法生成的密鑰長度必須大於46字節。
在hello消息以後,server會把本身的證書在一條Certificate消息裏面發給客戶端(若是須要作服務器端認證的話,例如https)。 而且,若是須要的話,server會發送一條ServerKeyExchange消息,(例如若是服務器的證書只用作簽名,不用作密鑰交換,或者服務器沒有證書)。client對server的認證完成後,server能夠要求client發送client的證書,若是這是協商出來的CipherSuite容許的。下一步,server會發送ServerHelloDone消息,表示握手的hello消息部分已經結束。而後server會等待一個client的響應。若是server已經發過了CertificateRequest消息,client必須發送Certificate消息。而後發送ClientKeyExchange消息,而且這條消息的內容取決於ClientHello和ServerHello消息協商的算法。若是client發送了有簽名能力的證書,就顯式發送一個通過數字簽名的CertificateVerify消息,來證實本身擁有證書私鑰。
而後,client發送一個ChangeCipherSpec消息,而且client拷貝待定的Cipher Spec到當前的Cipher Spec。而後client當即用新算法+新key+新密鑰 發送Finished消息。收到後,server發送本身的ChangeCipherSpec消息,做爲響應,而且拷貝待定的Cipher Spec到當前的Cipher Spec。此時,握手就完成了,client和server能夠開始交換應用層數據(以下圖所示)。應用層數據不得在握手完成前發送。
引用一個來自網絡的圖片:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Client Server ClientHello --------> ServerHello Certificate* ServerKeyExchange* CertificateRequest* <-------- ServerHelloDone Certificate* ClientKeyExchange CertificateVerify* [ChangeCipherSpec] Finished --------> [ChangeCipherSpec] <-------- Finished Application Data <-------> Application Data Figure 1. Message flow for a full handshake * 表示可選的消息,或者根據上下文在某些狀況下會發送的消息。Indicates optional or situation-dependent messages that are not always sent. |
注:爲了幫助解決管道阻塞的問題,ChangeCipherSpec是一個獨立的TLS protocol content type,並非一個握手消息。
TLS的完整握手過程,要進行RSA/ECDH/ECDSA等非對稱計算,非對稱計算是很慢的。關於非對稱的性能: 例如在2015年的服務器cpu: Intel® Xeon® CPU E3-1230 V2 @ 3.30GHz 上, 使用以下命令測試:
1
2 3 4 5 |
openssl speed rsa2048 openssl speed ecdsap256 openssl speed ecdhp256 openssl speed aes-128-cbc openssl speed -evp aes-128-cbc |
結果以下表:
算法 | 性能 | 性能 | |
---|---|---|---|
RSA-2048 | 私鑰運算 723.7 次/秒 | 公鑰運算 23505.8 次/秒 | | |
256 bit ecdsa (nistp256) | 簽名 8628.4 次/秒 | 驗證 2217.0 次/秒 | | |
256 bit ecdh (nistp256) | ECDH協商 2807.8 次/秒 | | | |
aes-128-cbc | 加密 121531.39 K/秒 | | | |
aes-128-cbc 使用aesni硬件加速 | 加密 683682.13 K/秒 | | |
注:非對稱的單位是 次/秒,這是因爲非對稱通常只用於處理一個block, 對稱的單位是 K/秒,由於對稱通常用於處理大量數據流,因此單位和流量同樣。 能夠給非對稱的 次/秒 乘以 block size ,就能夠和對稱作比較了。例如rsa-2048,723.7*2048/8/1024=185.2672 K/秒 , 故 RSA-2048 私鑰運算性能 是aes-128-cbc 的 $1.5/1000$。是aesni的 $2.6/10000$。
如上,性能數據慘不忍睹, 簡直不能忍!!!
有鑑於此,TLS從設計之初,就採用了萬能手段—加cache,有2種cache手段:session id,和session ticket。把握手的結果直接cache起來,繞過握手運算。
當client和server決定恢復一個以前的session,或複用一個已有的session時(能夠不用協商一個新的SecurityParameters),消息流程以下:
客戶端使用要被恢復的session,發送一個ClientHello,把Session ID包含在其中。server在本身的session cache中,查找客戶端發來的Session ID,若是找到,sever把找到的session 狀態恢復到當前鏈接,而後發送一個ServerHello,在ServerHello中把Session ID帶回去。而後,client和server都必須ChangeCipherSpec消息,並緊跟着發送Finished消息。這幾步完成後,client和server 開始交換應用層數據(以下圖所示)。若是server在session cache中沒有找到Session ID,那server就生成一個新的session ID在ServerHello裏給客戶端,而且client和server進行完整的握手。
流程圖以下:
1
2 3 4 5 6 7 8 9 10 11 |
Client Server ClientHello --------> ServerHello [ChangeCipherSpec] <-------- Finished [ChangeCipherSpec] Finished --------> Application Data <-------> Application Data Figure 2. Message flow for an abbreviated handshake |
3. handshake 協議外層結構
從消息格式來看,TLS Handshake Protocol 在 TLS Record Protocol 的上層. 這個協議用於協商一個session的安全參數。 Handshake 消息(例如ClientHello,ServerHello等) 被包裝進 TLSPlaintext結構裏面,傳入TLS record層,根據當前session 狀態作處理,而後傳輸。
以下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
enum { hello_request(0), client_hello(1), server_hello(2), certificate(11), server_key_exchange (12), certificate_request(13), server_hello_done(14), certificate_verify(15), client_key_exchange(16), finished(20), (255) } HandshakeType; struct { HandshakeType msg_type; /* handshake type */ uint24 length; /* bytes in message */ select (HandshakeType) { case hello_request: HelloRequest; case client_hello: ClientHello; case server_hello: ServerHello; case certificate: Certificate; case server_key_exchange: ServerKeyExchange; case certificate_request: CertificateRequest; case server_hello_done: ServerHelloDone; case certificate_verify: CertificateVerify; case client_key_exchange: ClientKeyExchange; case finished: Finished; case session_ticket: NewSessionTicket; /* NEW */ } body; } Handshake; |
TLS協議規定,handshake 協議的消息必須按照規定的順序發,收到不按順序來的消息,當成fatal error處理。也就是說,TLS協議能夠當成狀態機來建模編碼。
下面按照消息發送必須遵循的順序,逐個解釋每一條握手消息。
handshake協議的外層字段,見這個抓包:
4. handshake — ClientHello,ServerHello,HelloRequest
Hello消息有3個:ClientHello, ServerHello,HellloRequest 逐個說明:
4.1 Client Hello
當客戶端第一次鏈接到服務器時,第一條message必須發送ClientHello。 另外,rfc裏規定,若是客戶端和服務器支持重協商,在客戶端收到服務器發來的HelloRequest後,也能夠回一條ClientHello,在一條已經創建的鏈接上開始重協商。(重協商是個不多用到的特性。)
消息結構:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
struct { uint32 gmt_unix_time; opaque random_bytes[28]; } Random; opaque SessionID<0..32>; uint8 CipherSuite[2]; enum { null(0), (255) } CompressionMethod; struct { ProtocolVersion client_version; Random random; SessionID session_id; CipherSuite cipher_suites<2..2^16-2>; CompressionMethod compression_methods<1..2^8-1>; select (extensions_present) { case false: struct {}; case true: Extension extensions<0..2^16-1>; }; } ClientHello; |
Random 其中:
gmt_unix_time 是 unix epoch時間戳。 random_bytes 是 28字節的,用密碼學安全隨機數生成器 生成出來的隨機數。
密碼學安全的隨機數生成,這是個很大的話題,也是一個大陷阱,目前最好的作法就是用 /dev/urandom,或者openssl庫的 RAND_bytes()
歷史上,剛好就在SSL的random_bytes這個字段,NetScape瀏覽器早期版本被爆出過隨機數生成器漏洞。 被爆菊的隨機數生成器使用 pid + 時間戳 來初始化一個seed,並用MD5(seed)得出結果。 見 http://www.cs.berkeley.edu/~daw/papers/ddj-netscape.html, 建議讀者檢查一下本身的隨機數生成器。
client_version : 客戶端支持的最高版本號。
random : 客戶端生成的random。
ClientHello.session_id 惟一標識一個session,用來作session cache。若是爲空,表示不作複用,要求服務器生成新的session。 session_id的來源有:
- 以前的協商好的鏈接的session_id
- 當前鏈接的session_id
- 當前也在使用中的另外一條鏈接的session_id
其中第三種容許不作從新握手,就同時創建多條獨立的安全鏈接。這些獨立的鏈接可能順序建立,也能夠同時建立。一個SessionID當握手協商的Finished消息完成後,就合法可用了。存活直到太舊被移除,或者session 關聯的某個鏈接發生fatal error。SessionID的內容由服務器端生成。
注:因爲SessionID的傳輸是不加密,不作MAC保護的,服務器不容許把私密信息發在裏面,不能容許僞造的SessionID在服務器形成安全問題。(握手過程當中的數據,總體是受Finished消息的保護的)
ClientHello.cipher_suites字段,包含了客戶端支持的CipherSuite的列表,按照客戶端但願的優先級排序,每一個CipherSuite有2個字節,每一個CipherSuite由:一個密鑰交換算法,一個大量數據加密算法(須要制定key length參數),一個MAC算法,一個PRF 構成。服務器會從客戶端發過來的列表中選擇一個;若是沒有能夠接受的選擇,就返回一個 handshake failure 的 alert,並關閉鏈接。若是列表包含服務器不認識,不支持,或者禁用的CipherSuite,服務器必須忽略。 若是SessionID不爲空,則cipher_suites裏面起碼要包含客戶端cache的session裏面的那個CipherSuite
compression_methods,相似地,ClientHello裏面包含壓縮算法的列表,按照客戶端優先級排序。固然,如前介紹,服務器通常禁用TLS的壓縮。
compression_methods 後面能夠跟一組擴展(extensions), extensions都是可選的,比較有用的擴展如: SNI, session ticket,ALPN,OCSP 等,後文介紹。
客戶端發送了ClientHello後,服務器端必須回覆ServerHello消息,回覆其餘消息都會致使 fatal error 關閉鏈接。
4.2 Server Hello
當收到客戶端發來的ClientHello後,正常處理完後,服務器必須回覆ServerHello。
消息結構:
1
2 3 4 5 6 7 8 9 10 11 12 13 |
struct { ProtocolVersion server_version; Random random; SessionID session_id; CipherSuite cipher_suite; CompressionMethod compression_method; select (extensions_present) { case false: struct {}; case true: Extension extensions<0..2^16-1>; }; } ServerHello; |
server_version : 服務器選擇 ClientHello.client_version 和 服務器支持的版本號 中的最小的。
random : 服務器生成的random,必須確保和客戶端生成的random沒有關聯。
session_id : 服務器爲本鏈接分配的SessionID。若是ClientHello.session_id不爲空,服務器會在本身的本地作查找。
- 若是找到了匹配,而且服務器決定複用找到的session創建鏈接,服務器應該把ClientHello.session_id一樣的 session id填入ServerHello.session_id,這表示恢復了一個session,而且雙方會當即發送Finished消息。
- 不然,回覆一個和ClientHello.random_id不一樣的Serverhello.session_id,來標識新session。服務器能夠回覆一個空的session_id,來告訴客戶端這個session不要cache,不能恢復。 若是一個session 被恢復了,那必須恢復成以前協商的session裏面的 CipherSuite。要注意的是,並不要求服務器必定要恢復session, 服務器能夠不作恢復。
在實踐中,session cache在服務器端要求key-value形式的存儲,若是tls服務器不止一臺的話,就有一個存儲怎麼共享的問題,要麼存儲同步到全部TLS服務器的內存裏,要麼專門搞服務來支持存儲,並使用rpc訪問, 不管如何,都是很麻煩的事情,相比之下,後文要介紹的session ticket就簡單多了,因此通常優先使用session ticket。
cipher_suite : 服務器選定的一個CipherSuite。若是是恢復的session,那就是session裏的CipherSuite。
compression_method : 跟上面相似。
extensions : 擴展列表。要注意的是,ServerHello.extensions 必須是 ClientHello.extensions的子集。
4.3 Hello Extensions
The extension 的格式是:
1
2 3 4 5 6 7 8 |
struct { ExtensionType extension_type; opaque extension_data<0..2^16-1>; } Extension; enum { signature_algorithms(13), (65535) } ExtensionType; |
其中:
-
「extension_type」 標識是哪個擴展類型。
-
「extension_data」 一坨二進制的buffer,擴展的數據體,各個擴展本身作解析。
extension_type 只能出現一次,ExtensionType之間不指定順序。
extensions 可能在新鏈接建立時被髮送,也可能在要求session恢復的時候被髮送。因此各個extension都須要規定本身再完整握手和session恢復狀況下的行爲。 這些狀況比較瑣碎而微妙,具體案例要具體分析。
4.4 Hello Request
服務器任什麼時候候均可以發送 HelloRequest 消息。
HelloRequest的意思是,客戶端應該開始協商過程。客戶端應該在方便的時候發送ClientHello。服務器不該該在客戶端剛建立好鏈接後,就發送HelloRequest,此時應該讓客戶端發送ClientHello。
客戶端收到這個消息後,能夠直接忽略這條消息。 服務器發現客戶端沒有響應HelloRequest後,能夠發送fatal error alert。
消息結構:
1
|
struct { } HelloRequest; |
HelloRequest不包含在握手消息的hash計算範圍內。
5. handshake — Server Certificate
當服務器肯定了CipherSuite後,根據CipherSuite裏面的認證算法,若是須要發送證書給客戶端,那麼就發送 Server Certificate消息給客戶端。Server Certificate老是在ServerHello以後當即發送,因此在同一個RTT裏。
Server Certificate裏面包含了服務器的證書鏈。
消息結構:
1
2 3 4 5 |
opaque ASN.1Cert<1..2^24-1>; struct { ASN.1Cert certificate_list<0..2^24-1>; } Certificate; |
certificate_list : 證書列表,發送者的證書必須是第一個,後續的每個證書都必須是前一個的簽署證書。根證書能夠省略
證書申請的時候,通常會收到好幾個證書,有的須要本身按照這個格式來拼接成證書鏈。
若是服務器要認證客戶端的身份,那麼服務器會發送Certificate Request消息,客戶端應該也以 這條Server Certificate消息的格式回覆。
服務器發送的證書必須:
-
證書類型必須是 X.509v3。除非明確地協商成別的了(比較少見,rfc裏提到了例如 OpenPGP格式)。
-
服務器證書的公鑰,必須和選擇的密鑰交換算法配套。
密鑰交換+認證算法 | 配套的證書中公鑰類型 | |
---|---|---|
RSA / RSA_PSK | RSA 公鑰;證書中必須容許私鑰用於加密 (即若是使用了X509V3規定的key usage擴展, keyEncipherment比特位必須置位) 這種用法沒有前向安全性,所以在 TLS 1.3中被廢棄了 | | |
DHE_RSA / ECDHE_RSA | RSA 公鑰;證書中必須容許私鑰用於簽名(即若是使用了X509V3規定的key usage擴展, digitalSignature比特位必須置位),而且容許server key exchange消息將要使用的簽名模式(例如 PKCS1_V1.5 ,OAEP等)和hash算法(例如sha1, sha256等) | | |
DHE_DSS | DSA 公鑰; 歷史遺留產物,歷來沒有被大規模用過,安全性差,廢棄狀態。證書必須容許私鑰用於簽名,必須容許server key exchange消息中使用的hash算法。 | |
DH_DSS / DH_RSA | Diffie-Hellman 公鑰; 要求key usage裏面的keyAgreement比特位必須置位。 這種用法沒有前向安全性,所以在 TLS 1.3中被廢棄了 | | |
ECDH_ECDSA / ECDH_RSA | 能作 ECDH 用途的公鑰;公鑰必須使用 客戶端支持的ec曲線和點格式。這種用法沒有前向安全性,所以在 TLS 1.3中被廢棄了| | |
ECDHE_ECDSA | ECDSA用途的公鑰;證書必須運輸私鑰用做簽名,必須容許server key exchange消息裏面要用到的hash算法。公鑰必須使用客戶端支持的ec曲線和點格式。| |
- 「server_name」 和 「trusted_ca_keys」 擴展用於引導證書選擇。
其中有5種是ECC密鑰交換算法:ECDH_ECDSA, ECDHE_ECDSA, ECDH_RSA, ECDHE_RSA, ECDH_anon。 ECC(橢圓曲線)體制相比RSA,因爲公鑰更小,性能更高,因此在移動互聯網環境下愈加重要。 以上ECC的5種算法都用ECDH來計算premaster secret, 僅僅是ECDH密鑰的生命週期和認證算法不一樣。 其中只有 ECDHE_ECDSA 和 ECDHE_RSA 是前向安全的。
若是客戶端在ClientHello裏提供了 「signature_algorithms」 擴展,那麼服務器提供的全部證書必須用 「signature_algoritms」中提供的 hash/signature算法對 之一簽署。要注意的是,這意味着,一個包含某種簽名算法密鑰的證書,可能被另外一種簽名算法簽署(例如,一個RSA公鑰可能被一個ECDSA公鑰簽署)。(這在TLS1.2和TLS1.1中是不同的,TLS1.1要求全部的算法都相同。)注意這也意味着DH_DSS,DH_RSA,ECDH_ECDSA,和ECDH_RSA 密鑰交換不限制簽署證書的算法。固定DH證書可能使用」signature_algorithms」擴展列表中的 hash/簽名算法對 中的某一個簽署。名字 DH_DSS, DH_RSA, ECDH_ECDSA, 和 ECDH_RSA 只是歷史緣由,這幾個名字的後半部分中指定的算法,並不會被使用,即DH_DSS中的DSS並不會被使用,DH_RSA中並不會使用RSA作簽名,ECDH_ECDSA並不會使用ECDSA算法。。。 若是服務器有多個證書,就必須從中選擇一個,通常根據服務器的外網ip地址,SNI中指定的hostname,服務器配置來作選擇。若是服務器只有一個證書,那麼要確保這一個證書符合這些條件。 要注意的是,存在一些證書使用了TLS目前不支持的 算法組合。例如,使用 RSASSA-PSS簽名公鑰的證書(即證書的SubjectPublicKeyInfo字段是id-RSASSA-PSS)。因爲TLS沒有給這些算法定義對應的簽名算法,這些證書不能在TLS中使用。 若是一個CipherSuite指定了新的TLS密鑰交換算法,也會指定證書格式和要求的密鑰編碼方法。
6. handshake — Server Key Exchange
服務器會在 server Certificate 消息以後,當即發送 Server Key Exchange消息。 (若是協商出的CipherSuite不須要作認證,即anonymous negotiation,會在ServerHello以後當即發送Server Key Exchange消息)
只有在server Certificate 消息沒有足夠的信息,不能讓客戶端完成premaster的密鑰交換時,服務器才發送 server Key Exchange, 主要是對前向安全的幾種密鑰協商算法,列表以下:
- DHE_DSS
- DHE_RSA
- DH_anon
- ECDHE_ECDSA
- ECDHE_RSA
- ECDH_anon
對下面幾種密鑰交換方法,發送ServerKeyExchange消息是非法的:
- RSA
- DH_DSS
- DH_RSA
- ECDH_ECDSA
- ECDH_RSA
須要注意的是,ECDH和ECDSA公鑰的數據結構是同樣的。因此,CA在簽署一個證書的時候,可能要使用 X.509 v3 的 keyUsage 和 extendedKeyUsage 擴展來限定ECC公鑰的使用方式。
ServerKeyExchange傳遞足夠的信息給客戶端,來讓客戶端交換premaster secret。通常要傳遞的是:一個 Diffie-Hellman 公鑰,或者一個其餘算法(例如RSA)的公鑰。
在TLS實際部署中,咱們通常只使用這4種:ECDHE_RSA, DHE_RSA, ECDHE_ECDSA,RSA
其中RSA密鑰協商(也能夠叫密鑰傳輸)算法,因爲沒有前向安全性,在TLS 1.3裏面已經被廢除了。參見: Confirming Consensus on removing RSA key Transport from TLS 1.3
消息格式:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
enum { dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa, ec_diffie_hellman } KeyExchangeAlgorithm; struct { opaque dh_p<1..2^16-1>; opaque dh_g<1..2^16-1>; opaque dh_Ys<1..2^16-1>; } ServerDHParams; /* Ephemeral DH parameters */ dh_p Diffie-Hellman密鑰協商計算的大質數模數。 dh_g Diffie-Hellman 的生成元, dh_Ys 服務器的Diffie-Hellman公鑰 (g^X mod p). struct { opaque point <1..2^8-1>; } ECPoint; enum { explicit_prime (1), explicit_char2 (2), named_curve (3), reserved(248..255) } ECCurveType; struct { ECCurveType curve_type; select (curve_type) { case named_curve: NamedCurve namedcurve; }; } ECParameters; struct { ECParameters curve_params; ECPoint public; //ECDH的公鑰 } ServerECDHParams; struct { select (KeyExchangeAlgorithm) { case dh_anon: ServerDHParams params; case dhe_dss: case dhe_rsa: ServerDHParams params; digitally-signed struct { opaque client_random[32]; opaque server_random[32]; ServerDHParams params; } signed_params; case ec_diffie_hellman: ServerECDHParams params |