TLS協議分析

TLS協議分析

本文目標:php

  1. 學習鑑賞TLS協議的設計,透徹理解原理和重點細節
  2. 跟進一下密碼學應用領域的歷史和進展
  3. 整理現代加密通訊協議設計的通常思路

本文有門檻,讀者須要對現代密碼學有清晰而系統的理解,建議花精力補足背景知識再讀。本文最後的參考文獻裏有一些很不錯的學習資料。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. 密碼學安全 (1). 保密, message privacy (保密經過加密encryption實現,全部信息都加密傳輸,第三方沒法竊聽 ) (2). 完整性, message integrity( 經過MAC校驗機制,一旦被篡改,通訊雙方會馬上發現 ) (3). 認證, mutual authentication (雙方認證,雙方均可以配備證書,防止身份被冒充 )
  2. 互操做,通用性 ( 根據公開的rfc,任何符合rfc的軟件實現均可以互操做,不受限於任何專利技術)
  3. 可擴展性 ( 經過擴展機制 tls_ext能夠添加功能,有大量的新功能,都是經過擴展添加的)
  4. 高效率 (經過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等應用層

密碼學通訊協議也是分層構造獲得。大體能夠這麼分層:

  1. 最底層是基礎算法原語的實現,例如: aes , rsa, md5, sha256,ecdsa, ecdh 等(舉的例子都是目前的主流選擇,下同)

  2. 其上是選定參數後,符合密碼學裏標準分類的算法,包括塊加密算法,簽名算法,非對稱加密算法,MAC算法等,例如: aes-128-cbc-pkcs7,rsaes-oaep ,rsassa-pkcs1-v1_5, hmac-sha256,ecdsa-p256,curve25519 等

  3. 再其上,是把多種標準算法組合而成的半成品組件,例如:對稱傳輸組件例如 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 等

  4. 再其上,是用各類組件拼裝而成的各類成品密碼學協議/軟件,例如:tls協議,ssh協議,srp協議,gnupg文件格式,iMessage協議,bitcoin協議等等

第1層,通常程序員都有所瞭解,例如rsa,簡直路人皆知; md5 被普遍使用(固然,也有普遍的誤用) 第2層,各類莫名其妙的參數,通常很讓程序員摸不着頭腦,須要深刻學習才能理清。 第3層,不少程序員本身造的輪子,每每說白了就是想重複實現第3層的某個組件而已。 第4層,正確地理解,使用,部署這類成熟的開放協議,並非那麼容易。不少的誤用來源於不理解,須要密碼學背景知識,才能搞懂是什麼,爲何,怎麼用。

最難的是理論聯繫實際。面對一個一團亂麻的實際業務問題,最難的是從中抽象分析出其本質密碼學問題,而後用密碼學概念體系給業務建模。在分析建模過程當中,要求必須有嚴密的,體系化的思考方式。不體系化的思考方式會致使疏漏,或者誤用。

第2層中,密碼學算法,常見的有下面幾類:

  1. 塊加密算法 block cipher: AES, Serpent, 等
  2. 流加密算法 stream cipher: RC4,ChaCha20 等
  3. Hash函數 hash funtion:MD5,sha1,sha256,sha512 , ripemd 160,poly1305 等
  4. 消息驗證碼函數 message authentication code: HMAC-sha256,AEAD 等
  5. 密鑰交換 key exchange: DH,ECDH,RSA,PFS方式的(DHE,ECDHE)等
  6. 公鑰加密 public-key encryption: RSA,rabin-williams 等
  7. 數字簽名算法 signature algorithm:RSA,DSA,ECDSA (secp256r1 , ed25519) 等
  8. 密碼衍生函數 key derivation function: TLS-12-PRF(SHA-256) , bcrypto,scrypto,pbkdf2 等
  9. 隨機數生成器 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協議天然分爲:

  1. 作對稱加密傳輸的record協議 ,the record protocol
  2. 作認證密鑰協商的handshake協議,the handshake protocol

還有3個很簡單的輔助協議:

  1. changecipher spec 協議,the changecipher spec protocol, 用來通知對端從handshake切換到record協議(有點冗餘,在TLS1.3裏面已經被刪掉了)
  2. alert協議,the alert protocol, 用來通知各類返回碼,
  3. 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 協議 — 從應用層接受數據,而且作:

  1. 分片,逆向是重組
  2. 生成序列號,爲每一個數據塊生成惟一編號,防止被重放或被重排序
  3. 壓縮,可選步驟,使用握手協議協商出的壓縮算法作壓縮
  4. 加密,使用握手協議協商出來的key作加密/解密
  5. 算HMAC,對數據計算HMAC,而且驗證收到的數據包的HMAC正確性
  6. 發給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類:

  1. Block Cipher (CBC mode of operation) + HMAC:例如 aes-128-cbc+hmac-sha256
  2. Stream Cipher (RC4) + HMAC
  3. 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種加密和認證的組合方式:

  1. Encrypt-and-MAC
  2. MAC-then-Encrypt
  3. 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

[FreeBuf] RC4加密已再也不安全,破解效率極高

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中,作了更改:

  1. 規定 AEAD算法的 nonce的長度規定爲 max(8 bytes, N_MIN),即若是N_MIN比8大,就用N_MIN; 若是比8小,就用8。
  2. 而且規定 N_MAX小於8字節的AEAD不得用於TLS。
  3. 規定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 包含下列字段:

  1. session identifier session id,用來惟一標識一個session,在session 恢復的時候,也要用到
  2. peer certificate 對端的 X509v3 格式證書. 若是不須要認證對端的身份,就爲空。
  3. compression method 壓縮算法,通常被禁用
  4. cipher spec CipherSuite,如上文介紹,包含: 用於生成key的pseudorandom function (PRF) , 塊加密算法例如AES, MAC算法 (例如 HMAC-SHA256). 還包括一個 mac_length字段,在後文的we握手協議介紹
  5. master secret 48字節的,client和server共享密鑰。
  6. 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的來源有:

  1. 以前的協商好的鏈接的session_id
  2. 當前鏈接的session_id
  3. 當前也在使用中的另外一條鏈接的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, 主要是對前向安全的幾種密鑰協商算法,列表以下:

  1. DHE_DSS
  2. DHE_RSA
  3. DH_anon
  4. ECDHE_ECDSA
  5. ECDHE_RSA
  6. ECDH_anon

對下面幾種密鑰交換方法,發送ServerKeyExchange消息是非法的:

  1. RSA
  2. DH_DSS
  3. DH_RSA
  4. ECDH_ECDSA
  5. 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;  Signature signed_params;  case rsa:  case dh_dss:  case dh_rsa:  struct {} ;  /* message is omitted for rsa, dh_dss, and dh_rsa */  /* may be extended, e.g., for ECDH -- see [TLSECC] */  };  } ServerKeyExchange;   params  服務器的密鑰交換參數。   signed_params  對須要認證的(即非anonymous的)密鑰交換,對服務器的密鑰交換參數的數字簽名。 

ECParameters 結構比較麻煩,其中ECCurveType是支持3種曲線類型的,能夠自行指定橢圓曲線的多項式係數,基點等參數。可是,咱們基本不會用到這種功能,由於通常部署都是使用 NamedCurve,即參數已經預先選定,各類密碼學庫廣泛都支持的一組曲線,其中目前用的最廣的是 secp256r1 (還被稱爲 P256,或 prime256v1)

NamedCurve 列表中比較重要的曲線(在TLS1.3中,只保留了這幾條曲線。),定義以下:

1
2 3 4 5 6 
 enum {  ...  secp256r1 (23), secp384r1 (24), secp521r1 (25),  reserved (0xFE00..0xFEFF),  (0xFFFF)  } NamedCurve; 

ECDHE_RSA 密鑰交換算法的 SignatureAlgorithm 是 rsa 。 ECDHE_RSA 密鑰交換算法的 SignatureAlgorithm 是 ecdsa。

若是客戶端提供了 「signature_algorithms」 擴展, 則簽名算法和hash算法必須是列在擴展中的算法。 要注意的是,這個地方可能有不一致,例如客戶端可能提供了 DHE_DSS 密鑰交換,可是 「signature_algorithms」擴展中沒有DSA算法,在這類狀況下,爲了正確地協商,服務器必須確保知足本身選擇的CipherSuite知足 「signature_algorithms」 的限制。這不優雅,可是是爲了把對原來的CipherSuite協商的設計的改動減到最小,而作的妥協。

而且,hash和簽名算法,必須和服務器的證書裏面的公鑰兼容。

7. handshake — Certificate Request

TLS規定了一個可選功能:服務器能夠認證客戶端的身份,這經過服務器要求客戶端發送一個證書實現,服務器應該在ServerKeyExchange以後當即發送CertificateRequest消息。

消息結構:

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
 enum {  rsa_sign(1), dss_sign(2), rsa_fixed_dh(3),dss_fixed_dh(4),  rsa_ephemeral_dh_RESERVED(5),dss_ephemeral_dh_RESERVED(6),  fortezza_dms_RESERVED(20),  ecdsa_sign(64), rsa_fixed_ecdh(65),  ecdsa_fixed_ecdh(66),  (255)  } ClientCertificateType;   opaque DistinguishedName<1..2^16-1>;   struct {  ClientCertificateType certificate_types<1..2^8-1>;  SignatureAndHashAlgorithm  supported_signature_algorithms<2^16-1>;  DistinguishedName certificate_authorities<0..2^16-1>;  } CertificateRequest; 

certificate_types : 客戶端能夠提供的證書類型。

  • rsa_sign 包含RSA公鑰的證書。
  • dss_sign 包含DSA公鑰的證書。
  • rsa_fixed_dh 包含靜態DH公鑰的證書。
  • dss_fixed_dh 包含靜態DH公鑰的證書。

    supported_signature_algorithms : 服務器支持的 hash/signature 算法的列表。

    certificate_authorities : 服務器能夠接受的CA(certificate_authorities)的 distinguished names 的列表 DER編碼格式.

這些 distinguished names 可能爲root CA或者次級CA指定了想要的 distinguished name ,所以,這個消息能夠用來描述已知的root,或者但願的受權空間。 若是 certificate_authorities 列表是空的,那麼客戶端能夠發送任何適當的 ClientCertificateType 類型的證書,若是沒有別的限制的話。

certificate_types 和 supported_signature_algorithms 字段的交叉選擇很複雜。 certificate_types 這個字段從SSLv3時代就定義了,可是一直都沒有詳細定義,其大多數功能都被 supported_signature_algorithms 代替了。 有以下規則:

  • 客戶端提供的任何證書,必須用一個supported_signature_algorithms 中出現過的 hash/signature 算法對 簽名.

  • 客戶端提供的末端證書必須提供一個和 certificate_types 兼容的key。 若是這個key是一個簽名key,那必須能和 supported_signature_algorithms 中提供的某個 hash/signature 算法對配合使用。

  • 因爲歷史緣由,某些客戶端證書類型的名字,包含了證書的簽名算法,例如,早期版本的TLS中, rsa_fixed_dh 意思是一個被RSA算法簽署,而且包含一個固定DH密鑰的證書。在TLS1.2中,這個功能被 supported_signature_algorithms 淘汰,而且證書類型再也不限制用來簽署證書的算法。例如,若是服務器發送了 dss_fixed_dh 證書類型,和 { {sha1, dsa}, {sha1,rsa} } 簽名類型,客戶端能夠回覆一個 包含靜態DH密鑰,用RSA-sha1簽署的證書。

  • 若是協商出來的是匿名CipherSuite,服務器不能要求客戶端認證。

