评论数:240条

扫盲 HTTPS 和 SSL/TLS 协议[4]:历史版本的演变及 Record 协议的细节

  俺一直在等 TLS 1.3 定稿(之所以这么期待,因为 1.3 是一次【大】升级)。
  前些天(2018年8月),IETF 终于发布了 RFC 8446,标志着 TLS 1.3 协议大功告成。于是俺就来继续完成本系列的后面几篇。
  本系列的前一篇,咱们聊了“密钥交换/密钥协商”的相关算法。从这篇开始,会逐步谈及协议的细节,今天就从 Record 协议说起。由于恰逢 TLS 1.3 新鲜出炉,俺也顺便聊聊 SSL/TLS 历史上几个版本的演变及差异。


★名词解释


  对于本文会涉及到的几个专业术语,先放上相应的解释。

◇块加密算法


  “块加密算法”又称“分组加密算法”,洋文叫做“Block Cipher”,相关的维基百科链接在“这里”。
  顾名思义,就是这类加密算法要求:被加密的明文数据必须分成【相同大小】的若干坨(每一坨的大小称为【块长度】)。
  以目前流行的对称加密算法 AES 为例。AES 的【块长度】是“128 比特”(16字节)。也就是说,AES 要求被加密的明文必须是【128位】的整数倍。
  由于【块加密算法】对明文的长度有要求,所以用这类算法对明文数据进行加密之前,要先进行【补齐】——在明文数据末尾追加一些垃圾数据,使之达到【块长度】的整数倍。

◇流加密算法


  与“块加密算法”相对应的是“流加密算法”,洋文叫做“Stream Cipher”,相关的维基百科页面在“这里”。
  与“块加密算法”最大的差别在于——流加密算法对明文数据的长度【没有】要求(可以是任意字节数)。
  典型的流加密算法是 RC4(顺便提一句:RC4 里面的 R 也就是 RSA 的那个 R)

◇MAC(消息认证码)


  MAC 是洋文“Message Authentication Code”的缩写,维基百科的介绍在“这里”。这玩意儿是通讯及密码学的常见的概念——用 MAC 算法来确保某个信息在传输的过程中【没有】被篡改。
  说到这儿,某些聪明的同学已经联想到【散列函数】——用散列函数计算出来的哈希值确实可以用来作为 MAC。这种基于哈希(HASH)的“消息验证代码”也称作“HMAC”。不了解哈希算法的同学可以看这篇博文:《扫盲文件完整性校验——关于散列值和数字签名

◇MAC 的几种搞法


  常见的有如下3种。俺从维基百科剽窃了对应的流程图,大伙儿看图就明白其原理,省得俺浪费力气打字了。

  Encrypt-then-MAC(EtM)
不见图 请翻墙
(先加密明文得到密文,再根据密文计算 MAC,最后把密文与 MAC 合并成一坨)

  Encrypt-and-MAC(E&M)
不见图 请翻墙
(对明文加密得到密文,对明文计算 MAC,最后把密文与 MAC 合并成一坨)

  MAC-then-Encrypt(MtE)
不见图 请翻墙
(对明文计算 MAC,把明文与 MAC 合并成一坨,然后一起加密)

