轉自:http://chuansong.me/n/1268791552834
4. record 協議
record協議做應用數據的對稱加密傳輸,佔據一個TLS連接的絕大多數流量,因此,先看看record協議
圖片來自網絡:
Record 協議 — 從應用層接受數據,並且做:
-
分片,逆向是重組
-
生成序列號,爲每個數據塊生成唯一編號,防止被重放或被重排序
-
壓縮,可選步驟,使用握手協議協商出的壓縮算法做壓縮
-
加密,使用握手協議協商出來的key做加密/解密
-
算HMAC,對數據計算HMAC,並且驗證收到的數據包的HMAC正確性
-
發給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類:
-
Block Cipher (CBC mode of operation) + HMAC:例如 aes-128-cbc+hmac-sha256
-
Stream Cipher (RC4) + HMAC
-
Authenticated-Encryption using block cipher (GCM/CCM 模式):例如 aes-128-gcm
1.Block Cipher+HMAC 和 2.Stream Cipher + HMAC 的各類算法目前(2015年)都已經爆出各種漏洞(後文解釋),目前最可靠的是 3.Authenticated-Encryption 類的算法,主要就是aes-gcm,下一代的TLS v1.3乾脆只保留了3.Authenticated-Encryption,把1和2直接禁止了(所以。。。你真的還要繼續用aes-cbc嗎?)。
GCM模式是AEAD的,所以不需要MAC算法。
GCM模式是AEAD的一種,AEAD 的 作用類似於 Encrypt-then-HMAC ,例如 Sha256 + Salt + AES + IV
此處需要介紹一個陷阱。
在密碼學歷史上,出現過3種加密和認證的組合方式:
-
Encrypt-and-MAC
-
MAC-then-Encrypt
-
Encrypt-then-MAC
在TLS協議初定的那個年代,人們還沒意識到這3種組合方式的安全性有什麼差別,所以TLS協議規定使用 2.MAC-then-Encrypt,即先計算MAC,然後把 “明文+MAC” 再加密(塊加密或者流加密)的方式,做流加密+MAC,和塊加密+MAC。
但是,悲劇的是,近些年,人們發現 MAC-then-Encrypt 這種結構導致了 很容易構造padding oracle 相關的攻擊,例如這在TLS中,間接形成被攻擊者利用,這間接導致了 BEAST 攻擊 , Lucky 13攻擊 (CVE-2013-0169), 和 POODLE 攻擊 (CVE-2014-3566).
目前因此,學術界已經一致同意:
Encrypt-then-MAC 纔是最安全的!
tls使用的是 MAC-then-Encrypt 的模式,導致了一些問題。
具體比較,參見:
http://cseweb.ucsd.edu/~mihir/papers/oem.pdf
https://www.iacr.org/archive/crypto2001/21390309.pdf
http://crypto.stackexchange.com/questions/202/should-we-mac-then-encrypt-or-encrypt-then-mac
https://news.ycombinator.com/item?id=4779015
http://tozny.com/blog/encrypting-strings-in-android-lets-make-better-mistakes/
鑑於這個陷阱如此險惡,學術界有人就提出了,乾脆把Encrypt和MAC直接集成爲一個算法,在算法內部解決好安全問題,不再讓碼農選擇,避免衆碼農再被這個陷阱坑害,這就是AEAD(Authenticated-Encryption With Addtional data)類的算法,GCM模式就是AEAD最重要的一種。
4.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個險惡的陷阱:
-
實現的代碼必須在收到全部明文之後才能傳輸密文,否則可能會有BEAST攻擊
-
實現上,根據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中,做了更改:
-
規定 AEAD算法的 nonce的長度規定爲 max(8 bytes, N_MIN),即如果N_MIN比8大,就用N_MIN; 如果比8小,就用8。
-
並且規定 N_MAX小於8字節的AEAD不得用於TLS。
-
規定TLS AEAD中每條record的nonce通過下面的方法構造出來:
64bit的sequence number的右側填充0,直到長度達到iv_length。然後把填充過的sequence number和靜態的 client_write_iv或 server_write_iv (根據發送端選擇)做異或(XOR)。異或完成後,得到的 iv_length 的nonce就可以做每條record的nonce用了。AEAD輸入的明文就是 TLSCompressed.fragment (記得上面的介紹嗎?AEAD是MAC和encrypt的集成,所以輸入數據不需要在算MAC了).
AEAD輸入的additional_data 是:
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的標準算法之一。