8. handshake — Server Hello Done

在 ServerHello和相關消息已經處理結束後,服務器發送ServerHelloDone。在發送ServerHelloDone後,服務器開始等待客戶端的響應。

ServerHelloDone消息表示,服務器已經發送完了密鑰協商須要的消息,而且客戶端能夠開始客戶端的密鑰協商處理了。

收到ServerHelloDone後,客戶端應該確認服務器提供了合法的證書,而且確認服務器的ServerHello消息裏面的參數是能夠接受的。

消息格式:

1
 struct { } ServerHelloDone; 

 

9. handshake — Client Certificate

ClientCertificate消息是客戶端收到ServerHelloDone後,能夠發送的第一條消息。僅當服務器要求了一個證書的狀況下,客戶端才發送ClientCertificate消息,若是沒有可用的合適證書,客戶端必須發送一條不包含任何證書的ClientCertificate消息(即 certificate_list 結構長度爲0)。

若是客戶端沒有發送任何證書,服務器自行決定,能夠放棄要求客戶端認證,繼續握手;或者發送一條 fatal handshake_failure的alert消息,斷開鏈接。而且,若是證書鏈的某些方面是不能接受的(好比證書沒有被可信任的CA簽署),服務器能夠自行決定,是繼續握手(放棄要求客戶端認證),或者發送一條fatal的alert。

客戶端證書使用和ServerCertificate相同的結構發送。

ClientCertificate把客戶端的證書鏈發送給服務器。服務器會使用證書鏈來驗證CertificateVerify 消息(若是使用基於簽名的客戶端認證),或者來計算premaster secret(對於非短暫的 DH)。證書必須和協商出來的CipherSuite的密鑰交換算法配套,並和任何協商的擴展配套。

尤爲是:

  • 證書必須是X.509v3 類型的。
  • 客戶端的末級證書的公鑰必須和CertificateRequest裏列出的證書類型兼容。
  客戶端證書類型 證書公鑰類型
  rsa_sign RSA公鑰;證書必須容許公鑰用於certificateVerify消息中的數字簽名和hash算法 |
  dss_sign DSA 公鑰;證書必須容許密鑰使用CertificateVerify中的hahs函數作簽名;|
  ecdsa_sign 能夠用做 ECDSA 的公鑰;證書必須容許 公鑰用 CertificateVerify中的hash函數作簽名;公鑰必須使用服務器支持的曲線,和點格式;|
  rsa_fixed_dh / dss_fixed_dh Diffie-Hellman 公鑰; 必須使用和服務器key相同的參數。|
  rsa_fixed_ecdh / ecdsa_fixed_ecdh 能夠用做 ECDH 的公鑰。必須和服務器的公鑰使用一樣的曲線,一樣的點格式|
  • 若是 certificate_authorities 列表不是空的,客戶端證書鏈中的某一個證書必須是CA中的某一個簽署的。
  • 證書必須使用 服務器能夠接受的 hash/signature 算法對。

相似於Server Certificate,有一些證書目前沒法在TLS中使用。

10. handshake — Client Key Exchange

客戶端必須在客戶端的Certificate消息以後,當即發送ClientKeyExchange消息。 或者必須在ServerHelloDone後當即發送ClientKeyExchange消息。

ClientKeyExchange消息中,會設置premaster secret,經過發送 RSA公鑰加密premaster secret的密文,或者發送容許雙方得出相同的premaster secret的Diffie-Hellman參數。

當客戶端使用短暫的 Diffie-Hellman 密鑰對時,ClientKeyExchange包含客戶端的 Diffie-Hellman 公鑰。若是客戶端發送一個包含靜態 Diffie-Hellman 指數的證書(好比,在使用固定DH的客戶端認證),那麼這條消息必須被髮送,而且必須爲空。

消息結構: 消息的選擇取決於選擇的密鑰交換算法。

1
2 3 4 5 6 7 8 9 10 11 12 13 14 
 struct {  select (KeyExchangeAlgorithm) {  case rsa:  EncryptedPreMasterSecret;  case dhe_dss:  case dhe_rsa:  case dh_dss:  case dh_rsa:  case dh_anon:  ClientDiffieHellmanPublic;  case ec_diffie_hellman:  ClientECDiffieHellmanPublic;  } exchange_keys;  } ClientKeyExchange; 

(1). RSA 加密的 Premaster Secret 消息

若是用RSA作密鑰協商和認證,客戶端生成 48字節的 premaster secret,使用服務器證書裏面的公鑰加密,而後把密文EncryptedPreMasterSecret發送給服務器,結構定義以下:

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
 struct {  ProtocolVersion client_version;  opaque random[46];  } PreMasterSecret;   client_version  客戶端支持的最新協議版本號,這個字段用來檢測中間人版本回退攻擊。T  random  46 字節的,安全生成的隨機值。   struct {  public-key-encrypted PreMasterSecret pre_master_secret;  } EncryptedPreMasterSecret;   pre_master_secret  這個隨機值由客戶端生成,用於生成master secret

注:PreMasterSecret裏面的 client_version 是 ClientHello.client_version,而不是協商的到的版本號,這個特性用來阻止版本回退攻擊。不幸的是,有些不正確的老的代碼使用了協商獲得的版本號,致使檢查client_version字段的時候,和正確的實現沒法互通。

客戶端實現必須在PreMasterSecret中發送正確的版本號。若是 ClientHello.client_version 的版本號是 TLS 1.1 或者更高,服務器實現必須以下檢查版本號。若是版本號是 TLS 1.0 或者更早,服務器必須檢查版本號,可是能夠經過配置項關閉檢查。

要注意的是,若是版本號檢查失敗了,PreMasterSecret 應該像下面描述的那樣填充成隨機數。

TLS中的RSA使用的是 PKCS1-V1.5 填充( PKCS1-V1.5也是openssl庫RSA的默認填充方式)。Bleichenbacher 在1998年發表了一種針對 PKCS1-V1.5 的選擇密文攻擊, Klima在2003年發現 PKCS1-V1.5 中 PreMasterSecret 版本號檢查的一個側通道攻擊。只要TLS 服務器暴露一條特定的消息是否符合PKCS1-V1.5格式,或暴露PreMasterSecret解密後結構是否合法,或版本號是否合法,就能夠用上面2種方法攻擊。

Klima 還提出了徹底避免這類攻擊的方法:對格式不正確的消息,版本號不符的狀況,要作出和徹底正確的RSA塊同樣的響應,要讓客戶端區分不出這3種狀況。 具體地說,要以下:

  1. 生成 46 字節的密碼學安全隨機值 R
  2. 解密消息,得到明文 M
  3. 若是 PKCS#1 填充不正確,或者 PreMasterSecret 消息的長度不是48字節,則 pre_master_secret = ClientHello.client_version || R 或者若是 ClientHello.client_version <= TLS 1.0,而且明確禁止了版本號檢查,則 pre_master_secret = ClientHello.client_version || M[2..47]

注意:明確地用 ClientHello.client_version 構造 pre_master_secret 時,當客戶端在原來的 pre_master_secret 中發送了錯誤的 客戶端版本值時,會產生一個不合法的 master_secret 。

另外一種解決問題的方法是,把版本號不符,當成 PKCS-1 格式錯誤來對待,而且徹底隨機填充 premaster secret。

  1. 生成 48 字節的密碼學安全隨機值 R
  2. 解密 PreMasterSecret 恢復出明文 M
  3. 若是 PKCS#1 填充不正確,或者消息的長度不是48字節,則 pre_master_secret = R 或者若是 ClientHello.client_version <= TLS 1.0,而且 明確禁止了版本號檢查,則 pre_master_secret = M 或者若是 M[0..1] != CleintHello.client_version pre_master_secret = R 或者 pre_master_secret = M

儘管實踐中,尚未發現針對這種結構的攻擊,Klima 在論文中描述了幾種理論上的攻擊方式,所以推薦上述的第一種結構。

在任何狀況下,一個 TLS 服務器絕對不能在:1. 處理 RSA 加密的 premaster 消息失敗, 2.或者版本號檢查失敗 時產生alert消息。當遇到這兩種狀況時,服務器必須用隨機生成的 premaster 值繼續握手。服務器能夠把形成失敗的真實緣由log下來,用於調查問題,可是必須當心確保不能把這種信息泄漏給攻擊者(好比經過時間側通道,log文件,或者其它通道等泄漏)。

RSAES-OAEP 加密體制,更能抵抗 Bleichenbacher 發表的攻擊,然而,爲了和早期的TLS版本最大程度保持兼容,TLS 仍然規定使用 RSAES-PKCS1-v1_5 體制。只要遵照了上面列出的建議,目前尚未 Bleichenbacher 的變化形式能攻破 TLS 。

實現的時候要注意:公鑰加密的數據用 字節數組 <0..216-1> 的形式表示。所以,ClientKeyExchange中的 RSA加密的PreMasterSecret 前面有2個字節用來表示長度。這2個字節在使用RSA作密鑰協商時,是冗餘的,由於此時 EncryptedPreMasterSecret 是 ClientKeyExchange 中的惟一字段,所以能夠無歧義地得出 EncryptedPreMasterSecret 的長度。所以更早的 SSLv3 規範沒有明確規定 public-key-encrypted 數據的編碼格式,所以有一些SSLv3的實現沒有包含 長度字段,這些實現直接把 RSA 加密的數據放入了 ClientKeyExchange消息裏面。 TLS規範要求 EncryptedPreMasterSecret 字段包含長度字段。所以得出的結果會和一些 SSLv3 的實現不兼容。實現者從 SSLv3 升級到 TLS 時,必須修改本身的實現,以接受而且生成帶長度的格式。若是一個實現要同時兼容 SSLv3 和 TLS,那就應該根據協議版本肯定本身的行爲。

注意:根據 Boneh 等在2003年USENIX Security Symposium上發表的論文 「Remote timing attacks are practical」,針對 TLS RSA密鑰交換的遠程時間側通道攻擊,是實際可行的,起碼當客戶端和服務器在同一個LAN裏時是可行的。所以,使用靜態 RSA 密鑰的實現,必須使用 RSA blinding,或者Boneh論文中提到的,其餘抵抗時間側通道攻擊的技術。

openssl中的RSA blinding,參見:http://linux.die.net/man/3/rsa_blinding_on

(2). 客戶端 Diffie-Hellman 公鑰

這條消息把客戶端的 Diffie-Hellman 公鑰 ( Yc ) 發送給服務器。

Yc的編碼方式由 PublicValueEncoding 決定。