◇AE(带认证的加密)


  传统的加密算法只负责实现【保密性】,而不负责【完整性】。这么说有点抽象,俺举个例子:
  假设你把一段明文 P 加密为一段密文 C,通过网络把 C 发送给另一个人。中途如果被攻击者篡改了(把 C 修改为 C'),那么接收方收到 C' 之后,还是可以正常进行解密操作(当然,解密之后得到的就不再是 P 了,而是得到一段无意义的数据)
  为了解决上述弊端,业界引入 AE(Authenticated Encryption)算法的概念。也就是说,AE 算法不但能做到【保密性】还可以做到【完整性】。
  刚才扫盲的三种 MAC 实现方式,【从理论上讲】就可以算 AE 啦。但上述那三种 MAC 的实现方式有个弊端——【解密】的一方还要自己进行 MAC 的验证操作。这种搞法既麻烦又增加额外风险。比如说:写解密代码的程序猿/程序媛万一太粗心忘记进行验证,岂不前功尽弃?

◇【真正的】AE


  为了避免上述提到的弊端,密码学界那帮专家又捣鼓出一些新的算法(比如 CCM、GCM)。这些算法可以在解密的同时验证数据的有效性,而且这些算法也【不】需要再额外存储一个独立的 MAC 数据。
  本文后续部分提及的 AE,如果没有特别说明,就是指这类【真正的】AE。
  知名的那些 AE 算法,可以组合现有的加密算法。比如说:从 TLS 1.2 开始引入的 GCM 和 CCM,这两个 AE 算法都可以组合 AES128 与 AES256 加密算法。
  组合现有加密算法的好处不光是避免重新发明轮子,而且还可以充分利用硬件加速。比如 AES 作为对称加密的标准算法,某些芯片(比如 Intel/AMD)会把 AES 算法直接做成 CPU 指令,以实现硬件加速。

◇AEAD


  AEAD 是洋文“Authenticated Encryption with Associated Data”的缩写,普通话叫做“带关联数据的认证加密”。简而言之,AEAD 是 AE 的变种。为了方便理解,俺再来找个栗子:
  比如说在网络通讯中,数据包的【头部】必须是明文且保证完整性;而数据包的【载荷】既要加密(保密性)又要保证完整性。这时候 AEAD 算法就派上用场啦——数据包的【头部】就是 AEAD 算法里面的【关联数据】。

◇前向保密 / 完美正向加密(forward secrecy)


  在本系列的前一篇《密钥交换(密钥协商)算法及其原理》,俺已经补充了一个章节,简单扫盲了一下“回溯性破解”与“前向保密”的概念。
  所以这里就不再浪费口水啦。


★SSL/TLS 历史版本的演变及差异


  趁着 TLS 1.3 正式发布的大好时机,简单扫盲一下 SSL/TLS 各个版本的差异。

◇SSL 1.0


  在本系列的第一篇,俺曾经提到:SSL 是上世纪90年代中期,由网景公司设计的。早期设计者是网景公司的 Taher Elgamal(一位埃及的密码学家)。此人也被誉为“SSL 它爹”。
  SSL 1.0 【从没】正式发布过,所以业界对它了解不多。之所以没有正式发布,据说是设计完之后发现了若干严重的安全缺陷,就不好意思再拿出来丢人现眼。

◇SSL 2.0


  SSL 2.0 是 1995 年正式发布滴,坦率地说,协议设计比较粗糙。
  比如俺在前一篇介绍过“密钥交换算法”和“身份认证算法”。在这两方面,SSL 2.0 都仅仅支持 RSA 这一种算法。
  另一个值得吐槽之处是:SSL 2.0【没有】考虑到“前向保密”(洋文是“forward secrecy”),因此会遭遇【回溯性破解】的风险。(关于“前向保密”与“回溯性破解”,请看本文开头的名词解释)

◇SSL 3.0


  SSL 2.0 发布之后不久,又被发现若干安全漏洞。所以又赶紧在 1996 年发布了 SSL 3.0 版本。(接连两个版本都不太灵光,看来“SSL 它爹”的水平实在令人不敢恭维)
  这个 3.0 版本可以说是另起炉灶——换了几个密码学专家,【重新设计】了 SSL 协议。所以 SSL 3.0 相比 SSL 2.0 有很大差别。
  关于 SSL 3.0 的权威技术规范,可以参见 RFC 6101

  请允许俺稍微跑题一下:
  重新设计 SSL 3.0 的那些专家,为首的是来自斯坦福大学的 Paul Kocher——此人堪称密码学奇才,SSL 3.0 发布的那年(1996),他才23岁(回想俺23岁的时候,在密码学方面是只菜鸟,真是情何以堪)。
  在同一年,他还发表了篇论文,描述了一种【全新的】密码学攻击方式——timing attack(基于时间因素的边信道攻击)。这种攻击手法的原理,说起来并不算复杂,但很有创意,之前从来没人想到过。

◇TLS 1.0


  TLS 1.0 是 1999 年发布滴,技术规范参见 RFC 2246
  为啥从 SSL 改名为 TLS 捏?主要是安全性在 Web 世界中越来越重要,因此 IETF 组织急需把 SSL 的协议【标准化】,为了以示区别,另外起个名字叫 TLS(洋文“Transport Layer Security”的缩写)。
  虽然协议名改了,但其实 TLS 1.0 与 SSL 3.0 的差别不大。这点从协议版本号也可以看出来——TLS 1.0 内部的协议版本号其实是【3.1】。

◇TLS 1.1


  TLS 1.1 是 2006 年发布滴,技术规范是 RFC 4346
  发布该版本的主要动机是:修补 CBC(cipher-block chaining)相关的漏洞,以防范某些攻击(比如“padding oracle attack”)。
  在 1.1 版本,原有的“【隐式】初始化向量”改为“【显式】初始化向量”,修正了 CBC 方式下填充数据的缺陷。

◇TLS 1.2


  TLS 1.2 是 2008 年发布滴,技术规范是 RFC 5246
  相比 TLS 1.1 的变化如下:
支持 AEAD 加密模式(参见 RFC 5116
加密算法废弃了 DES、DES40、IDEA、RC2
HMAC 增加了 SHA256

◇TLS 1.3


  俺写本文时,TLS 1.3 刚刚新鲜出炉没几天(2018年8月),其技术规范是 RFC 8446
  从2008到2018,真所谓“十年磨一剑”。目前看来,这个 1.3 版本是一次雄心勃勃的升级,相对 TLS 1.2 加了不少东西,也删了不少东西。考虑到篇幅,俺挑几个主要的来说说:
首先要表扬的是:TLS 1.3 完善了 SNI(Server Name Identification)扩展,非常有利于翻墙工具借助【依附的自由】对抗网络封锁;
其次是强制使用“完美正向加密(PFS)”,所以很多做不到 PFS 的密钥协商算法在 TLS 1.3 规范中被无情地抛弃了(比如:RSA、静态 DH、静态 ECDH...);
传统的 HMAC 也被无情地抛弃了,今后只使用 AEAD 方式来保障完整性(关于 AEAD,请看本文开头的名词解释);
原有的对称加密算法只保留 AES(3DES、RC4 废弃),另增加 CHACHA20 流加密算法;
压缩特性被废除(以消除 CRIME 攻击的风险);
初始握手的过程有【很大】的改变(这个等下一篇再聊)
......


★Record 协议概述


  很多介绍 SSL/TLS 的文章都把 record 协议给忽略了。可能这些文章的作者觉得 record 协议不太重要。但俺本着“高度负责任”的心态,觉得还是有必要跟大伙儿聊一下。
  SSL/TLS 协议在通讯的过程中会把需要传输的数据分成一坨一坨的,每次都只发送或接收一坨。在洋文中,每一坨称作一个 record。下面要聊的“Record 协议”,就是用来定义这个 record 的格式。


★Record 协议的结构


  Record 协议比较简单,主要结构见下表:

字段名称字段长度备注
类型1字节
版本2字节TLS 1.3 废弃,仅留作向下兼容
载荷长度2字节
消息0~N 字节
消息认证码0~N 字节TLS 1.3 不需要该字段
填充0~N 字节

◇类型(type)


  “类型”字段是个枚举值,协议允许的有效值参见下表(对表格中的每种类型,后续小节会有详细介绍)
十六进制十进制含义备注
0x1420ChangeCipherSpec(切换到加密方式)TLS 1.3 废弃
0x1521Alert(告警)
0x1622Handshake(握手)
0x1723Application(应用层数据)
0x1824Heartbeat(心跳)始于 TLS 1.3

◇版本(version)


  “版本”字段含两个字节,分别表示:主版本号 & 次版本号。其有效值如下:
主版本号次版本号含义
0x20x0SSL 2.0
0x30x0SSL 3.0
0x30x1TLS 1.0
0x30x2TLS 1.1
0x30x3TLS 1.2
(注:从 TLS 1.3 版本开始,“版本”字段已经被废弃,仅用于向后兼容)

◇长度(length)


  “长度”字段含两个字节,表示载荷长度。
  对于【明文】的 record,【没有】“消息认证码”字段,也【没有】“填充”字段——“载荷长度”也就是消息的长度。
  对于【加密】的 record——“载荷长度”是“消息、消息验证码、填充”三者的长度之和。
  SSL/TLS 协议规定了长度字段最多只能表示 0~16384 字节(注:214 = 16384)。

◇消息(message)


  每个 record 的“消息”字段的内容取决于“类型”字段。关于这个“消息”字段,待会儿再聊。

◇消息认证码(MAC)


  关于 MAC 这个概念,参见本文开头部分的名词解释,此处不再浪费口水。
  在 SSL/TLS 协议中,MAC 对于明文的 record 没有意义(为啥没意义,请自行思考)。
  对于【加密】的 record,要分两种情况:
其一,如果是【传统的】块加密与流加密,会带有额外的 MAC;
其二,如果使用 AEAD 加密模式,其本身已经内置了【完整性】的校验,不需额外的 MAC。
  前面提到,AEAD 是从 TLS 1.2 开始引入,到了 TLS 1.3 就【只支持】AEAD 啦。所以 TLS 1.3 【没有】MAC 部分。

  SSL/TLS 各个版本实现【完整性】的方式,参见如下表格:
算法SSL 2.0SSL 3.0TLS 1.0TLS 1.1TLS 1.2TLS 1.3
HMAC-MD5
HMAC-SHA1
HMAC-SHA256
AEAD

◇填充(padding)


  只有当 record 是加密的,并且使用的加密算法属于【块加密算法】,才会使用“填充”字段。


★各种类型 Record 简介


  从 Record 协议的头部类型字段可以看出,总共有5种类型的 Record。下面简单说一下:

◇握手(Handshake)


  Record 协议的“类型”字段为 220x16),表示这条 record 是 Handshake 类型。
  “握手”的意思就是——通讯双方初次打交道,需要交换一些初始化的信息。
  对于 SSL/TLS 协议,为了建立起【可靠的】加密信道,通讯双方需要在握手的过程交换很多信息(加密算法、压缩算法、MAC 算法、等等)。所以这个握手的过程是比较复杂滴,需要耗费很多口水。俺留到本系列的下一篇,专门来聊“握手的细节”。
  由于握手的过程,加密信道尚未建立,所以用来进行握手的 record 是【明文】滴,并且也【没有】“MAC”字段及“填充”字段。

◇切换到加密方式(ChangeCipherSpec)


  Record 协议的“类型”字段为 200x14),表示这条 record 是 ChangeCipherSpec 类型。
  这个 ChangeCipherSpec 也是跟握手过程相关滴,留到下一篇。
  (注:从 TLS 1.3 版本开始,ChangeCipherSpec 类型的 record 已经被废弃,仅用于向后兼容)

