因为 HTTP 协议是明文的,存在很多缺点,例如:传输内容会被嗅探和篡改。所以在 1994 年,由 Netscape(网景公司)设计并发明了 SSL(Secure Sockets Layer,安全套接层)协议的原始规范,志在解决这些问题。到了 1999 年,SSL 因为应用广泛,已经成为互联网上的事实标准。IETF 就在那年把 SSL 标准化。标准化之后的名称改为 TLS(Transport Layer Security,传输层安全协议)。
至今为止(2020 年),TLS 协议也经过了很多次版本的更新。目前低版本的 TLS (例如:SSL 3.0/TLS 1.0 等)同样被指出了存在许多严重的漏洞。根据 Nist(美国国家标准与技术研究院)所说,现在没有补丁或修复程序能够充分修复低版本 TLS 的漏洞,尽快升级到高版本的 TLS 是最好的方法。
目前,我们最常见的是 TLS 1.2 版本,相对于 TLS 1/1.1,1.2 引入了 SHA-256 哈希算法,摒弃了 SHA-1,对增强数据完整性有着显著优势。而 2018 年发布的 TLS 1.3(RFC 8446)是时隔九年对 TLS 1.2 等之前版本的新升级,也是迄今为止改动最大的一次。针对目前已知的安全威胁,IETF(Internet Engineering Task Force,互联网工程任务组) 正在制定 TLS 1.3 的新标准,使其有望成为有史以来最安全,但也最复杂的 TLS 协议。
实际上现代的浏览器已经基本不使用 SSL,使用的都是 TLS,SSL 3.0 于 2015 年已经寿终正寝,各大浏览器也不支持了。但是由于 SSL 这个术语存在的时间太长,很多地方还是广泛的使用它,但是要清楚其实它说的是 TLS。有调查显示现在绝大部分浏览器(>99.5%)都使用 TLS 1.2 或者 TLS 1.3。只有不足 1% 的浏览器仍然使用 TLS 1.0 或者 TLS 1.1。
TLS 1.2 仍然是主流协议(本文写于 2020 年初),相信将来逐渐 TLS 1.3 将会作为主流协议。
驱使 TLS 1.3 的设计的主要问题如下:
TLS 1.3 与之前的协议有较大差异,主要在于:
对比旧协议中的不足,TLS 1.3 确实可以称得上是向前迈了一大步。既避免之前版本出现的缺陷,也减少了 TLS 握手的时间。
总结一下,TLS 1.3 与以前的版本相比具有如下两个大的优势,分别是:
下面是各大浏览器的 TLS 1.3 支持情况:
目前最新的 Chrome 和 Firefox 都已支持 TLS 1.3,但需要手动开启。
测试网站是否开启了 TLS 1.3:
git clone --depth 1 https://github.com/drwetter/testssl.sh.git
cd testssl.sh
./testssl.sh -p
Testing protocols via sockets except NPN+ALPN
SSLv2 not offered (OK)
SSLv3 not offered (OK)
TLS 1 offered
TLS 1.1 offered
TLS 1.2 offered (OK)
TLS 1.3 offered (OK): draft 28, draft 27, draft 26
NPN/SPDY h2, spdy/3.1, http/1.1 (advertised)
ALPN/HTTP2 h2, spdy/3.1, http/1.1 (offered)
TLS 公钥部分的作用就是建立一个共享秘钥,公钥加密:一方使用另一方的公钥来加密共享秘钥然后直接将其发送出去。然后另一方使用它的私钥解密出共享秘钥。然后,双反都有了相同的秘钥。这个技术是 Rivest,Shamir 和 Adelman 在 1977 年发现的,因此称其为 RSA 秘钥交换。在 TLS 的RSA 秘钥交换中,共享秘钥由客户端来决定,然后将其使用服务端的公钥(从证书导出的)加密,然后发送给服务器。
RSA 有一个严重的缺陷:它不满足前向保密(forward secret)。即:如果有人记录了加密对话,然后获取了服务器的 RSA 私钥,他们可以将对话解密。有这样一种情况,各国政府获得了加密过的对话,然后使用一些比如心脏出血(Heartbleed)之类的技术来偷取私钥,就可以进行解密。
因此,TLS 1.3 移除了 RSA 加密方式。保留了临时 Diffie-Hellman 方式作为唯一的秘钥交换机制。
它是由 Diffie 和 Hellman 在 1976 年发明的,因此成为 Diffie-Hellman 秘钥协议。在 Diffie-Hellman 协议中,客户端和服务器都会创建一个公私钥对,由此作为开始。然后他们将公钥部分发送给另一方。当每一方都收到了对方的公钥,他们使用各自的私钥将其组合,并以相同的值作为结尾:即 pre-master secret。然后服务器使用数字签名来保证交换的数据没有被篡改。如果客户端和服务器在每一个数据交互中都选择一个新的秘钥对,这种秘钥交换则称为 “临时秘钥(ephemeral)”。
ClientHello 消息包含:
ServerHello 消息包含:
随后服务器发送服务器的安全证书(含公钥)。如果需要客户端也提供证书的话,还会发出客户端证书请求(Client Certificate Request),只有少数金融机构才需要客户端也提供客户端证书。
此后客户端发送 Server Hello Done 消息表示 Hello 阶段完成。
我们来看一下为什么可以通过 CA(Certificate Authority,证书颁发机构)签发的证书来确认网站的身份?
当我们安装操作系统或者浏览器的时候,会安装一组可信任的 CA(根证书 CA 包括 GlobalSign、GeoTrust、Verisign 等)列表。根 CA(如 GlobalSign)就在我们的可信任的 CA 列表里,你的浏览器或者操作系统含有 GlobalSign 的公钥。
先来看一下 Google 的证书,当你访问 Google 的时候,Google 会发给你它的证书。证书中包含颁发机构的签名以及服务器的公钥。
浏览器首先用哈希函数对明文信息的摘要做哈希得到一个哈希值(用到的就是证书中的签名哈希算法 SHA256),然后用根 CA 的公钥对根证书的签名作解密得到另一个哈希值(用到的算法就是 RSA 非对称算法)。如果两个哈希值相等则说明证书没有被篡改过。当然还需校验证书中服务器名称是否合法以及验证证书是否过期。
这样就免受中间人攻击了,因为假如有中间人修改了证书的内容(如将证书中的公钥替换成自己的公钥),那么将获得不同的哈希值,从而两个哈希值不匹配导致验证失败。
如果要绕过这个机制,中间人必须要也替换签名,使签名也相匹配。而做到这一点就需要破解到了根证书的密钥(而这是不可能的,中间人必然会失败)。
浏览器会出现以下画面,告诉你正在遭受中间人攻击,因为证书被篡改了:
密钥交换过程:客户端用第三步中服务器的证书中拿到服务器的公钥,用这个公钥加密(算法是加密套件中的密钥交换算法,譬如 ECDHE 算法)生成密文发送给服务器。
客户端用 server-random+client-random+pre-master 一起计算出对称密钥 master secret。
服务器收到第四步的信息之后,用服务器的私钥对密文进行解密得到密钥 pre-master。
因为只有服务器有私钥,可以针对客户端发出的加密过的信息进行解密得到 pre-master,这样就保证了只有服务器和客户端知道 pre-master。服务器端也可以用 server-random+client-random+pre-master 一起计算出对称密钥 master secret。
现在客户端和服务器均有密钥 master secret 了,后面就可以用它来进行加密和解密了。
综上,整个握手过程主要是通过一系列步骤通过非对称加密的算法交换得到了 master secret,这个步骤通常需要几百毫秒,但是就是这一顿猛操作之后使得只有服务器和客户端知道 master secret。
之后的通信又利用了高效的对称算法对所有信息进行加密和解密,虽然加密和解密也需要耗时耗流量,不过信息是完全不可能被别人篡改和破解的,这一点损耗还是值得的。
在 TLS 1.2 及更早版本中,服务器的签名仅涵盖了部分的握手通信流程。而其他部分,特别是用于协商使用哪个对称密码的部分,不使用私钥进行签名。相反,使用对称 MAC 来确保握手未被篡改。这种疏忽导致了许多备受瞩目的漏洞(e.g. FREAK、LogJam etc.)。在 TLS 1.3 中,这些疏忽被避免了,因为服务器将对整个握手记录进行签名。
FREAK,LogJam 和 CurveSwap 攻击利用了以下两个事实:
“中间人” 攻击者可以通过服务器支持的易于破解的选择从客户端得到所支持的密码(或支持的群组或支持的曲线)。之后他们会打破其关键并伪造了两条已完成的消息,让双方都认为他们已经同意了传输协议。
这些攻击被称为降级攻击,它们允许攻击者强制两个参与者使用双方支持的最弱密码,即使支持更安全的密码也是如此。在这种攻击方式中,肇事者处于握手的中间,并将从客户端通告的服务器支持的密码列表更改为仅包含弱导出密码。然后,服务器选择一个弱密码,攻击者通过暴力攻击计算出密钥,允许攻击者在握手时伪造 MAC。
简而言之,TLS 的发展有 20 多年的历史,在之前的版本中,TLS 1.2 是高度可配置的,为了更好的兼容旧版本的浏览器,这意味着那些易受攻击的站点始终在运行着不安全的加密算法,这让互联网黑客有可乘之机。
在 TLS 1.3 中,这种类型的降级攻击是不可能的,因为服务器现在签署了整个握手,包括密码协商。
并且 TLS 1.3 在之前版本的基础上删除了那些不安全的加密算法,这些加密算法包括:
总之,TLS 1.3 相比老版本的 TLS 协议将会更加安全,这也代表着互联网安全的一大进步。
为了让浏览器和 Web 服务器之间协商秘钥,它们需要交换密码数据。这个交换,在 TLS 中称为 “握手”,这个过程从 1999 年 TLS 1.0 标准化以来一直保持不变。在加密数据发送之前(或者重新开始之前的连接的时候),握手在浏览器和服务器之间需要两次(2-RTT)额外的往返交互。与单独使用 HTTP 通信相比,HTTPS 中的 TLS 握手产生的额外代价会对延迟产生明显的影响。
TLS 1.2 建立新连接的流程,需要两次往返( 2-RTT )才能完成握手,然后才能发送请求:
在一次完整协商的连接断开时,客户端和服务器都会将会话的安全参数保留一段时间。希望使用会话恢复的服务器会为会话指定唯一的标识,称为会话 ID。
而 TLS 1.3 现在具有更简单的密码协商模型和一组瘦身后的密钥协商选项(没有 RSA,没有用户定义的 Diffie-Hellman 参数)。这意味着每个连接都将使用基于 Diffie-Hellman 的密钥协议,并且服务器所支持的参数很容易被猜到(使用 X25519 或 P-256 的 ECDHE)。由于这种有限的选择,客户端可以简单地选择在第一条消息中发送 Diffie-Hellman 密钥共享,而不是等到服务器确认它希望支持哪些密钥共享。这样,服务器可以获知已共享密钥并提前一次往返发送加密数据。
简而言之,而 TLS 1.3 的握手不再支持静态的 RSA 密钥交换,这意味着必须使用带有前向安全的 Diffie-Hellman 进行全面握手。只需要一次往返( 1-RTT )就可以完成握手。
在 TLS 1.2 中,有两种方法可以用于恢复连接,会话 ID 和会话 Ticket。而在 TLS 1.3 中,这些被组合起来以形成称为 PSK(pre-shared key,预共享密钥)恢复的新模式。其思路是在建立会话之后,客户端和服务器可以得到称为 “恢复主密钥” 的共享密钥。这可以使用 ID(会话 ID 样式)存储在服务器上,也可以通过仅为服务器所知的密钥(会话 Ticket 样式)进行加密。此会话 Ticket 将发送到客户端并在恢复连接时进行查兑。
也就是说,对于已恢复的连接,双方共享恢复主密钥,因此除了提供前向保密之外,不需要交换密钥。下次客户端连接到服务器时,它可以从上一个会话中获取秘密并使用它来加密应用程序数据以及会话 Ticket 发送到服务器。像第一次飞行中发送加密数据一样惊人的事情确实伴随着它的衰落。
TLS 1.2 中通过 1 个 RTT 即可完成会话恢复,那么 TLS 1.3 是如何做到 0 RTT 连接的?
当一个支持 TLS 1.3 的客户端连接到同样支持 TLS 1.3 的服务器时, 客户端会将收到服务器发送过来的 Ticket 通过相关计算,一起组成新的 预共享密钥,PSK (PreSharedKey)。客户端会将该 PSK 缓存在本地,在会话恢复时在 Client Hello 上带上 PSK 扩展,同时通过之前客户端发送的完成(finished)计算出恢复密钥 (Resumption Secret)通过该密钥加密数据发送给服务器。服务器会从会话 Ticket 中算出 PSK,使用它来解密刚才发过来的加密数据。至此完成了该 0-RTT 会话恢复的过程。
可见,相比 TLS 1.2,TLS 1.3 的握手时间减半。这意味着访问一个移动端网站,使用 TLS 1.3 协议,可能会减少将近 100ms 的时间。
OpenSSL 最新的 1.1.1 版本提供了 TLS 1.3 的支持,而且和 1.1.0 版本完全兼容。OpenSSL 详见《使用 OpenSSL 自建 CA 中心并签发 CA 证书》。
CentOS7 检查是否开启了 TLS 1.3:
openssl s_client --help
查看是否存在 “-tls1_3” 选项,若存在,则表示开启成功。
CentOS7 开启 TLS 1.3:
wget https://github.com/openssl/openssl/archive/OpenSSL_1_1_1-stable.zip
unzip OpenSSL_1_1_1-stable.zip
./config enable-tls1_3 --prefix=/usr/local/openssl
make && make install
mv /usr/bin/openssl /usr/bin/openssl.old
mv /usr/lib64/openssl /usr/lib64/openssl.old
mv /usr/lib64/libssl.so /usr/lib64/libssl.so.old
ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl
ln -s /usr/local/openssl/include/openssl /usr/include/openssl
ln -s /usr/local/openssl/lib/libssl.so /usr/lib64/libssl.so
echo "/usr/local/openssl/lib" >> /etc/ld.so.conf
ldconfig -v
openssl version
openssl s_client --help
TLS 1.3 是一次激进的版本更迭,但为了广泛部署,它依旧支持向后兼容。但实际上,在像互联网一样复杂的生态系统中升级安全协议是困难的,你需要更新客户端和服务器,并确保它们连接起来都能继续正常工作。2016 年,Cloudflare 是第一个在服务器端默认支持 TLS 1.3 的供应商。但至今仍然没有一个主流的浏览器默认启用 TLS 1.3。
中间件(middlebox)网络设备不兼容:TLS 1.3 是完全不兼容老版本的,HTTPS 协议是 TLS 协议最大的是使用者,本质上是 HTTP 协议。而 HTTP 应用非常的灵活,在互联网中,有很多的代理服务器或网关,他们为了各种各样的目的,都会透明解析 TLS 协议,这些网络设备会被用于监视并拦截企业环境和移动网络中的 HTTPS 流量。这些网络设备是能够解析 TLS 1.2 的,但 TLS 1.3 版本的解析规则很可能在这些设备上失效。那么对于用户来说,他们可能就无法使用 TLS 1.3 协议了,这也是 TLS 1.3 协议非常重要的一个问题。
客户端支持有限:当前还有很多客户端根本不支持较新的一些密码套件,从兼容性的角度看,老版本的 TLS 仍然会存在很长的生命周期。
认知度和可信任度还比较欠缺:很多服务器的 OpenSSL 版本还比较久,而且很多服务都会使用到 OpenSSL,所以 OpenSSL 是轻易不敢升级的。还得等到 Linux 发版本发布 OpenSSL 1.1.1 的 APT 或 RPM 包,才能表明这个版本被普遍接纳。
总结来说,TLS 1.3 的普及还需要很长时间。
并非所有的客户端和服务器都支持相同版本的 TLS,因此大多数服务段都会同时支持多个版本,并且客户端和服务器会进行协商。TLS 的版本协商非常简单。客户端会通知服务端它支持的协议的最新版本,服务端则会回复支持的协议版本。
建立 TLS 连接时,客户端会在连接开始时发送其支持的最高版本:
(3, 3) → server
假如服务端支持相同的版本,则回复:
client ← (3, 3)
假如服务端的版本更低,则使用旧版本进行回复。比如,服务器只支持 TLS 1.0,则回复:
client ← (3, 1)
如果客户端同时也支持服务端返回的版本,则他们就可以继续使用该版本的 TLS 建立安全连接了。否则,连接失败。
虽然版本协商的过程很简单,但事实证明,很多连接场景并未能正确地实现这一功能,从而导致安全事故。
https://www.oschina.net/translate/rfc-8446-aka-tls-1-3