消息的結構:

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 
 enum { implicit, explicit } PublicValueEncoding;   implicit  若是客戶端已經發送了一個包含合適的 DH 公鑰的證書(即 fixed_dh 客戶端認證方式),那麼Yc已經隱式包含了,不須要再發送。這種狀況下,ClientKeyExchange消息必須發送,而且必須是空的。   explicit  表示Yc須要發送。   struct {  select (PublicValueEncoding) {  case implicit: struct { };  case explicit: opaque dh_Yc<1..2^16-1>;  } dh_public;  } ClientDiffieHellmanPublic;   dh_Yc  客戶端的 Diffie-Hellman 公鑰 Yc. 

(3). 客戶端 EC Diffie-Hellman 公鑰

1
2 3 4 5 6 
 struct {  select (PublicValueEncoding) {  case implicit: struct { };  case explicit: ECPoint ecdh_Yc;  } ecdh_public;  } ClientECDiffieHellmanPublic; 

Diffie-Hellman 推廣到橢圓曲線羣上,就是 EC Diffie-Hellman ,簡稱 ECDH,其它的計算,和通常的 DH 計算相似。

ECDH 是目前最重要的密鑰協商算法 http://vincent.bernat.im/en/blog/2011-ssl-perfect-forward-secrecy.html

11. handshake — Cerificate Verify

當須要作客戶端認證時,客戶端發送CertificateVerify消息,來證實本身確實擁有客戶端證書的私鑰。這條消息僅僅在客戶端證書有簽名能力的狀況下發送(就是說,除了含有固定 Diffie-Hellman 參數的證書之外的證書)。CertificateVerify必須緊跟在ClientKeyExchange以後發送。

消息結構: Structure of this message:

1
2 3 4 5 
 struct {  digitally-signed struct {  opaque handshake_messages[handshake_messages_length];  }  } CertificateVerify; 

此處, handshake_messages 表示全部發送或者接收的握手消息,從client hello開始,一直到CertificateVerify以前的全部消息,包括handshake消息的type和length字段,這是以前全部握手結構體的拼接。要注意,這要求雙方在握手過程當中,都得緩存全部消息,或者在握手過程當中,用每一種可能的hash算法計算到CeritificateVerify爲止的hash值。

signature中用的hash和簽名算法必須是 CertificateRequest 的 supported_signature_algorithms 中的某一種。另外,hash和簽名算法必須和客戶端的證書的算法兼容。 RSA公鑰可能被用於任何容許的hash函數,只要遵循證書中的限制。

12. handshake — Finished

在 ChangeCipherSpec 消息以後,應該當即發送 Finished 消息,來確認密鑰交換和認證過程已經成功了。ChangeCipherSpec 必須在其它握手消息和 Finished 消息之間。

Finished 消息是第一條用剛剛協商出來的參數保護的消息。接收方必須確認Finished消息的內容是正確的。一旦某一方發送了,而且確認了對端發來的Finished消息,就能夠開始在鏈接上發送和接收應用數據了。

消息結構:

1
2 3 4 5 6 7 8 9 10 11 
 struct {  opaque verify_data[verify_data_length];  } Finished;   verify_data  PRF(master_secret, finished_label,Hash(handshake_messages))  [0..verify_data_length-1];   finished_label  對客戶端發的Finished消息來講,固定是字符串 "client finished".  對服務器發的Finished消息來講,固定是字符串 "server finished". 

Hash表示握手消息的hash。hash函數是前文 PRF 的hash 函數。或者 CipherSuite 規定的用於 Finished 計算的hash函數。

在TLS的以前版本中,verify_data 老是 12 字節。在TLS 1.2中,這取決於CipherSuite。若是CipherSuite沒有顯式規定 verify_data_length ,就當成12字節處理。未來的CipherSuite可能會規定別的長度,可是不能小於12字節。

Finished 消息必須跟在 ChangeCipherSpec 消息以後,若是順序錯亂,就是 fatal error.

handshake_message 的內容包含從 ClientHello開始,直到 本條Finished以前的全部消息,只包含handshake層的消息體,不包含record層的幾個消息頭字段。包括CertificateVerify 消息。同時,對客戶端和服務器來講,handshake_message 的內容不一樣, 後發送者必須包含前發送者的 Finished 消息。

注意:ChangeCipherSpec 消息,alert,和其它的record 類型不是握手消息,不包含在 hash計算中。同時,HelloRequest 消息也不算在內。

13. handshake — NewSessionTicket

SessionTicket 定義在 RFC5077 標準裏面,2008年發佈。

SessionTicket是一種不須要服務器端狀態的,恢復TLS session的方式。 SessionTicket能夠用於任何CipherSuite。 TLS 1.0, TLS 1.1, TLS 1.2 都適用。

在下面這些場景下,尤爲有用:

用戶量巨大,session id的方式耗費服務器內存過多 服務器但願長時間緩存session 服務器有多臺,不但願服務器間有共享狀態 服務器內存不足 客戶端在 ClientHello中設置一個 SessionTicket 擴展來標識本身支持 SessionTicket。若是客戶端本地沒有存以前收到的ticket,就把這個擴展設爲空。

若是服務器但願使用 SessionTicket 機制,服務器把本地的 session 狀態存入一個ticket中,ticket會被加密,並被MAC保護,沒法篡改,加密和算MAC用的key只有服務器知道。 加密並MAC過的ticket用 NewSessionTicket 消息分發給客戶端,NewSessionTicket 消息應該在 ChangeCipherSpec 消息以前,在服務器驗證經過客戶端的Finished消息以後發送。

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
 Client Server  ClientHello  (empty SessionTicket extension)------->  ServerHello  (empty SessionTicket extension)  Certificate*  ServerKeyExchange*  CertificateRequest*  <-------- ServerHelloDone  Certificate*  ClientKeyExchange  CertificateVerify*  [ChangeCipherSpec]  Finished -------->  NewSessionTicket  [ChangeCipherSpec]  <-------- Finished  Application Data <-------> Application Data  Figure 1: Message flow for full handshake issuing new session ticket 

客戶端把收到的ticket和master secret等其它與當前session有關的參數一塊兒,緩存起來。 單客戶端但願恢復會話時,就把ticket包含在 ClientHello 的 SessionTicket 擴展中發給服務器。 服務器收到後,解密ticket,算MAC確認ticket沒有被篡改過,而後從解密的內容裏面,獲取session 狀態,用來恢復會話。若是服務器成功地驗證了ticket,能夠在 ServerHello 以後返回一個 NewSessionTicket 消息來更新ticket。

顯然,這種狀況下,相比完整握手,能夠省掉1個RTT。以下圖:

1
2 3 4 5 6 7 8 9 10 11 12 13 
 Client Server  ClientHello  (SessionTicket extension) -------->  ServerHello  (empty SessionTicket extension)  NewSessionTicket  [ChangeCipherSpec]  <-------- Finished  [ChangeCipherSpec]  Finished -------->  Application Data <-------> Application Data  Figure 2: Message flow for abbreviated handshake using new  session ticket 

若是服務器不能,或者不想使用客戶端發來的ticket,那服務器能夠忽略ticket,啓動一個完整的握手流程。

若是服務器此時不但願下發新的ticket,那就能夠不回覆 SessionTicket 擴展,或者不回覆 NewSessionTicket 消息。 此時除了 ClientHello裏面的 SessionTicket擴展,就和通常的TLS流程同樣了。

若是服務器拒絕收到的ticket,服務器可能仍然但願在完整的握手以後,下發新的ticket。 此時流程和全新 ticket 生成下發的區別,就是ClientHello的SessionTicket不是空的。

NewSessionTicket 消息 服務器在握手過程當中,發ChangeCipherSpec以前發送NewSessionTicket消息。 若是服務器在ServerHello中包含了一個SessionTicket擴展,那就必須發送NewSessionTicket消息。 若是服務器沒有包含SessionTicket擴展,那絕對不能發送NewSessionTicket消息。 若是服務器在包含了SessionTicket擴展以後,不想發送ticket,那能夠發送一個長度爲0的NewSessionTicket消息。

在完整握手的狀況下,客戶端必須在確認服務器的Finished消息正確以後,才能認爲NewSessionTicket 裏面的ticket合法。

服務器能夠NewSessionTicket消息中更新 ticket。

ticket_lifetime_hint 字段包含一個服務器的提示,提示客戶端本ticket應該存多長時間就失效。單位是秒,網絡字節序。當時間到期時,客戶端應該刪掉ticket和關聯的狀態。客戶端也能夠提早刪除。服務器端也能夠提早認爲ticket失效。

1
2 3 4 
 struct {  uint32 ticket_lifetime_hint;  opaque ticket<0..2^16-1>;  } NewSessionTicket; 

SessionTicket 和 Session ID 之間的關係比較繁瑣。感興趣的自行去看RFC吧。

對於客戶端來講,ticket就是一塊二進制buffer,客戶端並無論裏面的內容。因此ticket具體怎麼加密加MAC服務器能夠隨心所欲,無需顧及客戶端的感覺。

RFC5077中推薦了一種ticket的加密保護方法: 服務器使用2個key,一個 aes-128-cbc的key,一個 HMAC-SHA-256 的key。

ticket的格式像這樣:

1
2 3 4 5 6 
 struct {  opaque key_name[16];  opaque iv[16];  opaque encrypted_state<0..2^16-1>;  opaque mac[32];  } ticket; 

其中,key_name 用來標識一組key,這樣服務器端就能夠使用多組key。

加密過程,首先隨機生成IV,而後用 aes-128-cbc 加密 session 的序列化結果, 而後用 HMAC-SHA-256 對 key_name,IV,encrypted_data 的長度(2字節),encrypted_data 計算MAC。 最好把各個字段填入上面ticket結構體。 顯然,此處是 Encrypt-then-MAC的方式,是最安全的。

實際在openssl 中的session,用asn1格式序列化保存了下面這些字段:

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 
 typedef struct ssl_session_asn1_st {  ASN1_INTEGER version;  ASN1_INTEGER ssl_version;  ASN1_OCTET_STRING cipher;  ASN1_OCTET_STRING master_key;  ASN1_OCTET_STRING session_id;  ASN1_OCTET_STRING session_id_context;  ASN1_INTEGER time;  ASN1_INTEGER timeout;  ASN1_INTEGER verify_result;  ASN1_OCTET_STRING tlsext_hostname;  ASN1_INTEGER tlsext_tick_lifetime;  ASN1_OCTET_STRING tlsext_tick;  } SSL_SESSION_ASN1; 

6. ChangeCipherSpec 協議

ChangeCipherSpec用來通知對端,開始啓用協商好的Connection State作對稱加密,內容只有1個字節。 這個協議是冗餘的,在TLS 1.3裏面直接被刪除了。

changeCipherSpec協議抓包:

7. Alert 協議

一種返回碼機制,簡單

enum { warning(1), fatal(2), (255) } AlertLevel;

  struct {
      AlertLevel level;
      AlertDescription description;
  } Alert;

其中level是等級,不一樣等級要求不一樣的處理。

其中有一種:close_notify,用來通知對端,我不會再發送更多數據了。這個可讓對端主動close fd,這樣能夠減小我方tcp timewait狀態的socket 量。

alert協議:

8. application data協議