◇应用层数据(Application)


  Record 协议的“类型”字段为 230x17),表示这条 record 是 Application 类型。
  也就是说,这条 record 的载荷部分存放的是上层(应用层)协议的数据。既然传输的是上层数据,肯定得是【加密】滴!但不一定有“MAC”字段。要看具体的 SSL/TLS 版本(如下):
1. 对于 TLS 1.1 及之前的版本,总是使用 HMAC 进行完整性校验,所以总是含有“MAC”字段。
2. 对于 TLS 1.2,如果握手之后采用 AEAD 加密模式,就没有 MAC;反之,则有 MAC。
3. 对于 TLS 1.3 及之后的版本,只支持 AEAD,【不】再有“MAC”字段。
  另外,在 TLS 1.2 及【之前】的版本中,还支持“对应用层数据进行压缩”。本来俺还想聊聊这方面的实现细节。但是 TLS 1.3 已经【废弃】了压缩选项(为了防 CRIME 攻击),恐怕未来版本也不会再有压缩选项了。搞得俺也没积极性来聊这个话题了 :(

◇告警(Alert)


  Record 协议的“类型”字段为 210x15),表示这条 record 是 Alert 类型。
  这种类型的 record 用来发送警告或出错信息。
  在通讯的过程(包括握手过程)中,有时候某一方会发现不对劲(比如收到的数据出现缺失或错误),这时候就要发送一条 Alert 类型的 record 给对方。
  不对劲的情况分为两种,洋文分别称之为 Warning 和 Fatal。两者的差别在于:
Warning 表示通讯出现【不稳定】的情况(这种“不稳定”通常是【可恢复】滴)
Fatal 表示通讯出现【不可靠】的情况(比如:证书失效、数据被篡改。这种“不可靠”通常是【不可恢复】滴)
  如果不对劲的情况属于 Warning,通讯可能会继续也可能会断开;如果不对劲的情况属于 Fatal,通讯会在发送 Alert 之后立即断开。
  这种类型的 record,其“消息”字段仅有2字节,头一个字节表示告警的“级别/Level”(1表示 warning,2表示 fatal);后一个字节表示具体的描述(有一个对照表,用不同的整数表示不同的情况)。
  如果在握手【之后】发送告警,此时双方已经建立起加密信道,则告警 record 的“消息”字段是【密文】的。
  如果在握手【之前】发送告警,此书尚未建立加密信道,则告警 record 的“消息”字段是【明文】的。

◇心跳(Heartbeat)


  Record 协议的“类型”字段为 240x18),表示这条 record 是 Heartbeat 类型。
  这种类型的 record 用来发送心跳信息。
  所谓的【心跳】,主要用来确认“通讯的对端”依然正常。在 SSL/TLS 连接建立之后,有可能在某些情况下出现【通讯空闲】(上层的协议在某个时间段没有数据传输)。这时候就需要依靠【心跳机制】来判断对方是否还活着。
  由于“心跳”的传输是在加密信道建立【之后】,所以“心跳”的 record 也是加密滴。
  关于这个心跳机制的技术细节,请参见 RFC6520(链接在“这里”)。
  这个心跳协议的 RFC 发布于2012年(晚于2008年的 TLS 1.2),因此目前只有 TLS 1.3 版本才支持它。


回到本系列的目录
版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想和本文原始地址:
https://program-think.blogspot.com/2018/09/https-ssl-tls-4.html