TLS協議分析

TLS協議分析

2015-09-06

本文目標:

  1. 學習鑑賞TLS協議的設計,透徹理解原理和重點細節
  2. 跟進一下密碼學應用領域的歷史和進展
  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
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]

一 . TLS協議的設計目標:

1. 密碼學的方法論

密碼學和軟件開發不同,軟件開發是工程,是手藝,造輪子是寫代碼的一大樂趣。軟件開發中常常有各種權衡,一般難有明確的對錯,一般還用建築來比擬軟件的結構,設計的優雅被高度重視。

密碼學就不一樣了。密碼學是科學,不是工程,有嚴格的技術規範,嚴禁沒有經過學術訓練者隨意創造。要求嚴謹的理論建模,嚴密的數學證明。很少有需要權衡的地方,正確就是正確,錯誤就是錯誤。又由於密碼學過去在軍事上的重要價值,各國政府一直投入大量人力物力財力,不斷深入強化己方的算法,破解對手的算法,所以密碼學就是一種殘酷的軍備競賽。

  • 密碼學有很多的陷阱(下文會介紹幾個),設計使用密碼學的協議或者軟件,是極其容易出錯,高風險的專業活動,單純的碼農背景是做不了的。本着不作死就不會死的偉大理念,首先推薦讀者儘可能使用 TLS 這種標準化,開源,廣泛使用,久經考驗,高性能的協議。本文也只是整理一點粗淺的科普常識,讀完這篇文章,並不能使讀者具有設計足夠安全的密碼學協議的能力。

  • 密碼學經過幾十年的軍備競賽式發展,已經發展出大量巧妙而狡猾的攻擊方法,我們使用的算法,都是在所有已知的攻擊方法下都無法攻破的,由於我們大多數碼農並沒有精力去了解最前沿的攻擊方法,所以我們其實並沒有能力去評價一個加密算法,更沒有能力自己發明算法。所以最好跟着業界的主流技術走,肯定不會有大錯。

  • 現代密碼學近20年進展迅猛,現在搞現代密碼學研究的主要都是數學家,在這個領域裏面以一個碼農的知識背景,已經很難理解最前沿的東西,連正確使用加密算法都是要謹慎謹慎再謹慎的。一個碼農,能瞭解密碼學基本概念,跟進密碼學的最新應用趨勢,並正確配置部署TLS這種協議,就很不錯了。

  • 密碼學算法很難被正確地使用,各種細節非常容易出錯。 例如:

    • 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及標準

協議分析文章

實際部署調優相關

密碼學相關

相關開源項目

發佈了39 篇原創文章 · 獲贊 18 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章