application data協議,就是把應用數據直接輸入record層,作分段,算MAC,加密,傳輸。 抓包舉例以下:

8. TLS協議的安全分析

安全分析,重中之重,也是你們最關心的。

安全分析的第一步是創建攻擊模型,TLS的攻擊模式是: * 攻擊者有充足的計算資源 * 攻擊者沒法獲得私鑰,沒法獲得客戶端和服務器內存裏面的密鑰等保密信息 * 攻擊者能夠抓包,修改包,刪除包,重放包,篡改包。

這個模型其實就是密碼學裏面通常假定的攻擊模型。

好了,在這個模型下,TLS的安全性分析以下:

1. 認證和密鑰交換 的安全性

TLS有三種認證模式:雙方認證,服務器認證,無認證。 只要包含了有服務器端認證,就能夠免疫 man-in-the-middle 攻擊。可是徹底匿名的會話是能夠被 MITM 攻擊的。匿名的服務器不能認證客戶端。

密鑰交換的目的,是產生一個只有通訊雙方知道的共享密鑰 pre_master_secret 。pre_master_secret 用來生成 master_secret 。 master_secret 用來生成 Finished 消息,加密key,和MAC key。經過發送正確的Finished消息,雙方能夠證實本身知道正確的 pre_master_key。

1. 匿名密鑰交換

匿名密鑰交換是一種歷史遺留的不安全方式。 匿名密鑰交換缺失認證(Authentication),因此絕大多數場景下,咱們應該禁用這種方式。

2. RSA 密鑰交換和認證

當使用RSA的時候,合併了密鑰交換 和 服務器端認證。 RSA公鑰包含在服務器證書中。要注意的是,一旦服務器證書的RSA私鑰泄露,以前用該證書保護的全部流量都會變成能夠破解的,即沒有前向安全性(Perfect Forward Secrecy)。 須要前向安全性的TLS用戶,應該使用 DHE 或者 EC TLS users desiring Perfect Forward Secrecy should use DHE 類的CcipherSuite。這樣,若是私鑰泄露,只須要更換私鑰和證書就行,不會有更大的損失。

RSA密鑰交換和認證的安全性基於,在驗證了服務器的證書以後,客戶端用服務器的公鑰加密一個 pre_master_secret 。成功地解密 pre_master_secret 併產生正確地 Finished 消息以後,就能夠確信服務器擁有證書對應的私鑰。

若是使用了客戶端認證,經過 CertificateVerify 消息來認證客戶端。客戶端會簽署一個以前全部握手消息的hash值,這些握手消息包括 服務器的證書,ServerHello.random 。其中服務器證書確保客戶端簽署了和本服務器有關的綁定(即不能重放和別的服務器的握手),ServerHello.random 確保簽名和當前握手流程綁定(即不能重放)。

3. Diffie-Hellman 密鑰交換和認證

當使用 DH 密鑰交換的時候,服務器:

  1. 或者發送包含固定 DH參數的證書
  2. 或者發送一組臨時DH參數,並用 ECDSA/RSA/DSA 證書的私鑰簽署。並且在簽署以前,臨時DH參數和 hello.random 都參與hash計算,來確保攻擊者不能重放老的簽名值。

不管如何,客戶端均可以經過驗證證書,或者驗證簽名,來確保收到的DH參數確實來自真正的服務器。

若是客戶端有一個包含固定 Diffie-Hellman 參數的證書,則證書包含完成密鑰交換所需的參數。要注意的是,這種狀況下,客戶端和服務器每次都會協商出相同的 DH 結果(就是 pre_master_secret)。 爲了儘量減小 pre_master_secret 存在在內存裏面的時間,當再也不須要的時候,儘快將其清除,pre_master_secret 應該儘早轉換成 master_secret 的形式。 爲了進行密鑰交換,客戶端發送的 Diffie-Hellman 參數必須和服務器發送的兼容。

若是客戶端有一個標準的 DSA 或者 RSA 證書,或者 客戶端沒有被認證,那麼客戶端在ClientKeyExchange中發送一組臨時參數,或者可選地發送一個CertificateVerify消息來證實本身的身份。

若是相同的 DH 密鑰對,被屢次用於握手協商,無論是因爲客戶端或服務器使用了固定DH密鑰的證書,仍是服務器在重用 DH 密鑰,都必須當心避免 small subgroup 攻擊。實現都必須遵循 rfc2785 中的標準。

最簡單避免 small subgroup 攻擊的方法是使用一種 DHE CipherSuite,而且每次都握手都生成一個新的 DH 私鑰 X。若是選擇了一個合適的base(例如2),gX mod p 的計算能夠很是快,於是性能開銷會最小化。而且每次都使用一個新的DH密鑰,能夠提供前向安全性。當使用 DHE 類的CipherSuite時,實現必須每次握手都生成一個全新的DH私鑰(即 X )。

因爲TLS容許服務器提供任意的 DH 羣,客戶端必須確認服務器提供的DH 羣的大小適合本地策略。 客戶端必須確認 DH 公共指數有足夠的大小。 若是DH羣已知的話,客戶端作簡單比對就好了,所以服務器能夠使用一個已知的羣,來方便客戶端的檢查。

2. 版本回退攻擊

因爲 TLS 歷史上出現過多個版本,服務器端實現可能會兼容多個版本的協議,而像 SSL 2.0 這樣的版本是有嚴重安全問題的,所以攻擊者可能會嘗試誘騙客戶端和服務器,來使TLS鏈接回退到 SSL 2.0這種老版本。

TLS 對此的解決辦法,就是PreMasterSecret裏面包含版本號。

3. 針對握手過程的攻擊

攻擊者可能會嘗試影響握手過程,來使雙方選擇不安全的加密算法。

對這種攻擊的解決辦法是,若是握手消息被篡改了,那麼在Finished消息裏,客戶端和服務器都會計算 握手消息的hash,若是攻擊者篡改了握手消息,那麼雙方得出的hash就不同,這樣Finished消息就會驗證不過。就會發現攻擊。

4. 針對 Resuming Sessions 的攻擊

當使用 session resuming的時候,會產生新的 ClientHello.random 和 ServerHello.random ,並和session的 master_secret 一同被hash。只要master_secret沒有泄漏,而且PRF中用來生成加密key和MAC key的hash算法是安全的,鏈接就是安全的,而且獨立於前一個鏈接(被恢復的前一個鏈接)。

只有在客戶端和服務器都贊成的狀況下,纔會作session resuming。只要有任意一方懷疑 session 泄漏,或者證書過時/被吊銷,就能夠要求對端作完整的握手。 一個session的生命週期建議定位24小時。因爲若是攻擊者得到了 master_secret 就能夠在session ID過時以前假裝成被泄漏者,因此要加一個生命期限制。 運行在不安全環境的應用程序,不該該把session ID寫入持久存儲。

5. 針對應用數據保護的攻擊

master_secret 和 ClientHello.random 及 ServerHello.random 一塊兒作 hash,來生成每一個鏈接惟一的加密key和MAC key(就算是session resuming獲得的鏈接,也是不一樣的)。

在CBC和stream cipher的狀況下, 發送出去的數據,在發送前用MAC保護,來避免消息重放,避免篡改。 MAC根據 MAC key,序列號,消息長度,消息內容,固定字符串算出。 消息類型字段(content type)是必須的,來確保握手消息,ChangeCipherSpec消息,應用數據消息不會被混淆。 序列號用來確保刪除包或者打亂包順序的攻擊沒法得逞。 因爲序列號是64位的,能夠認爲不會迴繞。 從一方發給對端的消息,不能被插入對端發來的字節流中,這是用於兩端使用不一樣的 MAC key。 相似地,server write key 和 client write key相互獨立。所以stream cipher的key只使用了一次,避免了相似問題。

若是攻擊者獲取了加密key,那麼就能夠解密全部的消息。 相似地,泄漏MAC key,會使攻擊者能夠篡改消息。

AEAD就簡單了。

6. 顯式 IV的安全性

如前文所述,TLS 1.0是把前一條消息的最後一個block,看成下一條消息的第一個IV的,這促成了2004年公開的 BEAST 攻擊,後來就改爲這種顯式IV的更安全的方式了。

7. 加密和MAC組合模式的安全性

前文介紹CBC和AEAD時已有分析,此處略過。

8. DOS 攻擊下的安全性

TLS容易遭受某些 DoS 攻擊。例如,攻擊者建立不少TCP鏈接,就可讓服務器忙於作 RSA 解密計算。然而,因爲TLS運行在TCP之上,只要操做系統TCP棧的 SYN-ACK裏seqnum是隨機的,攻擊者就沒法隱藏本身的ip,這樣就能夠和通常的TCP鏈接同樣作DOS防護。

因爲TLS運行在TCP上,每一個獨立的鏈接均可能遭受一系列DOS攻擊。尤爲是,攻擊者能夠僞造RST包,來中斷一條TCP+TLS鏈接。或者僞造部分TLS記錄,致使鏈接阻塞掛起。不過這些攻擊都是任何TCP協議都有問題,不是TLS特有的。

9.Session Ticket 的安全分析

Ticket必須: 1.有MAC (即 authenticated,不可篡改),2.加密(即保密)。

下面分析在各類攻擊方法下的安全性。

1. 無效的Session

TLS協議要求當發現錯誤的時候,把TLS session變爲無效。

這不會影響到ticket的安全性。

2. 竊取 Tickets

攻擊者或者中間人,可能會竊取到ticket,而且嘗試用來和server創建會話。 然而,因爲ticket是加密過的,而且攻擊者不知道密鑰,竊取到的ticket沒法使攻擊者恢復會話。 TLS服務器必須使用強加密和MAC算法,來保護ticket。

3. 僞造 Tickets

一個惡意用戶可能會僞造,或者篡改一個ticket,來恢復一個會話,來延長ticket的生命週期,或者僞裝成另外一個用戶。

然而,因爲服務器使用了強的校驗保護算法,好比帶密碼的 HMAC-SHA1 ,所以沒法得逞。

4. DoS 攻擊

推薦ticket 格式中的 key_name 字段幫助服務器有效地拒毫不是本身簽發的票據。 所以,一個攻擊者可能發送大量的ticket,讓服務器忙於驗證ticket。 然而,只要服務器使用了高效的加密和MAC算法,就不會有問題。(現實中,加密和MAC算法效率都極高,這根本不是問題)

5. 加密 Ticket 的key 的管理

加密ticket的key的管理,推薦的作法:

  • key 應該用密碼學安全的隨機數生成器生成,按照RFC4086。
  • key 和加密算法最少應該是 128 比特安全程度的。
  • key 除了加密和解密ticket之外,不該該有其餘用途。
  • key 應該按期更換
  • 當ticket格式更換,或者算法更換時,應該更換key

6. Ticket 的有效期

TLS服務器控制ticket的生命週期。服務器根據配置來決定能夠接受的ticket生命週期。 ticket的生命週期可能會長於24小時。TLS客戶端可能會接受到一個ticket生命週期的提示,固然,客戶端本地的策略最終決定ticket保存多久。

7. 其餘的 Ticket 格式和分發方法

