TLS協議分析 (三) record協議

轉自:http://chuansong.me/n/1268791552834

4. record 協議

record協議做應用數據的對稱加密傳輸,佔據一個TLS連接的絕大多數流量,因此,先看看record協議
圖片來自網絡:

Record 協議 — 從應用層接受數據,並且做:

  1. 分片,逆向是重組

  2. 生成序列號,爲每個數據塊生成唯一編號,防止被重放或被重排序

  3. 壓縮,可選步驟,使用握手協議協商出的壓縮算法做壓縮

  4. 加密,使用握手協議協商出來的key做加密/解密

  5. 算HMAC,對數據計算HMAC,並且驗證收到的數據包的HMAC正確性

  6. 發給tcp/ip,把數據發送給 TCP/IP 做傳輸(或其它ipc機制)。

4.1. SecurityParameters

record層的上述處理,完全依據下面這個SecurityParameters裏面的參數進行:

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個,如果不需要,那就是空):

 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

4.2. record層分段

如上圖所示,對要發送的數據流,首先分段,分段成如下格式:

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必須小於 214,一般我們不希望length過長,因爲解密方需要收完整個record,才能解密,length過長會導致解密方需要等待更多的rtt,增大latency,破壞用戶體驗,參考 [Web性能權威指南] 鏈接 http://book.douban.com/subject/25856314/ TLS那一章。

  • type字段
    :  ,用來標識當前record是4種協議中的哪一種,

record壓縮 :  TLS協議定義了可選的壓縮,但是,由於壓縮導致了 2012 年被爆出[CRIME攻擊,BREACH攻擊] 鏈接 https://en.wikipedia.org/wiki/CRIME  ,所以在實際部署中,一定要禁用壓縮
 http://www.unclekevin.org/?p=640
 http://www.freebuf.com/articles/web/5636.html

4.3. record層的密碼學保護

record層的密碼學保護:

經過處理後的包格式定義如下:

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.4. record層的密碼學保護—MAC

TLS record 層 MAC的計算方法:

MAC(MAC_write_key, seq_num +
        TLSCompressed.type +
        TLSCompressed.version +
        TLSCompressed.length +
        TLSCompressed.fragment);

其中的seq_num是當前record的 sequence number,每條record都會++,
可以看到把 seq_num,以及record essay-header裏面的幾個字段也算進來了,這樣解決了防重放問題,並且保證record的任何字段都不能被篡改。

算完MAC,格式如下:

stream-ciphered struct {
    opaque content[TLSCompressed.length];
    opaque MAC[SecurityParameters.mac_length];
} GenericStreamCipher;

然後根據SecurityParameters.cipher_type,選擇對應的對稱加密算法進行加密,分類解說如下:

4.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.freebuf.com/news/72622.html

http://www.imperva.com/docs/HII_Attacking_SSL_when_using_RC4.pdf

4.6. record層的密碼學保護— CBC block cipher

CBC模式塊加密
TLS目前靠得住的的塊加密cipher也不多,基本就是AES(最靠譜,最主流),Camellia,SEED,(3DES,IDEA之類已經顯得老舊,DES請禁用),加密完的格式如下:

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攻擊] 鏈接 http://www.openssl.org/~bodo/tls-cbc.txt  。
  所以,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是否正確無關

4.7. record層的密碼學保護— AEAD cipher

AEAD
到了我們重點關注的AEAD,AEAD是新興的主流加密模式,是目前最重要的模式,其中主流的AEAD模式是 aes-gcm-128/aes-gcm-256/chacha20-poly1305

AEAD加密完的格式是:

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 是:

additional_data = seq_num + TLSCompressed.type +
    TLSCompressed.version + TLSCompressed.length;

“+” 表示字符串拼接。
   可以看到,此處類似上面的MAC計算,算入了seq_num來防重放,type,version,length等字段防止這些元數據被篡改。

AEADEncrypted = AEAD-Encrypt(write_key, nonce, plaintext,
    additional_data)

解密+驗證完整性:

TLSCompressed.fragment = AEAD-Decrypt(write_key, nonce,
    AEADEncrypted,
    additional_data)

如果解密/驗證完整性失敗,就回復一條 fatal bad_record_mac alert 消息.

aes-gcm的iv長度,nonce長度,nonce構成等,後續再深入探討。

4.8. record層的密碼學保護— Key擴展

Key 擴展

TLS握手生成的master_secret只有48字節,2組encryption key, MAC key, IV加起來,長度一般都超過48,(例如 AES_256_CBC_SHA256 需要 128字節),所以,TLS裏面用1個函數,來把48字節延長到需要的長度,稱爲PRF:

key_block = PRF(SecurityParameters.master_secret,    "key expansion",
    SecurityParameters.server_random +
    SecurityParameters.client_random);

然後,key_block像下面這樣被分割:

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)擴展成無限長的字節流:

P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
    HMAC_hash(secret, A(2) + seed) +
    HMAC_hash(secret, A(3) + seed) + ...

其中”+”表示字符串拼接。
 A() 定義爲:

A(0) = seed
A(i) = HMAC_hash(secret, A(i-1))

TLS的 PRF 就是把 P_hash 應用在secret上:

 PRF(secret, label, seed) = P_(secret, label + seed)

其中 label 是一個協議規定的,固定的 ASCII string.

要注意的是,TLS 1.3裏面已經廢棄了這種方式,改爲使用更靠譜的 HKDF,HKDF 也是 html5的WebCryptoAPI的標準算法之一。

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