TLS协议分析 (三) record协议

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.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个险恶的陷阱:

实现的代码必须在收到全部明文之后才能传输密文,否则可能会有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_<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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容