若是沒使用推薦的ticket格式,那必須當心地分析方案的安全性。尤爲是,若是保密數據好比保密密鑰傳輸給了客戶端,那必須用加密方式傳輸,來防止泄露或篡改。

8. Identity Privacy, Anonymity, and Unlinkability

ticket的加密和加MAC,就保證了敏感信息不會泄露。

因爲在ticket解密以前的TLS握手,沒法隱藏客戶端的特徵,所以中間人可能根據相同的ticket被複用,發現相同的ticket屬於相同的用戶。TLS對這種狀況不提供保證。

9. TLS擴展:

https://tools.ietf.org/html/rfc6066

幾個比較重要的TLS擴展:

  1. Server Name Indication (SNI) 因爲在SNI提出以前,tls握手過程當中沒有字段標明客戶端但願鏈接服務器端的哪一個域名,這樣若是一個服務器端口上有多個域名,服務器就沒法給出正確的證書。隨着ipv4地址空間緊張,這個問題愈加突出。所以提出了SNI。

  2. TLSEXT_ALPN 上層協議協商,就是在握手過程當中,標明TLS裏面是什麼協議,例如 http2就是 h2

  3. Maximum Fragment Length Negotiation 主要用於嵌入式環境,須要客戶端發送。

  4. Session Ticket Session Ticket,就是把session cache加密後存入客戶端,這樣服務器端就不須要任何存儲了。

  5. TLSEXT_SAFE_RENEGOTIATION 重協商

  6. Certificate Status Request: OCSP ,OCSP 主要是爲了減小客戶端查詢 證書撤銷列表(Ceritificate Revoke List)的網絡調用,而提出的。

10. TLS的配套:PKI體系

1. X.509 證書

X.509是PKI的一個標準,其中內容包括:

  • 公鑰證書
  • 證書撤銷列表,CRL
  • 證書路徑驗證算法(CA/證書 鏈的格式)

X.509使用ASN.1語法作序列化/反序列化

ASN1 就是一個數據序列化/反序列化格式,跟 protobuf 差很少,能夠算做競爭對手。

DER 就是用 ASN1 序列化某些數據結構的格式。

PEM 就是 DER作base64,加上一些其餘字段。

證書鏈,以一個或多個CA證書開頭的證書的列表,其中:

  • 每個證書的 Issuer 和下一個證書的 Subject 相同
  • 每個證書都被下一個證書的私鑰簽署
  • 最後一個證書是 根證書(「root CA」),在TLS握手中不會被髮送

證書裏面包含公鑰,和其它一些字段(好比證書用途,有效期,簽發者等等) x509.v3證書的字段: 

mozilla的ca證書列表 https://www.mozilla.org/en-US/about/governance/policies/security-group/certs/

https://www.apple.com/certificateauthority/ca_program.html 蘋果對CA提的要求:

