随着互联网越来越普及,网络通信的环境也越来越复杂,网络上的应用越来越容易受到攻击。攻击者可分为两类:仅可以观察到网络上数据包的被动攻击者和可以修改和注入数据包的主动攻击者。因此,网络通信的安全需要达到三个目标:保密性、消息完整性和端点认证。保密性指的是消息要进行加密,不能让被动攻击者观察到消息的内容;消息完整性指的是如果主动攻击者修改了消息的内容,接收者需要能够检测到这种修改;端点认证指的是确保通信的对端是正确的端点,而不是冒充的。
1994年4月,网景公司发布了SSLv2(Secure Socket Layer,安全套接层),意在为Web应用(主要是基于HTTP的应用)提供安全通信保障。由于SSL的设计简单,对应用来讲相对透明,再加上当时网景浏览器的市场占有率很高,SSLv2获得了广泛的认同和使用。
但由于SSLv2的设计和开发投入太少,也没有安全专家的参与,所以安全性不高,很容易攻破。于是网景公司又请多位安全专家开始设计SSLv3,并于1995年末发布。SSLv3设计了新的规格描述语言,使用了新的记录类型和数据编码,增加了只认证不加密的模式,重写了密钥扩展算法,支持防止对数据流进行截断攻击的关闭握手。另外,网景公司还增加了多种新的加密算法:DSS(Digital Signature Standard,数字签名标准)、Diffie-Hellman(DH)以及美国政府鼓励的FORTEZZA加密套件。
SSLv3协议的具体描述可以参考RFC 6101,分为两个阶段:握手阶段和数据传输阶段。握手阶段完成对端点的认证和确定保护数据传输的密钥。一旦确定了密钥,后面的数据传输和SSL协议过程都受到加密和完整性保护。SSLv3是一种分层协议,通过记录层承载不同的消息类型来区分不同的内容;记录层则由某种保证可靠性的协议承载,通常是TCP。SSLv3的分层如图1-12所示。
图1-12 SSLv3分层
SSLv3握手阶段对预主密钥进行协商,然后使用客户端和服务器的随机数扩展出不同的密钥。
首先使用PRF(Pseudo Random Function,伪随机函数)从协商出的预主密钥、客户端随机数和服务器随机数扩展出主密钥。PRF的定义为:
主密钥计算方法可以简单表示为:
然后使用伪随机函数从主密钥、客户端随机数和服务器随机数、衍生标签扩展出不同用途的密钥:
各密钥间的关系可以简化为图1-13,其中IV指初始向量。
图1-13 SSL密钥导出示意图
图1-14展示了SSL典型的首次握手过程。
1)客户端先发出ClientHello消息,其中包含了支持的加密算法和随机数,可能还有压缩算法,但很少使用 。随机数用于产生最终的主密钥(通常也称为master_secret),用于保证不同连接在预主密钥(通常也称为pre_master_secret)相同的情况下仍然可以产生不同的主密钥,保证主密钥的前向安全,也用于保证其他信息都相同情况下握手信息的消息认证码不同,以用于抗重放攻击。
2)服务器回复ServerHello消息,其中包括从ClientHello消息中选中的加密算法和随机数。同时通过Certificate消息发送服务器证书,其中包含服务器的公钥。然后发送ServerHelloDone消息,这个消息中没有实际内容,只是为了兼容其他变种中服务器还需要发送其他消息的场景。
图1-14 SSL典型的首次握手过程
注意 示例中服务器没有要求客户端提供证书,这是大多数的应用场景;但有的场景下服务器是需要认证客户端的,这种情况下客户端需要提供证书。
3)客户端验证服务器证书后,生成加密预主密钥,使用服务器证书的公钥加密后,在ClientKeyExchange消息中发送给服务器。需要说明的是,示例中使用的是RSA(RSA加密算法)交换预主密钥,如果使用DH算法交换预主密钥,服务器也需要发送一个ServerKeyExchange消息;使用临时RSA也需要ServerKeyExchange消息发送加密预主密钥的临时公钥。
4)客户端发送ChangeCipherSpec消息,表示后续消息使用协商好的主密钥加密;然后发送加密的Finished消息,其中包含握手信息的消息认证码。
5)服务器收到预主密钥(或者使用DH算法时计算出预主密钥),发送ChangeCipherSpec消息,表示后续消息使用协商好的主密钥加密;然后发送加密的Finished消息,其中包含握手过程中所有消息的散列值(verify_data字段)。
由于证书校验和密码协商是比较消耗CPU的工作,也增加了连接建立的RTT(Round-Trip Time,往返时延),根据《SSL与TLS》一书中的数据,使用512位RSA时,恢复连接比重建连接握手性能提高了20倍。所以SSL还设计了恢复连接的简单方法,其典型过程如图1-15所示。首次连接时,如果服务器想要支持后续的连接恢复,可以在ServerHello消息中携带sessionID;客户端收到后存储服务器的sessionID和本次产生或计算的预主密钥;下次发起连接时在ClientHello消息中将sessionID带给服务器;服务器如果选择恢复sessionID对应的预主密钥,则将相同的sessionID在ServerHello中返回给客户端,然后直接计算出主密钥,发送ChangeCipherSpec消息和Finished消息。
图1-15 SSL恢复连接的典型过程
这种方法也可用于使用同一个预主密钥快速建立多个连接,这时随机数的作用就凸显了出来,不同的随机数可以为多个连接建立不同的主密钥,从而得到了连接间的安全隔离。
注意 SSLv3于2015年废弃(见RFC 7568),但基本框架和协议过程还存在于TLS的实现中。
1996年5月,IETF组建TLS工作组来标准化传输层安全协议,主要基于SSLv3。由于微软与网景为Web统治权争斗地非常激烈,再加上PKIX(Public Key Infrastructure,公用密钥信息基础设施)工作组陷入停顿,经过漫长的过程,TLS 1.0最终于1999年1月发布为RFC 2246。TLS 1.0可以看作SSLv3的标准化版本,因此也被认为是SSLv3.1,这从版本号也可以看出来,TLS 1.0版本号为0x0301,而SSLv3版本号为0x0300。但TLS 1.0相对于SSLv3也有一些小的改进:定义了基于标准HMAC的PRF(将HMAC-MD5和HMAC-SHA异或),消息认证码使用了标准的HMAC(Hash-based Message Authentication Code,基于散列的消息验证码),补充了一些告警码,改进了证书链的大小(SSL需要完整的证书链,而TLS 1.0只需要到信任的CA),去除了FORTEZZA的支持,规定了必须支持DH、DSS和3DES,将TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA作为唯一强制支持的加密套件。这是因为IETF倾向于使用免费的、经过详细论证、安全的算法(RSA当时还在专利保护期内,于2000年9月过期,DSS则没有这种问题,FORTEZZA则不够透明、没有经过安全性的详细论证)。虽然TLS 1.0和SSLv3非常相似,但不能互操作。
TLS 1.0的基本原理和协议过程跟SSL3.0并没有差别,这里就不重复介绍了。TLS 1.0中密钥衍生的方法并没有变化,但PRF的计算方法改变为:
其中seed使用两端的随机数之和;S1、S2分别是secret的前一半和后一半,如果secret是奇数个字节,S1最后一个字节和S2第一个字节相同;P_MD5和P_SHA-1计算方法为:
主密钥计算方法仍然简单表示为:
然后使用伪随机函数从主密钥、客户端随机数和服务器随机数、衍生标签扩展出不同用途的密钥:
但是后来发现TLS 1.0有一些安全问题,这在之后的TLS 1.1中得以解决。首先是CBC(Cipher Block Chaining,密文分组链接)模式下IV(Initialization Vector,初始化向量)除了第一个记录来自于计算得到的值,之后其他记录的IV来自于上一个记录的最后一个密文块。CBC加密和解密模式原理分别如图1-16和图1-17所示。2011年披露的BEAST攻击就是利用了这个特性 。TLS 1.0得到IV的方式使得主动攻击者可以观察到当前记录的IV,然后猜测一个数据块,将其与IV和前一个密文块进行异或操作,并将得到的数据块注入会话,这样可以检查猜测的数据块是否正确。
图1-16 CBC加密示意图
图1-17 CBC解密示意图
2006年4月,TLS 1.1发布为RFC 4346,修复了一些关键的安全问题,包括:CBC加密使用每条记录一个的显式IV;为了防止CBC填充攻击,使用bad_record_mac错误码代替decryption_failed回复填充错误;支持传输参数的IANA(Internet Assigned Numbers Authority,互联网数字分配机构)注册,增加了传输参数的灵活性;改进了连接关闭过早情况下的连接恢复问题。
IETF从2021年3月25日起废弃了TLS 1.0和TLS 1.1,见RFC 8996,原因是TLS 1.0和TLS 1.1存在一些安全漏洞,还存在一些有明显漏洞的算法,比如RC4在2013年就被证明不安全,2015年IETF在RFC 7465中禁用了RC4;IETF于2011年发布的RFC 6151也指出了MD5的问题。此外,TLS 1.0和TLS 1.1也不支持更新更安全的算法。IETF认为废弃老旧版本更安全,支持的版本少了。实现起来也更简单。
2008年8月,TLS 1.2发布为RFC 5246,主要关注了架构灵活性和安全问题。
TLS协议从1.0到1.1再到1.2,都是对安全方面的问题进行改进,比如删除或增加一些加密套件、改变一些计算方法,但改进的东西并不多,却大大增加了TLS实现和升级的困难。TLS 1.2希望加密套件和算法能更加灵活的指定,不必因为加密算法的更新而实现新协议。在这方面的几点改进如下。
1)在ClientHello中增加了signature_algorithms参数,可以指定自己支持的签名和散列算法列表,签名中增加了一个字段用于指定使用的散列算法。而在TLS 1.1中签名算法来自于证书,不可以在ClientHello中指定。这个改进也意味着签名算法可以不与加密套件绑定。
2)特定PRF由密码套件指定的PRF取代,而非协议固定的算法,TLS 1.0和TLS 1.1都规定了由MD5/SHA-1组合计算的协议特定PRF。这就意味着可以在TLS 1.2中开发使用新的PRF。但TLS 1.2也规定了一些密码套件必须使用P_SHA256。PRF定义为:
P_hash计算方法是:
3)Finish消息中的verify_data的长度可变,取决于密码套件(默认值仍为12)。
4)TLS扩展定义和AES密码套件的规定合并进RFC 4066(后更新为RFC 6066)和RFC 3268。跟具体算法的切割也避免了算法的变化导致协议需要升级。
对于安全方面的改进是TLS版本的必要过程,TLS 1.2的安全改进主要有以下几个方面。
1)增加了对AEAD(Authenticated Encryption with Associated Data关联数据认证加密)的支持。AEAD可以在加密中认证没有加密部分的关联数据,甚至是不在报文中的关联数据,可以保护更大的范围;另外AEAD将消息认证码(Message Authentication Code,MAC)也进行了加密。TLS 1.2中使用的附加数据包括记录的序列号、压缩算法等:
AEAD的计算方法为:
2)规定必须实现密码套件TLS_RSA_WITH_AES_128_CBC_SHA。
3)增加了HMAC-SHA256密码套件。
4)删除了包含已废弃算法的IDEA和DES密码套件。
5)对EncryptedPreMasterSecret版本号进行了更严格的检查。
在使用TLS 1.2首次访问网站的流程中,即使忽略DNS(Domain Name System,域名系统)解析、证书吊销检查等环节,正常也需要3-RTT(Round-Trip Time,往返时延)才能够发送应用数据,如图1-18所示。为了使用户感受到更快的连接,希望能够更早地发送应用数据,在不能够使用连接恢复的情况下,可以使用TLS 1.2的False Start特性(见RFC 7981),如图1-19所示,该特性可以让客户端在收到服务器Finished消息之前就发送应用数据,首次连接发送应用数据只需要2-RTT。
图1-18 使用DH算法的TLS 1.2流程
图1-19 TLS 1.2 False Start特性
TLS 1.3于2018年8月发布(版本号0x0304)为RFC 8446,这是迄今为止TLS改变最大的一次版本升级,跟SSLv3.0的握手过程也开始显著不同。TLS 1.3除增强安全性(如密码套件的选择、密钥的计算方式、握手消息的发送方式)之外,重点改进了连接速度,首次连接发送数据最低可以1-RTT,恢复连接发送数据最低可以0-RTT。
TLS 1.3在安全方面的改进主要有以下几个方面。
1)删除了所有被证明有问题的对称加密算法,只保留了AEAD的加密套件。密码套件的概念也已经改变,将认证和密钥交换机制与加密算法和散列(用于密钥导出函数和握手消息认证码)分离。
2)删除RSA和静态DH密码套件,因为静态RSA加密预主密钥的方式和使用静态DH私钥都不能保证前向安全性,很容易泄露密钥。只保留能保证前向安全的密钥交换算法,如使用临时私钥的ECDHE(Elliptic Curve Diffie-Hellman Ephemeral,椭圆曲线DH临时密钥交换算法)和DHE(Diffie-Hellman Ephemeral,DH临时密钥交换算法)。
3)ServerHello之后的消息都加密传输,像EncryptedExtensions消息、CertificateRequest消息、Certificate消息、CertificateVerify消息等。之前的版本中,TLS扩展在ServerHello消息中以明文发送,新引入的EncryptedExtension消息可以保证服务器扩展以加密方式传输。证书也加密传输,不会被中间人轻易截获,增强了证书的保密性。
4)重新设计了密钥导出函数。使用HKDF(HMAC-based Extract-and-Expand Key Derivation Function,基于HMAC的密钥导出函数,见RFC 5869)作为密钥导出函数。
5)分离了握手密钥和记录密钥,重构了握手状态机,删除了ChangeCipherSpec、ClientKeyExchange等消息。
6)将ECDHE作为基本规范,添加了新的签名算法,如EdDSA(Edwards-Curve Digital Signature Algorithm,爱德华兹曲线数字签名算法),删除了点格式协商。
7)改变RSA填充以使用RSA概率签名方案(RSASSA-PSS),删除DSA和定制DHE组(Ephemeral Diffie-Hellman)。
8)不再使用TLS 1.2的版本协商机制,而是在TLS扩展supported_versions中添加版本列表。为了版本兼容仍然支持ClientHello.legacy_version和ServerHello.legacy_version,但扩展supported_versions在处理上具有更高优先级。
9)改进了会话恢复的方法,使用新的基于PSK(Pre-Shared Key,预共享密钥)的交换方法。
10)删除了压缩功能。之前版本的压缩功能由于存在被攻击的风险实际上很少使用,而且现代的压缩基本都在应用层实现,比如HTTP就自己实现的压缩。
11)删除了之前TLS版本中的重协商功能,增加了握手后客户端认证。
由于第一次数据发送要等好几个RTT(DNS解析、TCP连接、TLS连接)后才能进行,导致用户体验不好。为了尽快发送数据,每个层都做出了努力,如TCP Fast Open。对于TLS来说,业界也做出了一些尝试,如图1-20所示,图1-21a为之前发送数据的方式(客户端收到服务器的Finished消息后再发送数据),图1-21b为改进后的方式(客户端发送完自己的Finished消息后就立即发送数据,不等待服务器的Finished消息)。虽然这样更早地将应用数据发送出去,但也牺牲了部分安全性,因为客户端在没有确定服务器是否可信、消息是否被篡改就发送了应用数据。
TLS 1.3在加快连接速度方面做出了新的尝试,首次连接最低可以在1-RTT后发送数据,如图1-21a所示;恢复连接最低可以0-RTT发送数据,如图1-21b所示。另外,修改了之前版本中使用会话标识恢复连接的做法,改用更安全的ticket来标识特定的PSK,而ticket是在新增加的NewSessionTicket消息中传递的,受1-RTT流量密钥保护。