写点什么

TLS 协议分析 (三) record 协议

用户头像
OpenIM
关注
发布于: 42 分钟前

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 类型的,并且不得超过 。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=640http://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.pdfhttps://www.iacr.org/archive/crypto2001/21390309.pdfhttp://crypto.stackexchange.com/questions/202/should-we-mac-then-encrypt-or-encrypt-then-machttps://news.ycombinator.com/item?id=4779015http://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 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 用了。

  4. AEAD 输入的明文就是 TLSCompressed.fragment (记得上面的介绍吗?AEAD 是 MAC 和 encrypt 的集成,所以输入数据不需要在算 MAC 了).

  5. 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) = seedA(i) = HMAC_hash(secret, A(i-1))
复制代码


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


 PRF(secret, label, seed) = P_<hash>(secret, label + seed)
复制代码


其中 label 是一个协议规定的,固定的 ASCII string.


要注意的是,TLS 1.3 里面已经废弃了这种方式,改为使用更靠谱的 HKDF,HKDF 也是 html5 的 WebCryptoAPI 的标准算法之一。


本文转自微信后台团队,如有侵犯,请联系我们立即删除


OpenIMgithub 开源地址:


https://github.com/OpenIMSDK/Open-IM-Server


OpenIM 官网 : https://www.rentsoft.cn


**OpenIM 官方论坛: ** https://forum.rentsoft.cn/


更多技术文章:


开源 OpenIM:高性能、可伸缩、易扩展的即时通讯架构https://forum.rentsoft.cn/thread/3


【OpenIM 原创】简单轻松入门 一文讲解 WebRTC 实现 1 对 1 音视频通信原理https://forum.rentsoft.cn/thread/4


【OpenIM 原创】开源 OpenIM:轻量、高效、实时、可靠、低成本的消息模型https://forum.rentsoft.cn/thread/1

用户头像

OpenIM

关注

还未添加个人签名 2021.08.30 加入

还未添加个人简介

评论

发布
暂无评论
TLS协议分析 (三) record协议