1.CA必須取得完整的 WebTrust for Certification Authorities audit (WebTrust CA審計:http://www.webtrust.org/%EF%BC%89 2.你的root CA證書必須爲apple平臺的用戶提供普遍的商業價值。例如,一個組織內內部使用的證書不能被接受爲root證書。 3.你籤的證書必須含有能夠公開訪問的CRL地址。

Webtrust審計介紹: Webtrust是由世界兩大著名註冊會計師協會(美國註冊會計師協會,AICPA和加拿大註冊會計師協會,CICA)制定的安全審計標準,主要對申請對象的系統及業務運做邏輯安全性、保密性等共計七項內容進行近乎嚴苛的審查和鑑證。只有經過Webtrust國際安全審計認證,纔有可能成爲全球主流瀏覽器根信任的證書籤發機構。

https://www.geotrust.com/ 的網站上右下角,有個圖標:  點開就能夠看到 KPMG 對 geotrust 公司的 webtrust 審計報告: https://cert.webtrust.org/SealFile?seal=1567&file=pdf

2011年 荷蘭CA公司DigiNotar頒發假google,Facebook,微軟證書被發現,後發現被入侵,致使該公司破產。 http://www.cnbeta.com/articles/154375.htm

https://news.ycombinator.com/item?id=530600 CA公司簽署一個證書的成本是0 。 CA公司的主要成本構成:審覈 ,驗證CSR成本,支持成本,法律成本(保險費用,擔保費用)。 要進入各個瀏覽器的根證書列表,CA公司每一年必須過 WebTrust 年度審計,是很大的開銷。 一些瀏覽器廠商還會對植入根證書列表的CA收費。 基礎設施開銷,CRL 和 OCSP 服務器成本。 驗證CSR:就是提交證書申請後,CA要作多項驗證,越是高級的證書(好比EV)驗證越麻煩。不固定開銷,有些要花費不少人力和時間來完成。 法律開銷:CA公司得買保險,保險費跑不了。 CA鏈費用:新開的CA公司要等5-10年,纔會被廣泛信任,才能普遍進入根證書鏈。要想加快點,就得給別的大牌CA公司掏錢,買次級證書。

2.現有PKI體系暴露出的問題

http://googleonlinesecurity.blogspot.com/2015/03/maintaining-digital-certificate-security.html

https://blog.mozilla.org/security/2015/04/02/distrusting-new-cnnic-certificates/

https://www.dfn-cert.de/dokumente/workshop/2013/FolienSmith.pdf

https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf

解決方案:

1. public key pin

https://developer.mozilla.org/en-US/docs/Web/Security/Public_Key_Pinning

2. HSTS

http://www.chromium.org/hsts 收錄進chrome的默認HSTS列表:https://hstspreload.appspot.com/

11. TLS協議歷史上出現過的漏洞,密碼學常見陷阱

1. TLS的漏洞

漏洞分析很耗時間,這裏總結一些資料,有興趣的本身看吧。

雖然TLS的設計已經儘量的嚴密,可是隨着技術進步的滾滾車輪,歷史上仍是出現過不少漏洞, 能夠參看這個rfc,作了總結:

Summarizing Known Attacks on Transport Layer Security (TLS) and Datagram TLS (DTLS)

還有這個文檔: The Sorry State Of SSL

http://hyperelliptic.org/internetcrypto/OpenSSLPresentation.pdf

TLS 協議最近一些年被爆出過的設計缺陷,尤爲是在用的最多的 AES-CBC 和 RC4 上。

AES-CBC 發現了: 1. padding oracle 攻擊 2. BEAST 攻擊 3. Lucky 13 攻擊 4. TIME 攻擊 5. POODLE攻擊

2013 年, AlFardan發表了對 RC4 的一個攻擊分析,展現如何恢復 RC4 傳輸的鏈接上的數據。這種恢復攻擊利用了RC4的一些已知弱點,例如RC4最初的一些字節的顯著統計特徵。

最近幾年,TLS的代碼實現引發了安全研究者的關注,這致使了新漏洞不斷髮現。 2014年,OpenSSL庫爆出了好幾個漏洞,例如 HeartBleed,還有 CVE-2014-6321 ( Microsoft SChannel 的實現漏洞)等.

TLS的問題:

• 不少問題是因爲TLS使用了一些「史前時代」的密碼學算法(– Eric Rescorla) • CBC 和 Mac-Pad-then-Encrypt • RSA-PKCS#1v1.5 的 RSA padding • RC4 的任何使用 • 很蠢的設計:臨時 RSA 密鑰協商,GOST 類CipherSuite,Snap Start 等 • 可怕的向後兼容要求,致使遲遲不能廢棄一些老算法。

The Most Dangerous Code in the World: Validating SSL Certificates in Non-Browser Software

http://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html

https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf

Why Eve and Mallory Love Android An Analysis of Android SSL (In)Security

2. 密碼學常見陷阱

先舉幾個加密協議被破解的例子,給你們助興:

網上有一些資料,有興趣本身看吧:

密碼學常見應用錯誤 http://security.stackexchange.com/questions/2202/lessons-learned-and-misconceptions-regarding-encryption-and-cryptology

  • 不要本身發明加密算法。Don’t roll your own crypto.
  • 不要使用不帶MAC的加密 Don’t use encryption without message authentication.
  • 在拼接多個字符串作hash以前,要特別當心 Be careful when concatenating multiple strings, before hashing.
  • 要特別當心使用的隨機數生成器,確保有足夠的熵 Make sure you seed random number generators with enough entropy.
  • 不要重用 nonce 或者。IV Don’t reuse nonces or IVs.
  • 加密和MAC不要使用一樣的key,非對稱加密和簽名不要使用相同的key Don’t use the same key for both encryption and authentication. Don’t use the same key for both encryption and signing.
  • 不要使用ECB模式作對稱加密 Don’t use a block cipher with ECB for symmetric encryption
  • Kerckhoffs定律,一個密碼學系統的安全性必須創建在密碼保密的基礎上,其餘都是公開的。Kerckhoffs’s principle: A cryptosystem should be secure even if everything about the system, except the key, is public knowledge
  • 不要把用戶產生的密碼做爲加密的key。Try to avoid using passwords as encryption keys.
  • 在密碼學協議中,任何2條消息的密文都不該該同樣。In a cryptographic protocol: Make every authenticated message recognisable: no two messages should look the same
  • 不要把相同的key用在通訊的2個方向上。Don’t use the same key in both directions.
  • 不要使用不安全的key長度。Don’t use insecure key lengths.

13. 下一代TLS: TLS 1.3

tls 1.3的草案在 http://tlswg.github.io/tls13-spec/ 相比tls 1.2, 1.3改動巨大,這些改動對加密通訊協議的通常設計也有重要啓發。

TLS 1.3 的改動 值得關注的重大改進有:

  • 0-RTT支持
  • 1-RTT握手支持
  • 改成使用HKDF作密鑰拓展
  • 完全禁止RC4
  • 完全禁止壓縮
  • 完全禁止aead之外的其餘算法
  • 去除aead的顯式IV
  • 去除了AEAD的AD中的長度字段
  • 去除ChangeCipherSpec
  • 去除重協商
  • 去除靜態RSA和DH密鑰協商

移動互聯網興起以後,rtt延遲變得更重要,能夠看到,tls 1.3 的各項改進,主要就是針對移動互聯網場景的。

TLS 1.3 去掉了 ChangeCipherSpec ,這樣record之上有3個協議:handshake,alert,application data

1. record層的密碼學保護的改動

因爲只保留了aead,因此不須要MAC key了。

aead的具體參數用法也有調整,前文有。

KDF 換成了標準的HKDF,有2種 tls_kdf_sha256, tls_kdf_sha384

2.handshake協議的改動

鑑於session ticket如此之好用,簡直人見人愛,因此 TLS 1.3 直接把session ticket內置了,並更名叫 PSK

要注意的是,此 PSK 和 tls 1.2中一個很生僻的psk(見 rfc4279 )並非一回事。

綜合考慮了 session resuming ,session ticket後, TLS 1.3 提出了3種handshake模式:

  1. Diffie-Hellman ( 包含 DH 和 ECDH 兩種,下文說到 ECDH 的地方,請自行腦補成 「ECDH/DH」).
  2. A pre-shared symmetric key (PSK) ,預先共享的對稱密鑰,此處用統一的模型來處理session resuming 和 rfc4279的psk
  3. A combination of a symmetric key and Diffie-Hellman ,前二者合體

3.1-RTT 握手

首先,TLS 1.2 的握手有2個rtt,第一個rtt是 ClientHello/ServerHello,第二個rtt是ClientKeyExchange/ServerKeyExchange, 之因此KeyExchange要放在第二個rtt,是因爲tls1.2要支持多種密鑰交換算法,和各類不一樣參數(好比 DH仍是ECDH仍是RSA,ECDHE用什麼曲線,DH用什麼羣生成元,用什麼模數,等等),這些算法和參數都依賴第一個rtt去協商出來, TLS1.3大刀闊斧地砍掉了各類自定義DH羣,砍掉了ECDH的自定義曲線,砍掉了RSA協商,密鑰協商的算法只剩下很少幾個,並且其實你們實際應用中基本都用 ECDH P-256,也沒啥人用別的,因此乾脆讓客戶端緩存服務器上一次用的是啥協商算法,把 KeyExchange直接和入第一個rtt,客戶端在第一個rtt裏直接就用緩存的這個算法發KeyExchange的公鑰,若是服務器發現客戶端發上來的算法不對,那麼再告訴正確的,讓客戶端重試好了。 這樣,就引入了 HelloRetryRequest 這個消息。

這樣,基本沒有反作用,就能夠降到 1-RTT。 這是TLS 1.3 的完整握手。

顯然,若是一個協議只有一種密鑰協商算法,好比定死爲 ECDH P-256,那必定能夠作到 1-RTT

4. 有反作用的 0-RTT握手

0-RTT應該是受Google的QUIC協議的啓發, 若是服務器把本身的 ECDH 公鑰長期緩存在客戶端,那麼客戶端就能夠用緩存裏的ECDHE公鑰,構造一個電子信封,在第一個RTT裏,直接就發送應用層數據了。 這個長期緩存在客戶端的ECDH公鑰,稱爲 半靜態 ECDH 公鑰( semi-static (EC)DH share ) ECDH公鑰經過 ServerConfiguration 消息發送給客戶端。

這個0-rtt優化是有反作用的:

  1. 0-RTT發送的應用數據沒有前向安全性。
  2. 跨鏈接能夠重放0-RTT裏的應用數據(任何服務器端無共享狀態的協議,都沒法作到跨鏈接防重放)
  3. 若是服務器端 半靜態 ECDH公鑰對應的私鑰泄露了,攻擊者就能夠假裝成客戶端隨意篡改數據了。

服務器在 ServerConfiguration 消息裏把半靜態 ECDH 公鑰發送給客戶端。 ServerConfiguration 值得關注一下:

1
2 3 4 5 6 7 8 
 struct {  opaque configuration_id<1..2^16-1>;  uint32 expiration_date;  NamedGroup group;  opaque server_key<1..2^16-1>;  EarlyDataType early_data_type;  ConfigurationExtension extensions<0..2^16-1>;  } ServerConfiguration; 

其中的 expiration_date 是本 ServerConfiguration 最後的有效期限。 這個值絕對不容許大於7天。 客戶端絕對不容許存儲 ServerConfiguration 大於7天,無論服務器怎麼填這個值。

0-RTT 中的應用數據,放在 EarlyDataIndication 中發送,

TLS 1.3 還特地給 EarlyDataIndication 定義了一種 ContentType : early_handshake (共四種 alert(21), handshake(22), application_data(23), early_handshake(25) )

5. Resumption 和 PSK

TLS 1.3 裏面,把session resumption/session ticket 恢復出來的key,和 psk (rfc4279), 統一在一個 handshake PSK 模式下處理。

PSK CipherSuite能夠 把PSK和ECDHE結合起來用,這樣是有前向安全性的。 也能夠僅僅使用PSK,這樣就沒有前向安全性。

6. Key Schedule 過程的改動

TLS 1.3 中,綜合考慮的 session ticket的各類狀況後,提出了 ES,SS 兩個概念,統一處理密鑰協商的各類狀況。 在各類handshake模式下,ES和SS的取值來源不一樣。

Ephemeral Secret (ES) : 每一個鏈接新鮮的 ECDHE 協商得出的值。凡是從 ES 得出的值,都是前向安全的(固然,在 PSK only模式下,不是前向安全的)。

Static Secret (SS) : 從靜態,或者半靜態key得出的值。例如psk,或者服務器的半靜態 ECDH 公鑰。

在各類 handshake 模式下:

  Key Exchange Static Secret (SS) Ephemeral Secret (ES)
  (EC)DHE (完整握手) Client ephemeral w/ server ephemeral Client ephemeral w/ server ephemeral|
  (EC)DHE (w/ 0-RTT) Client ephemeral w/ server static Client ephemeral w/ server ephemeral |
  PSK Pre-Shared Key Pre-shared key |
  PSK + (EC)DHE Pre-Shared Key Client ephemeral w/ server ephemeral |

如上表所示:

  1. 完整的 1-RTT握手的時候, SS 和 ES 都是用的 ephemeral key ,這樣是必定有前向安全性的。
  2. 使用 0-RTT 的握手的時候,使用客戶端的 ephemeral key 和 服務器端的半靜態 ECDH 公鑰生成 SS,
  3. 純 PSK,這種場景徹底沒有前向安全性,應該避免。
  4. PSK + ECDHE,這種場景比較有意思,SS是用的Pre-Shared Key,沒有前向安全性,ES 用的 ephemeral key,有前向安全性。

能夠看到,相比 TLS 1.2 的 session ticket,TLS 1.3 中 的 PSK + ECDHE,是結合了 ES 的,這樣就有了前向安全性,相對更安全。

和 TLS 1.2 不一樣的是,TLS 1.3的 master_secret 是使用 ES和SS 兩個得出的。

1
2 3 4 5 6 7 8 9 10 11 12 
 HKDF-Expand-Label(Secret, Label, HashValue, Length) =  HKDF-Expand(Secret, Label + '\0' + HashValue, Length)   1. xSS = HKDF(0, SS, "extractedSS", L)   2. xES = HKDF(0, ES, "extractedES", L)   3. master_secret = HKDF(xSS, xES, "master secret", L)   4. finished_secret = HKDF-Expand-Label(xSS,  "finished secret",  handshake_hash, L) 

Traffic Key Calculation

加密流量用的key,在 TLS 1.3 裏面稱爲 Traffic Key,因爲多引入了一種ContentType,在不一樣的ContentType下,Traffic Key 並不相同。 以下表:

  Record Type Secret Label Handshake Hash
  Early data xSS 「early data key expansion」 ClientHello |
  Handshake xES 「handshake key expansion」 ClientHello… ServerKeyShare|
  Application master secret 「application data key expansion」 All handshake messages but Finished|

要關注的是, Early Data 的 Traffic Key 是用 xSS 算出來的。也就是說,是用 Pre-Shared Key決定的。所以是沒有前向安全性的。

在一個TLS 鏈接中,到底是用哪一種 handshake 模式,是由 CipherSuite 協商決定的。

三. TLS協議的代碼實現

TLS的主要實現:

  • OpenSSL
  • libressl
  • boringssl(Google)
  • libressl
  • s2n(Amazon)
  • nss(Mozilla)
  • polarssl
  • botan
  • gnutls(gpl)
  • cyassl
  • go.crypto

openssl 的 tls 協議實現有 6W 行,libressl 3.68W行, polarssl 1.29 W行, Botan 1.13 W行

openssl是其中代碼最糟糕的(沒有之一)。 openssl提供了的api都太過於底層,api設計的也很費解,並且嚴重匱乏文檔。 請參考 《使人做嘔的OpenSSL》

不幸的是,OpenSSL是用的最普遍的,是事實上的標準。

boringssl Google’s OpenSSL fork by Adam Langley (@agl__)

https://github.com/sweis/crypto-might-not-suck

四. TLS協議的部署與優化

這個方面網上的文章仍是很多的,本文就簡略一點。

全站https時代正在到來!, 移動互聯網對人們生活的介入愈來愈深人,用戶愈來愈多的隱私數據和支付數據經過網絡傳輸,人們的隱私意識安全意識不斷提升;運營商流量劫持,強行插入廣告愈來愈引發反感。所以,各互聯網大廠都開始切換到https。

例如,2015年3月百度全站切換到https,百度運維部的介紹文章:《全站 https 時代的號角 : 大型網站的 https 實踐系列》

不久後淘寶切了全站https,https://www.taobao.com/ http://velocity.oreilly.com.cn/2015/index.php?func=session&id=8

國外:由Snowden爆料,美國人發現NSA在大範圍深度地監聽互聯網; 還有openssl連續被爆多個嚴重安全漏洞。以後近2年,各類加密通訊協議,軟件,項目開始熱門,各大廠商開始關注密碼協議,作數據加密,信息安全。(openssl資助,pfs被重視,)

Google的性能數據:

「In January this year (2010), Gmail switched to using HTTPS for everything by default. .. In order to do this we had to deploy no additional machines and no special hardware. On our production frontend machines, SSL accounts for < 1% of the CPU load, < 10 KB of memory per connection, and < 2% of network overhead…

If you stop reading now you only need to remember one thing: SSL is not computationally expensive any more.」

— Overclocking SSL blog post by Adam Langley (Googlehttps://www.imperialviolet.org/2010/06/25/overclocking-ssl.html )

google的優化: https://bit.ly/gottls https://www.imperialviolet.org/2010/06/25/overclocking-ssl.html https://istlsfastyet.com/ https://www.ssllabs.com/downloads/SSL_TLS_Deployment_Best_Practices.pdf http://chimera.labs.oreilly.com/books/1230000000545/ch04.html

baidu的經驗: http://op.baidu.com/2015/04/https-index/

aws的配置 http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-https-load-balancers.html

能夠參考byron以前給出的一個介紹nginx配置的文章 Nginx下配置高性能,高安全性的https TLS服務,本人提供售後諮詢服務,哈哈。

CipherSuite配置(Mozilla的權威配置) https://wiki.mozilla.org/Security/Server_Side_TLS

hardenedlinux的這個文檔:SSL/TLS部署最佳實踐v1.4: http://hardenedlinux.org/jekyll/update/2015/07/28/ssl-tls-deployment-1.4.html

全站切https,值得關注的一個點是cdn切https,若是cdn資源不使用cdn提供商的域名的話,以前會有私鑰必須得交給cdn提供商的安全風險,可是幸運的是cloudflare提出了keyless ssl方案,解決了這個問題 https://github.com/cloudflare/keyless,cdn切https應該能夠借鑑。

有時候咱們會用wireshark之類的工具抓包,來調試http協議,可是切換到https後,都變成二進制密文了,直接抓包是行不通了,那怎麼調試協議呢? 有個簡單的解決辦法:小技巧:如何在wireshark裏查看https的明文數據

五. 更多的加密通訊協議case:QUIC,iMessage,TextSecure, otr, ios HomeKit,libsodium

時間有限,下面有些協議就沒有作詳細的分析了,讀者本身去看吧。

1. QUIC

QUIC = TCP+TLS+SPDY https://www.chromium.org/quic

其中的 crypto design文檔是本文關注的。

http://network.chinabyte.com/162/13361162.shtml http://blog.chromium.org/2015/04/a-quic-update-on-googles-experimental.html 截止2015.04,從Chrome到Google server的流量的大概50% 是走的QUIC協議,並且還在不斷增長。 聽說減小了YouTube的30%的卡頓。

https://github.com/devsisters/libquic

QUIC值得借鑑的地方有:crypto算法選擇,0-RTT的實現方法,證書壓縮省流量

QUIC的crypto算法選擇: 密鑰交換算法只有2種:

1
2 3 
// Key exchange methods const QuicTag kP256 = TAG('P', '2', '5', '6'); // ECDH, Curve P-256 const QuicTag kC255 = TAG('C', '2', '5', '5'); // ECDH, Curve25519 

對稱加密只使用AEAD,而且只有2種:

1
2 3 4 
// AEAD algorithms const QuicTag kNULL = TAG('N', 'U', 'L', 'N'); // null algorithm const QuicTag kAESG = TAG('A', 'E', 'S', 'G'); // AES128 + GCM-12 const QuicTag kCC12 = TAG('C', 'C', '1', '2'); // ChaCha20 + Poly1305 

證書類型2種,RSA證書, 和 RSA/ECDSA雙證書

1
2 3 
// Proof types (i.e. certificate types) const QuicTag kX509 = TAG(X, 5, 0, 9); // X.509 certificate, all key types const QuicTag kX59R = TAG(X, 5, 9, R); // X.509 certificate, RSA keys only 

handshake的結果是爲了協商出來下面這些參數:

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 
// Parameters negotiated by the crypto handshake. struct NET_EXPORT_PRIVATE QuicCryptoNegotiatedParameters {  // Initializes the members to 0 or empty values.  QuicCryptoNegotiatedParameters();  ~QuicCryptoNegotiatedParameters();   QuicTag key_exchange;  QuicTag aead;  std::string initial_premaster_secret;  std::string forward_secure_premaster_secret;  // subkey_secret is used as the PRK input to the HKDF used for key extraction.  std::string subkey_secret;  CrypterPair initial_crypters;  CrypterPair forward_secure_crypters;  // Normalized SNI: converted to lower case and trailing '.' removed.  std::string sni;  std::string client_nonce;  std::string server_nonce;  // hkdf_input_suffix contains the HKDF input following the label: the  // ConnectionId, client hello and server config. This is only populated in the  // client because only the client needs to derive the forward secure keys at a  // later time from the initial keys.  std::string hkdf_input_suffix;  // cached_certs contains the cached certificates that a client used when  // sending a client hello.  std::vector<std::string> cached_certs;  // client_key_exchange is used by clients to store the ephemeral KeyExchange  // for the connection.  scoped_ptr<KeyExchange> client_key_exchange;  // channel_id is set by servers to a ChannelID key when the client correctly  // proves possession of the corresponding private key. It consists of 32  // bytes of x coordinate, followed by 32 bytes of y coordinate. Both values  // are big-endian and the pair is a P-256 public key.  std::string channel_id;   // Used when generating proof signature when sending server config updates.  bool x509_ecdsa_supported;   // Used to generate cert chain when sending server config updates.  std::string client_common_set_hashes;  std::string client_cached_cert_hashes; }; 

能夠看到:QUIC內置支持sni 並且區分 initial_premaster_secret 和 forward_secure_premaster_secret。

先這樣吧,後續再分析。

2. apple ios iMessage

iOS Security Guide : https://www.apple.com/business/docs/iOS_Security_Guide.pdf

Apple 的 iMessage系統的密碼學安全機制設計,端到端加密,前向安全(PFS),簽名使用ECDSA P-256,非對稱加密使用RSA 1280 bit,蘋果本身維護一個 用戶名—》公鑰 的目錄服務。

iMessage在註冊時,給每一個用戶生成一對 RSA-1280 密鑰用做非對稱加密,一對 NIST P-256 ECDSA 密鑰用做簽名,2個私鑰本地保存,公鑰上傳給Apple的目錄服務器(IDS)。

當要發送消息的時候,根據接收方的用戶名,從IDS裏面找到RSA公鑰 和 APNS 地址。而後隨機生成 128 比特密鑰,用 AES-CTR-128 加密要發送的消息,用接收方的 RSA 1280 公鑰,使用 OAEP 填充加密 128比特aes密鑰。而後拼接 aes密文和rsa密文,對結果使用發送方的 ECDSA 私鑰,用sha1算一次數字簽名。 而後把aes密文,rsa密文,數字簽名拼接起來,發給 APNS 投遞給接收方。

若是要發送大文件,就生成一個key,用 aes-ctr-256 加密文件,並計算一個sha1,而後把key和sha1 放入消息裏面發送。

Apple iMessage is a messaging service for iOS devices and Mac computers. iMessage supports text and attachments such as photos, contacts, and locations.Apple does not log messages or attachments, and their contents are protected by end-to-end encryption so no one but the sender and receiver can access them. Apple cannot decrypt the data. … When a user turns on iMessage on a device, the device generates two pairs of keys for use with the service: an RSA 1280-bit key for encryption and an ECDSA 256-bit key on the NIST P-256 curve for signing. The private keys for both key pairs are saved in the device’s keychain and the public keys are sent to Apple’s directory service (IDS), where they are associated with the user’s phone number or email address, along with the device’s APNs address. …

Users start a new iMessage conversation by entering an address or name. If the user enters a name, the device first utilizes the user’s Contacts app to gather the phone numbers and email addresses associated with that name, then gets the public keys and APNs addresses from the IDS. The user’s outgoing message is individually encrypted for each of the receiver’s devices. The public RSA encryption keys of the receiving devices are retrieved from IDS. For each receiving device, the sending device generates a random 128-bit key and encrypts the message with it using AES in CTR mode. This per-message AES key is encrypted using RSA-OAEP to the public key of the receiving device. The combination of the encrypted message text and the encrypted message key is then hashed with SHA-1, and the hash is signed with ECDSA using the sending device’s private signing key. The resulting messages, one for each receiving device, consist of the encrypted message text, the encrypted message key, and the sender’s digital signature. They are then dispatched to the APNs for delivery. Metadata, such as the timestamp and APNs routing information, is not encrypted. Communication with APNs is encrypted using a forwardsecret TLS channel.

APNs can only relay messages up to 4 KB or 16 KB in size, depending on iOS version. If the message text is too long, or if an attachment such as a photo is included, the attachment is encrypted using AES in CTR mode with a randomly generated 256-bit key and uploaded to iCloud. The AES key for the attachment, its URI (Uniform Resource Identifier), and a SHA-1 hash of its encrypted form are then sent to the recipient as the contents of an iMessage, with their confidentiality and integrity protected through normal iMessage encryption,

3. apple ios HomeKit

iOS Security Guide : https://www.apple.com/business/docs/iOS_Security_Guide.pdf

Apple的HomeKit,是 WWDC2014 上提出的 iot 智能家居開發平臺 (iot啊,目前最火的概念啊,各類高大上啊)。 能夠看到 HomeKit 做爲一個全新的協議, 拋棄了歷史遺留算法,直接採用了目前最早進的算法

HomeKit 密碼學安全機制的設計: 使用Ed25519作 公鑰簽名/驗證,使用 SRP(3072 bit) 作來在iOS設備和HomeKit配件之間交換密碼並作認證,使用 ChaCha20-Poly1305作對稱加密, 使用HKDF-SHA512作密鑰拓展。每一個session的開始用Station-to-Station 協議作密鑰協商和認證, 隨後使用Curve25519作密鑰協商,生成共享key。

HomeKit provides a home automation infrastructure that utilizes iCloud and iOS security to protect and synchronize private data without exposing it to Apple.
… HomeKit identity and security are based on Ed25519 public-private key pairs. An Ed25519 key pair is generated on the iOS device for each user for HomeKit, which becomes his or her HomeKit identity. It is used to authenticate communication between iOS devices, and between iOS devices and accessories.
… 
Communication with HomeKit accessories HomeKit accessories generate their own Ed25519 key pair for use in communicating with iOS devices. To establish a relationship between an iOS device and a HomeKit accessory, keys are exchanged using Secure Remote Password (3072-bit) protocol, utilizing an 8-digit code provided by the accessory’s manufacturer and entered on the iOS device by the user, and then encrypted using ChaCha20-Poly1305 AEAD with HKDF-SHA-512-derived keys. The accessory’s MFi certification is also verified during setup. When the iOS device and the HomeKit accessory communicate during use, each authenticates the other utilizing the keys exchanged in the above process. Each session is established using the Station-to-Station protocol and is encrypted with HKDF-SHA-512 derived keys based on per-session Curve25519 keys. This applies to both IP-based and Bluetooth Low Energy accessories.

4. TextSecure

TextSecure是一個端到端im加密通訊協議,由WhisperSystem公司設計,目前whatsapp和WhisperSystem公司有合做,看網上資料,2014年11月開始,whatsapp已經開始使用TextSecure協議來作端到端加密(消息來源: https://whispersystems.org/blog/whatsapp/ http://www.wired.com/2014/11/whatsapp-encrypted-messaging/)。

TextSecure V2 協議: https://github.com/WhisperSystems/TextSecure/wiki/ProtocolV2 https://github.com/trevp/axolotl/wiki https://whispersystems.org/blog/advanced-ratcheting/

The TextSecure encrypted messaging protocol 是otr的一個衍生協議,主要有2個不一樣點: 1.ECDSA代替DSA 2.某些數據結構壓縮

5. otr 協議

標準文檔見:https://otr.cypherpunks.ca/Protocol-v3-4.0.0.html

open kullo協議 https://www.kullo.net/protocol/

Choice of algorithms Whenever we write about symmetric or asymmetric encryption or signatures, we mean the following algorithms, modes and parameters:

symmetric encryption: AES-256 in GCM mode asymmetric encryption: RSA-4096 with OAEP(SHA-512) padding asymmetric signatures: RSA-4096 with PSS(SHA-512) padding

6. libsodium/NaCL

libsodium/NaCL 值得重點介紹,大力推廣 。 新的沒有兼容包袱的系統,都值得考慮用 NaCL來代替 openssl。 libsodium是對NaCL的封裝,NaCL大有來頭,做者 DJB 是密碼學領域的權威人物,chacha20,Curve25519 的做者 。 沒有歷史包袱的項目,強烈建議使用 libsodium/NaCL。

這篇文章介紹了NaCL和openssl相比的各方面改進 http://cr.yp.to/highspeed/coolnacl-20120725.pdf https://cryptojedi.org/peter/data/tenerife-20130121.pdf http://nacl.cr.yp.to/

7. Tox.im

一款實用NaCL的端到端加密im https://github.com/irungentoo/toxcore/blob/master/docs/updates/Crypto.md

8. CurveCP

CurveCP值得重點介紹, http://curvecp.org/security.html

CurveCP的安全考量: Confidentiality and integrity server authentication? client authentication? replay attacks? man-in-the-middle attacks? passive forward secrecy? active forward secrecy? against traffic analysis? internet destination, exact timing, and approximate length of each packet that you send.

Availability availability, i.e., to make denial-of-service attacks more difficult. Blind amplification Unauthenticated memory consumption CPU consumption

Efficiency CPU overhead Network overhead without packet loss Latency without packet loss

Decongestion

9. tcpcrypt

http://tcpcrypt.org/

10.noise

https://github.com/trevp/noise/wiki

11.tcpcrypt

http://tcpcrypt.org/

12. netflix MSL

http://techblog.netflix.com/2014/10/message-security-layer-modern-take-on.html

http://www.infoq.com/cn/news/2014/11/netflix-safe-communication

12.Amazon KMS 密鑰管理服務 白皮書

https://d0.awsstatic.com/whitepapers/KMS-Cryptographic-Details.pdf

值得注意和借鑑的點:

  • 對稱加密算法選擇了 AES-GCM-256
  • 數字簽名有2種:ECDSA,RSA,
    • ECDSA 的曲線選擇了 secp384r1 (P384),hash 算法選擇了 SHA384
    • RSA 選擇2048位,簽名體制選擇 RSASSA-PSS,hash 算法選擇了 SHA256
  • 密鑰協商,使用ECDH,選擇曲線 secp384r1 (P384),有2種用法
    • one-pass ECDH.
    • ECDHE
  • 電子信封加密,KMS內置了電子信封。

電子信封就是,你預先知道對方的長期公鑰,你有一個消息要發送給對方,因此你生成一個隨機的msgKey,而後 ciphertext = Encrypt(msgKey, message), 而且用對方的公鑰加密 msgKey: encKey = Encrypt(k, msgKey), 最後把(encKey, ciphertext) 發給對方,這樣,只有公鑰對應私鑰的擁有者才能打開信封。典型應用好比 OpenPGP。

其中的 one-pass ECDH,大概意思是: 發起方有一對長期使用的簽名密鑰對,發起方生成一對臨時的 ECDH 密鑰,用本身的長期簽名密鑰簽署 臨時ECDH公鑰。對端有一對長期 ECDH 密鑰,收到發起方發來的 ECDH 公鑰後,驗證簽名,而且用本身的長期ECDH私鑰和收到的公鑰協商出共享密鑰。 整個過程當中,只是用了一對臨時ECDH密鑰,2對長期密鑰。

ECDHE就是比較典型的ECDHE了,和TLS用法同樣:雙方都持有一對長期使用的簽名密鑰對,並擁有對方的簽名公鑰,而後分別生成一對臨時ECDH密鑰,用本身的簽名私鑰簽署ECDH公鑰,把得出的簽名和ECDH公鑰發給對方, 雙方收到對方的ECDH公鑰後,驗證簽名,經過後用對方的ECDH公鑰和本身的ECDH私鑰協商出共享密鑰。DONE。

白皮書中還舉了幾個例子,

六. TLS協議給咱們的啓發 — 現代加密通訊協議設計

在看了這麼多的分析和案例以後,咱們已經能夠概括出加密通訊協議設計的廣泛問題,和常見設計決策,

設計決策點:

  1. 四類基礎算法 加密/MAC/簽名/密鑰交換 如何選擇? 對稱加密目前毫無疑問應該直接用aead,最佳選擇就是 aes-128-gcm/aes-256-gcm/chacha20-poly1305了 數字簽名/驗證方案,若是是移動互聯網,應該考慮直接放棄 RSA,考慮 P-256 的 ECDSA 公鑰證書,或者更進一步的 ed25519 公鑰證書。 密鑰交換算法,目前最佳選擇就是 curve25519,或者 P-256。

  2. 對稱加密算法+認證算法,如何選擇?或者直接用aead?

  3. 簽名算法如何選擇?RSA or ECDSA or Ed25519?

  4. 考慮未來的算法調整,要加版本號機制嗎? 建議是加上,起碼在密鑰協商的步驟,要加上版本號。便於未來更新算法。

  5. RSA用做密鑰交換是一個好的選擇嗎?考慮PFS 建議直接放棄RSA,RSA服務器端性能比ECDSA更差,簽名更大費流量,並且沒有前向安全性,給私鑰保管帶來更大風險。

  6. 自建PKI,是個好的選擇嗎?crl如何解決? 自建PKI能夠作到更安全,好比簡單的客戶端內置數字簽名公鑰。但是當須要緊急吊銷一個證書的時候,只能經過緊急發佈新版客戶端來解決。

  7. 必須用糟糕的openssl嗎?or something better?crypto++,botan, nacl/libsodium, polarssl?libsodium: ed25519+curve2519+chacha20+poly1305

  8. 重放攻擊如何解決?某種seq?或者nonce如何生成?

  9. 握手過程被中間人篡改的問題怎麼解決?

  10. 性能:私鑰運算的cpu消耗能夠承受嗎?加上某種cache? 要解決私鑰運算的高cpu消耗,必然就須要 session ticket/session id 這種cache機制。顯然session ticket 更好

  11. 延遲:密鑰協商須要幾個rtt?最少多少?加上cache後?和tcp對好比何

  12. TLS的性能(主要指服務器cpu消耗)還有空間能夠壓榨嗎?我能設計一個性能更牛逼的嗎?

七. 附錄:密碼學基礎概念

本文已經很長了,基礎概念的內容更多,再展開介紹就太長了,下面就列一下點,貼一下參考資料,就先這樣,之後再說吧。

固然,最好的資料是下面列的書。

1. 塊加密算法 block cipher

AES 等

《AES後分組密碼的研究現狀 及發展趨勢》 http://www.ccf.org.cn/resources/1190201776262/2010/04/15/019026.pdf

aead的介紹(做者是大神) https://www.imperialviolet.org/2015/05/16/aeads.html

3種組合方式之爭 http://www.thoughtcrime.org/blog/the-cryptographic-doom-principle/

CBC模式+MAC-then-encrypt的padding oracle 攻擊, tls POODLE 漏洞 http://drops.wooyun.org/papers/3194 https://defuse.ca/blog/recovering-cbc-mode-iv-chosen-ciphertext.html

128 bit 和 256 bit key size之爭 https://www.schneier.com/blog/archives/2009/07/another_new_aes.html

nist 對 aes gcm 的技術標準,官方權威文檔: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf

一個gcm的調用範例 https://github.com/facebook/conceal/blob/master/native/crypto/gcm_util.c

DES 1天以內破解DES(2008年) http://www.sciengines.com/company/news-a-events/74-des-in-1-day.html

iPhone 5S開始,A7芯片也有了aes硬件指令 (ARMv8 指令集),有825%的性能提高: http://www.anandtech.com/show/7335/the-iphone-5s-review/4

2. 流加密算法 stream cipher

RC4,ChaCha20 等

序列密碼發展示狀 http://www.ccf.org.cn/resources/1190201776262/2010/04/15/019018.pdf

rc4 : http://www.rc4nomore.com/

[RC4加密已再也不安全,破解效率極高(含視頻)] http://www.freebuf.com/news/72622.html

3. Hash函數 hash funtion

MD5,sha1,sha256,sha512 , ripemd 160,poly1305 等

MD5被碰撞: http://natmchugh.blogspot.com/2014/10/how-i-created-two-images-with-same-md5.html

http://blog.avira.com/md5-the-broken-algorithm/

4. 消息驗證碼函數 message authentication code

HMAC-sha256,AEAD 等

爲何要用MAC http://www.happybearsoftware.com/you-are-dangerously-bad-at-cryptography.html

Flickr的漏洞案例: http://netifera.com/research/flickr_api_signature_forgery.pdf

http://www.ietf.org/rfc/rfc2104.txt

5. 密鑰交換 key exchange

DH,ECDH,RSA,PFS方式的(DHE,ECDHE)等

https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/

關於 前向安全性( Perfect Forward Secrecy ) http://vincent.bernat.im/en/blog/2011-ssl-perfect-forward-secrecy.html

http://www.cryptopp.com/wiki/Elliptic_Curve_Cryptography

google對openssl裏面的橢圓曲線的優化: http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/37376.pdf

http://www.math.brown.edu/~jhs/Presentations/WyomingEllipticCurve.pdf

ripple從nistp256k1曲線遷移到ed25519 https://ripple.com/uncategorized/curves-with-a-twist/

openssh 6.5 開始支持 ed25519, curve25519, chacha20-poly1305 http://www.openssh.org/txt/release-6.5

6. 公鑰加密 public-key encryption

RSA,rabin-williams 等

RSA入門必讀(斯坦福,普渡的課件): http://crypto.stanford.edu/~dabo/courses/cs255_winter07/rsa.ppt https://engineering.purdue.edu/kak/compsec/NewLectures/Lecture12.pdf

PKCS1 標準,應用RSA必讀: https://www.ietf.org/rfc/rfc3447

RSA 的公鑰爲何比AES的key長? http://crypto.stackexchange.com/questions/8687/security-strength-of-rsa-in-relation-with-the-modulus-size

http://cryptofails.blogspot.ca/2013/07/saltstack-rsa-e-d-1.html

使用什麼padding? OAEP,爲何不要用PKCS V1.5

http://stackoverflow.com/questions/2991603/pkcs1-v2-0-encryption-is-usually-called-oaep-encryption-where-can-i-confirm-i

http://crypto.stackexchange.com/questions/12688/can-you-explain-bleichenbachers-cca-attack-on-pkcs1-v1-5 http://en.wikipedia.org/wiki/Adaptive_chosen-ciphertext_attack

PKCS #1 — #15標準協議官方網站: http://www.emc.com/emc-plus/rsa-labs/standards-initiatives/public-key-cryptography-standards.htm http://arxiv.org/pdf/1207.5446v1.pdf

blinding 一種實現上的技術,用來解決 timing 側通道攻擊的問題 https://en.wikipedia.org/wiki/Blinding_(cryptography) http://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf

Twenty Years of Attacks on the RSA Cryptosystem: http://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf

電子信封(digital envelope) http://www.emc.com/emc-plus/rsa-labs/standards-initiatives/what-is-a-digital-envelope.htm

在openssl的evp接口中有直接支持: https://wiki.openssl.org/index.php/EVP_Asymmetric_Encryption_and_Decryption_of_an_Envelope

7. 數字簽名算法 signature algorithm

RSA,DSA,ECDSA (secp256r1 , ed25519) 等

三大公鑰體制:RSA,DSA,ECDSA RSA目前是主流,佔據絕大多數市場份額 DSA已經被廢棄 ECDSA是將來的趨勢,例如bitcoin就用ECDSA https://blog.cloudflare.com/ecdsa-the-digital-signature-algorithm-of-a-better-internet/ https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/

8. 密碼衍生函數 key derivation function

TLS-12-PRF(SHA-256) , bcrypto,scrypto,pbkdf2 等

hkdf: http://tools.ietf.org/html/rfc5869 https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/

9. 隨機數生成器 random number generators

/dev/urandom 等

現代密碼學實踐指南[2015年]

八. 參考文獻:

TLS/SSL 相關RFC及標準

協議分析文章

實際部署調優相關

密碼學相關

相關開源項目

相關文章
相關標籤/搜索