编程随想|扫盲 HTTPS 和 SSL/TLS 协议

2022/08/16 HTTPS 共 23278 字,约 67 分钟

原文链接 https://program-think.blogspot.com/2014/11/https-ssl-tls-0.html

引子

今天这篇算是补之前的欠债——俺在4年前写过几篇关于 CA 证书的扫盲(“这里”和“这里”),之后有不止一位热心读者建议俺写一篇关于 HTTPS 的扫盲。因为俺比较懒,当时没动笔,一拖就是两三年,都有点忘了。正好今年出了两个跟 HTTPS 相关的高危漏洞(HeartbleedPODDLE),于是俺又想起这事儿。

本来想单独写一篇。等写完“背景知识”这一章节,发现篇幅已经很长了。所以就再开一个系列吧。

事先声明:
既然叫做“扫盲”,所以俺尽量避免讲太多的“技术实现细节”(当然,更不会去讲“代码实现”)。本系列侧重于:尽可能通俗地介绍“设计思路”、“实现原理”,最后再聊聊“针对 HTTPS 的攻击手法”和“相关的安全防范措施”。一开始计划写3~4篇,后来篇幅有点失控,估计要写7~8篇。

虽然是扫盲,或许也能让 IT 技术人员从中获益——因为俺发现:连安全行业的某些程序员,对 HTTPS 的原理也所知甚少。

第一节:背景知识、协议的需求、设计的难点

相关背景知识

要说清楚 HTTPS 协议的实现原理,至少需要如下几个背景知识。

  1. 大致了解几个基本术语(HTTPS、SSL、TLS)的含义
  2. 大致了解 HTTP 和 TCP 的关系(尤其是“短连接”VS“长连接”)
  3. 大致了解加密算法的概念(尤其是“对称加密与非对称加密”的区别)
  4. 大致了解 CA 证书的用途

考虑到很多技术菜鸟可能不了解上述背景,俺先用最简短的文字描述一下。如果你自认为不是菜鸟,请略过本章节,直接去看“HTTPS 协议的需求”。

先澄清几个术语——HTTPS、SSL、TLS

1.“HTTP”是干嘛用滴?
首先,HTTP 是一个网络协议,是专门用来帮你传输 Web 内容滴。关于这个协议,就算你不了解,至少也听说过吧?比如你访问俺的博客的主页,浏览器地址栏会出现如下的网址:
http://program-think.blogspot.com/
俺加了粗体的部分就是指 HTTP 协议。大部分网站都是通过 HTTP 协议来传输 Web 页面、以及 Web 页面上包含的各种东东(图片、CSS 样式、JS 脚本)。
(注:当年写这篇的时候,Google 的 blogspot 博客平台【尚未】支持全站 HTTPS,所以在上述举例中,主页的网址以 http:// 开头)

2.“SSL/TLS”是干嘛用滴?
SSL 是洋文“Secure Sockets Layer”的缩写,中文叫做“安全套接层”。它是在上世纪90年代中期,由网景公司设计的。(顺便插一句,网景公司不光发明了 SSL,还发明了很多 Web 的基础设施——比如“CSS 样式表”和“JS 脚本”)

为啥要发明 SSL 这个协议捏?因为原先互联网上使用的 HTTP 协议是明文的,存在很多缺点——比如传输内容会被偷窥(嗅探)和篡改。发明 SSL 协议,就是为了解决这些问题。

到了1999年,SSL 因为应用广泛,已经成为互联网上的事实标准。IETF 就在那年把 SSL 标准化。标准化之后的名称改为 TLS(是“Transport Layer Security”的缩写),中文叫做“传输层安全协议”。 很多相关的文章都把这两者并列称呼(SSL/TLS),因为这两者可以视作同一个东西的不同阶段。

3.“HTTPS”是啥意思?
解释完 HTTP 和 SSL/TLS,现在就可以来解释 HTTPS 啦。咱们通常所说的 HTTPS 协议,说白了就是“HTTP 协议”和“SSL/TLS 协议”的组合。你可以把 HTTPS 大致理解为——“HTTP over SSL”或“HTTP over TLS”(反正 SSL 和 TLS 差不多,你可以把这俩当作同义词)。

再来说说 HTTP 协议的特点

作为背景知识介绍,还需要再稍微谈一下 HTTP 协议本身的特点。HTTP 本身有很多特点,考虑到篇幅有限,俺只谈那些和 HTTPS 相关的特点。

1.HTTP 的版本和历史
如今咱们用的 HTTP 协议,版本号是 1.1(也就是 HTTP 1.1)。这个 1.1 版本是1995年底开始起草的(技术文档是 RFC2068),并在1999年正式发布(技术文档是 RFC2616)。
在 1.1 之前,还有曾经出现过两个版本“0.9 和 1.0”,其中的 HTTP 0.9 【没有】被广泛使用,而 HTTP 1.0 被广泛使用过。
另外,据说明年(2015)IETF 就要发布 HTTP 2.0 的标准了。俺拭目以待。

2.HTTP 和 TCP 之间的关系
简单地说,TCP 协议是 HTTP 协议的基石——HTTP 协议需要依靠 TCP 协议来传输数据。
在网络分层模型中,TCP 被称为“传输层协议”,而 HTTP 被称为“应用层协议”。有很多常见的应用层协议是以 TCP 为基础的,比如“FTP、SMTP、POP、IMAP”等。
TCP 被称为“面向连接”的传输层协议。关于它的具体细节,俺就不展开了(否则篇幅又失控了)。你只需知道:传输层主要有两个协议,分别是 TCP 和 UDP。TCP 比 UDP 更可靠。你可以把 TCP 协议想象成某个水管,发送端这头进水,接收端那头就出水。并且 TCP 协议能够确保,先发送的数据先到达(与之相反,UDP 不保证这点)。

3.HTTP 协议如何使用 TCP 连接?
HTTP 对 TCP 连接的使用,分为两种方式:俗称“短连接”和“长连接”(“长连接”又称“持久连接”,洋文叫做“Keep-Alive”或“Persistent Connection”)
假设有一个网页,里面包含好多图片,还包含好多【外部的】CSS 文件和 JS 文件。在“短连接”的模式下,浏览器会先发起一个 TCP 连接,拿到该网页的 HTML 源代码(拿到 HTML 之后,这个 TCP 连接就关闭了)。然后,浏览器开始分析这个网页的源码,知道这个页面包含很多外部资源(图片、CSS、JS)。然后针对【每一个】外部资源,再分别发起一个个 TCP 连接,把这些文件获取到本地(同样的,每抓取一个外部资源后,相应的 TCP 就断开)
相反,如果是“长连接”的方式,浏览器也会先发起一个 TCP 连接去抓取页面。但是抓取页面之后,该 TCP 连接并不会立即关闭,而是暂时先保持着(所谓的“Keep-Alive”)。然后浏览器分析 HTML 源码之后,发现有很多外部资源,就用刚才那个 TCP 连接去抓取此页面的外部资源。

在 HTTP 1.0 版本,【默认】使用的是“短连接”(那时候是 Web 诞生初期,网页相对简单,“短连接”的问题不大);
到了1995年底开始制定 HTTP 1.1 草案的时候,网页已经开始变得复杂(网页内的图片、脚本越来越多了)。这时候再用短连接的方式,效率太低下了(因为建立 TCP 连接是有“时间成本”和“CPU 成本”滴)。所以,在 HTTP 1.1 中,【默认】采用的是【Keep-Alive】的方式。
【HTTP Keep-Alive】有时候也叫做“HTTP persistent connection”或“HTTP connection reuse”。关于它的更多介绍,可以参见维基百科词条(在“这里”)

谈谈“对称加密”和“非对称加密”的概念

1.啥是“加密”和“解密”?
通俗而言,你可以把“加密”和“解密”理解为某种【互逆的】数学运算。就好比“加法和减法”互为逆运算、“乘法和除法”互为逆运算。
“加密”的过程,就是把“明文”变成“密文”的过程;反之,“解密”的过程,就是把“密文”变为“明文”。在这两个过程中,都需要一个关键的东东——叫做“密钥”——来参与数学运算。

2.啥是“对称加密”?
所谓的“对称加密技术”,意思就是说:“加密”和“解密”使用【相同的】密钥。这个比较好理解。就好比你用 7zip 或 WinRAR 创建一个带密码(口令)的加密压缩包。当你下次要把这个压缩文件解开的时候,你需要输入【同样的】密码。在这个例子中,密码/口令就如同刚才说的“密钥”。

3.啥是“非对称加密”?
所谓的“非对称加密技术”,意思就是说:“加密”和“解密”使用【不同的】密钥。这玩意儿比较难理解,也比较难想到。当年“非对称加密”的发明,还被誉为“密码学”历史上的一次革命。
由于篇幅有限,对“非对称加密”这个话题,俺就不展开了。有空的话,再单独写一篇扫盲。

4.各自有啥优缺点?
看完刚才的定义,很显然:(从功能角度而言)“非对称加密”能干的事情比“对称加密”要多。这是“非对称加密”的优点。但是“非对称加密”的实现,通常需要涉及到“复杂数学问题”。所以,“非对称加密”的性能通常要差很多(相对于“对称加密”而言)。
这两者的优缺点,也影响到了 SSL 协议的设计。

CA 证书的原理及用途

关于这方面,请看俺4年前写的《数字证书及 CA 的扫盲介绍》。这里就不再重复唠叨了,免得篇幅太长。

HTTPS 协议的【需求】是啥?

花了好多口水,终于把背景知识说完了。下面正式进入正题。先来说说当初设计 HTTPS 是为了满足哪些需求?

很多介绍 HTTPS 的文章一上来就给你讲实现细节。个人觉得:这是【不好】的做法。早在2009年开博的时候,发过一篇《学习技术的三部曲:WHAT、HOW、WHY》,其中谈到“WHY 型问题”的重要性。如果一上来就丢给你一大堆协议细节,你充其量只能知道 WHAT 和 HOW,无法理解 WHY。俺在前一个章节讲了“背景知识”,在这个章节讲了“需求”,这就有助于你理解:当初【为什么】要设计成这样?——这就是 WHY 型的问题。

兼容性

因为是先有 HTTP 再有 HTTPS。所以,HTTPS 的设计者肯定要考虑到对原有 HTTP 的兼容性。

这里所说的兼容性包括很多方面。比如已有的 Web 应用要尽可能无缝地迁移到 HTTPS;比如对浏览器厂商而言,改动要尽可能小;……

基于“兼容性”方面的考虑,很容易得出如下几个结论:
1.HTTPS 还是要基于 TCP 来传输
(如果改为 UDP 作传输层,无论是 Web 服务端还是浏览器客户端,都要大改——动静太大,伤筋动骨) 2.单独使用一个新的协议,把 HTTP 协议包裹起来
(所谓的“HTTP over SSL”,实际上是在原有的 HTTP 数据外面加了一层 SSL 的封装。HTTP 协议原有的 GET、POST 之类的机制,基本上原封不动)

打个比方:如果原来的 HTTP 是塑料水管,容易被戳破;那么如今新设计的 HTTPS 就像是在原有的塑料水管之外,再包一层金属水管。一来,原有的塑料水管照样运行;二来,用金属加固了之后,不容易被戳破。

可扩展性

前面说了,HTTPS 相当于是“HTTP over SSL”。
如果 SSL 这个协议在“可扩展性”方面的设计足够牛逼,那么它除了能跟 HTTP 搭配,还能够跟其它的应用层协议搭配。岂不美哉?

现在看来,当初设计 SSL 的人确实比较牛。如今的 SSL/TLS 可以跟很多常用的应用层协议(比如:FTP、SMTP、POP、Telnet)搭配,来强化这些应用层协议的安全性。

接着刚才打的比方:如果把 SSL/TLS 视作一根用来加固的金属管,它不仅可以用来加固输水的管道,还可以用来加固输煤气的管道。

保密性(防泄密)

HTTPS 需要做到足够好的保密性。
说到保密性,首先要能够对抗“嗅探”(圈内行话叫 Sniffer)。所谓的“嗅探”,通俗而言就是监视你的网络传输流量。如果你使用【明文】的 HTTP 上网,那么监视者通过嗅探,就知道你在访问哪些网站的哪些页面。

嗅探是最低级的攻击手法。除了嗅探,HTTPS 还需要能对抗其它一些稍微高级的攻击手法——比如“重放攻击”(后面讲协议原理的时候,会再聊)。

完整性(防篡改)

除了“保密性”,还有一个同样重要的目标是“确保完整性”。关于“完整性”这个概念,在之前的博文《扫盲文件完整性校验——关于散列值和数字签名》中大致提过。健忘的同学再去温习一下。

在发明 HTTPS 之前,由于 HTTP 是明文的,不但容易被嗅探,还容易被篡改。 举个例子:
比如咱们天朝的网络运营商(ISP)都比较流氓,经常有网友抱怨说访问某网站(本来是没有广告的),竟然会跳出很多中国电信的广告。为啥会这样捏?因为你的网络流量需要经过 ISP 的线路才能到达公网。如果你使用的是明文的 HTTP,ISP 很容易就可以在你访问的页面中植入广告。

所以,当初设计 HTTPS 的时候,还有一个需求是“确保 HTTP 协议的内容【不】被篡改”。

真实性(防假冒)

在谈到 HTTPS 的需求时,“真实性”经常被忽略。其实“真实性”的重要程度【不亚于】前面的“保密性”和“完整性”。

举个例子:
你因为使用网银,需要访问该网银的 Web 站点。那么,你如何确保你访问的网站确实是你想访问的网站?(这话有点绕口令)

有些天真的同学会说:通过看网址里面的域名,来确保。为啥说这样的同学是“天真的”?因为 DNS 系统本身是不可靠的(尤其是在设计 SSL 的那个年代,连 DNSSEC 都还没发明)。由于 DNS 的不可靠(存在“域名欺骗”和“域名劫持”),你看到的网址里面的域名【未必】是真实滴!

(不了解“域名欺骗”和“域名劫持”的同学,可以参见俺之前写的《扫盲 DNS 原理,兼谈“域名劫持”和“域名欺骗/域名污染”》)

所以,HTTPS 协议必须有某种机制来确保“真实性”的需求(至于如何确保,后面会细聊)。

性能

再来说最后一个需求——性能。
引入 HTTPS 之后,【不能】导致性能变得太差。否则的话,谁还愿意用? 为了确保性能,SSL 的设计者至少要考虑如下几点:

  1. 如何选择加密算法(对称加密 or 非对称加密)?
  2. 如何兼顾 HTTP 采用的【短连接】TCP 方式?

(再次提醒:SSL 是在1995年之前开始设计滴。那是 Web 的远古时代——HTTP 协议版本还只是 1.0;默认使用【短连接】的 TCP 方式;默认【不】启用 Keep-Alive)

小结

以上就是设计 SSL 协议时,必须兼顾的各种需求。后面聊协议的实现时,俺会拿 SSL 协议的特点跟前面的需求作对照。看看这些需求是如何被逐一满足滴。

设计 HTTPS 协议的主要【难点】是啥?

设计 HTTPS 这个协议,有好几个难点。俺个人认为:“密钥交换”是最大的难点(没有之一)。

在传统的密码学场景中,假如张三要跟李四建立一个加密通讯的渠道,双方事先要约定好使用哪种加密算法?同时也要约定好使用的密钥是啥?在这个场景中,加密算法的【类型】让旁人知道,没太大关系。但是密钥【千万不能】让旁人知道。一旦旁人知道了密钥,自然就可以破解通讯的密文,得到明文。

好,现在回到 HTTPS 的场景。
当你访问某个公网的网站,你的浏览器和网站的服务器之间,如果要建立加密通讯,必然要商量好双方使用啥算法,啥密钥。——在网络通讯术语中,这个过程称之为“握手”(洋文叫“handshake”)。在握手阶段,因为加密方式还没有协商好,所以握手阶段的通讯必定是【明文】滴!既然是明文,自然有可能被第三方偷窥到。然后,还要考虑到双方之间隔着一个【互联网】,啥样的事情都可能发生(不光会有“数据偷窥”,还会有【数据篡改】)。

因此,在握手的过程中,如何做到安全地交换密钥信息,而不让周围的第三方看到。这就是设计 HTTPS 最大的难点。

本节结尾

本文费这么多口水,来介绍 HTTPS 的“需求”和“难点”,为啥捏?因为只有当你了解这些,后面介绍 SSL/TLS 的实现原理时,你才能理解——当初为啥要把协议设计成这个样子。

第二节:可靠密钥交换的难点,以及身份认证的必要性

先插播一个安全通告

说来凑巧,就在本系列刚开播之后没几天(11月11日),微软爆了一个跟 SSL/TLS 相关的高危漏洞,影响【几乎所有的】Windows 平台。至此,【所有】主流的 SSL/TLS 协议栈(至少包括:开源的 OpenSSL、开源的 GnuTLS、微软的 SSP、苹果的 SecureTransport),全都在今年爆了高危漏洞。看来俺这个系列生逢其时啊!
个人觉得:【2014年】必将在信息安全历史上留下醒目的记录。
用 Windows 系统的同学,这几天要尽快升级微软的“安全更新”。因为该漏洞会导致“远程代码执行”,非常危险。
(微软的公告中没有提及 Win2000 和 WinXP 是因为这俩已经过了“产品支持周期”。【不】等于说这俩没问题)

上一节,已经介绍了相关的背景知识以及设计 SSL 需要考虑的需求。当时俺提到:设计 HTTPS 的最大难点(没有之一)是——如何在互联网上进行安全的“密钥交换”。今天就来讲讲密钥交换的难点和解决方法(暂不谈技术实现)。

方案1——单纯用“对称加密算法”的可行性

首先简单阐述一下,“单纯用对称加密”为啥是【不可行】滴。
如果“单纯用对称加密”,浏览器和网站之间势必先要交换“对称加密的密钥”。
如果这个密钥直接用【明文】传输,很容易就会被第三方(有可能是“攻击者”)偷窥到;如果这个密钥用密文传输,那就再次引入了“如何交换加密密钥”的问题——这就变成“先有鸡还是先有蛋”的循环逻辑了。
所以,【单纯用】对称加密,是没戏滴。

方案2——单纯用“非对称加密算法”的风险

说完“对称加密”,再来说说“非对称加密”。
在本文的上一节谈“背景知识”的时候,已经大致介绍过“非对称加密”的特点——“加密和解密采用【不同】的密钥”。基于这个特点,可以避开前面提到的“循环逻辑”的困境。大致的步骤如下:

第1步
网站服务器先基于“【非】对称加密算法”,随机生成一个“密钥对”(为叙述方便,称之为“k1 和 k2”)。因为是随机生成的,目前为止,只有网站服务器才知道 k1 和 k2。

第2步
网站把 k1 保留在自己手中,把 k2 用【明文】的方式发送给访问者的浏览器。
因为 k2 是明文发送的,自然有可能被偷窥。不过不要紧。即使偷窥者拿到 k2,也【极难】 根据 k2 推算出 k1(注:这是由“非对称加密算法”从数学上保证滴)

第3步
浏览器拿到 k2 之后,先【随机生成】第三个对称加密的密钥(简称 k)。
然后用 k2 加密 k,得到 k’(k’ 是 k 的加密结果)
浏览器把 k’ 发送给网站服务器。

由于 k1 和 k2 是成对的,所以只有 k1 才能解密 k2 的加密结果。
因此这个过程中,即使被第三方偷窥,第三方也【无法】从 k’ 解密得到 k

第4步
网站服务器拿到 k’ 之后,用 k1 进行解密,得到 k
至此,浏览器和网站服务器就完成了密钥交换,双方都知道 k,而且【貌似】第三方无法拿到 k
然后,双方就可以用 k 来进行数据双向传输的加密。

现在,给大伙儿留一点【思考时间】——你觉得上述过程是否严密?如果不严密,漏洞在哪里?

OK,现在俺来揭晓答案(希望你没有事先偷看)
“方案2”依然是【不】安全滴——虽然“方案2”可以在一定程度上防止网络数据的“偷窥/嗅探”,但是【无法】防范网络数据的【篡改】。
假设有一个攻击者处于“浏览器”和“网站服务器”的通讯线路之间,并且这个攻击者具备“【修改】双方传输数据”的能力。那么,这个攻击者就可以攻破“方案2”。具体的攻击过程如下:

第1步
这一步跟原先一样——服务器先随机生成一个“非对称的密钥对”k1 和 k2(此时只有网站知道 k1 和 k2)

第2步
当网站发送 k2 给浏览器的时候,攻击者截获 k2,保留在自己手上。
然后攻击者自己生成一个【伪造的】密钥对(以下称为 pk1 和 pk2)。
攻击者把 pk2 发送给浏览器。

第3步
浏览器收到 pk2,以为 pk2 就是网站发送的。
浏览器不知情,依旧随机生成一个对称加密的密钥 k,然后用 pk2 加密 k,得到密文的 k’ 浏览器把 k’ 发送给网站。
(以下是关键)
发送的过程中,再次被攻击者截获。
因为 pk1 pk2 都是攻击者自己生成的,所以攻击者自然就可以用 pk1 来解密 k’ 得到 k
然后,攻击者拿到 k 之后,用之前截获的 k2 重新加密,得到 k’‘,并把 k’’ 发送给网站。

第4步
网站服务器收到了 k’’ 之后,用自己保存的 k1 可以正常解密,所以网站方面不会起疑心。 至此,攻击者完成了一次漂亮的偷梁换柱,而且让双方都没有起疑心。

上述过程,也就是传说中大名鼎鼎的【中间人攻击】(洋文叫做“Man-In-The-Middle attack”,缩写是 MITM)。
“中间人攻击”有很多种“类型”,刚才演示的是针对“【单纯的】非对称加密”的中间人攻击。至于“中间人攻击”的其它类型,俺在本系列的后续博文中,还会再提到。

为了更加形象,补充两张示意图,分别对应“偷窥模式”和“中间人模式”。让你更直观地体会两者的差异。

方案2失败的根源——缺乏【可靠的】身份认证

为啥“方案2”会失败捏?
除了俺在图中提到的“攻击者具备篡改数据的能力”,还有另一点关键点——“方案2缺乏身份认证机制”。
正是因为“缺乏身份认证机制”,所以当攻击者一开始截获 k2 并把自己伪造的 pk2 发送给浏览器时,浏览器无法鉴别:自己收到的密钥是不是真的来自于网站服务器。
假如具备某种【可靠的】身份认证机制,即使攻击者能够篡改数据,但是篡改之后的数据很容易被识破。那篡改也就失去了意义。

【身份认证】的几种方式

下面,俺来介绍几种常见的“身份认证原理”。

基于某些“私密的共享信息”

为了解释“私密的共享信息”这个概念,咱们先抛开“信息安全”,谈谈日常生活中的某个场景。
假设你有一个久未联系的老朋友。因为时间久远,你已经没有此人的联系方式了。某天,此人突然给你发了一封电子邮件。
那么,你如何确保——发邮件的人确实是你的老朋友捏?
有一个办法就是:你用邮件向对方询问某个私密的事情(这个事情只有你和你的这个朋友知道,其他人不知道)。如果对方能够回答出来,那么对方【很有可能】确实是你的老朋友。
从这个例子可以看出,如果通讯双方具有某些“私密的共享信息”(只有双方知道,第三方不知道),就能以此为基础,进行身份认证,从而建立信任。

基于双方都信任的“公证人”

“私密的共享信息”,通常需要双方互相比较熟悉,才行得通。如果双方本来就互不相识,如何进行身份认证以建立信任关系捏?
这时候还有另一个办法——依靠双方都信任的某个“公证人”来建立信任关系。
如今 C2C 模式的电子商务,其实用的就是这种方式——由电商平台充当公证人,让买家与卖家建立某种程度的信任关系。
考虑到如今的网购已经相当普及,大伙儿应该对这类模式很熟悉吧。所以俺就不浪费口水了。

如何解决 SSL 的【身份认证】问题——CA 的引入

说完身份认证的方式/原理,再回到 SSL/TLS 的话题上。
对于 SSL/TLS 的应用场景,由于双方(“浏览器”和“网站服务器”)通常都是素不相识滴,显然【不可能】采用第一种方式(私密的共享信息),而只能采用第二种方式(依赖双方都信任的“公证人”)。
那么,谁来充当这个公证人捏?这时候,CA 就华丽地登场啦。
所谓的 CA,就是“数字证书认证机构”的缩写,洋文全称叫做“Certificate Authority”。关于 CA 以及 CA 颁发的“CA 证书”,俺已经写过一篇教程:《数字证书及 CA 的扫盲介绍》,介绍其基本概念和功能。所以,此处就不再重复唠叨了。
如果你看完那篇 CA 的扫盲,你自然就明白——CA 完全有资格和能力,充当这个“公证人”的角色。

方案3——基于 CA 证书进行密钥交换

其实“方案3”跟“方案2”很像的,主要差别在于——“方案3”增加了“CA 数字证书”这个环节。所谓的数字证书,技术上依赖的还是前面提到的“非对称加密”。为了描述“CA 证书”在 SSL/TLS 中的作用,俺大致说一下原理(仅仅是原理,具体的技术实现要略复杂些):

第1步(这是“一次性”的准备工作)
网站方面首先要花一笔银子,在某个 CA 那里购买一个数字证书。
该证书通常会对应几个文件:其中一个文件包含公钥,还有一个文件包含私钥。
此处的“私钥”,相当于“方案2”里面的 k1;而“公钥”类似于“方案2”里面的 k2。
网站方面必须在 Web 服务器上部署这两个文件。

所谓的“公钥”,顾名思义就是可以公开的 key;而所谓的“私钥”就是私密的 key。
其实前面已经说过了,这里再唠叨一下:
“非对称加密算法”从数学上确保了——即使你知道某个公钥,也很难(不是不可能,是很难)根据此公钥推导出对应的私钥。

第2步
当浏览器访问该网站,Web 服务器首先把包含公钥的证书发送给浏览器。

第3步 浏览器验证网站发过来的证书。如果发现其中有诈,浏览器会提示“CA 证书安全警告”。 由于有了这一步,就大大降低了(注意:是“大大降低”,而不是“彻底消除”)前面提到的“中间人攻击”的风险。

为啥浏览器能发现 CA 证书是否有诈?
因为正经的 CA 证书,都是来自某个权威的 CA。如果某个 CA 足够权威,那么主流的操作系统(或浏览器)会内置该 CA 的“根证书”。
(比如 Windows 中就内置了几十个权威 CA 的根证书)
因此,浏览器就可以利用系统内置的根证书,来判断网站发过来的 CA 证书是不是某个 CA 颁发的。
(关于“根证书”和“证书信任链”的概念,请参见之前的教程《数字证书及CA的扫盲介绍》)

第4步
如果网站发过来的 CA 证书没有问题,那么浏览器就从该 CA 证书中提取出“公钥”。
然后浏览器随机生成一个“对称加密的密钥”(以下称为 k)。用 CA 证书的公钥加密 k,得到密文 k’
浏览器把 k’ 发送给网站。

第5步
网站收到浏览器发过来的 k’,用服务器上的私钥进行解密,得到 k。
至此,浏览器和网站都拥有 k,“密钥交换”大功告成啦。

可能有同学会问:那么“方案3”是否就足够严密,无懈可击了捏?
俺只能说,“方案3”【从理论上讲】没有明显的漏洞。实际上 SSL 的早期版本(SSLv2)使用 RSA 进行身份认知和密钥交换,其原理与这个“方案3”类似。
但是,“理论”一旦落实到“实践”,往往是有差距滴,会引出新的问题。套用某 IT 大牛的名言,就是:In theory, there is no difference between theory and practice. But in practice, there is.
所以在本系列的后续博文,俺还会再来介绍“针对 SSL/TLS 的种种攻击方式”以及“对应的防范措施”。

关于【客户端证书】的补充说明

前面介绍的“方案3”仅仅使用了“服务端证书”——通过服务端证书来确保服务器不是假冒的。
除了“服务端证书”,在某些场合中还会涉及到“客户端证书”。所谓的“客户端证书”就是用来证明客户端(浏览器端)访问者的身份。
比如在某些金融公司的内网,你的电脑上必须部署“客户端证书”,才能打开重要服务器的页面。
由于本文主要介绍的是【公网】上的场景,这种场景下大都【不需要】“客户端证书”。所以,对“客户端证书”这个话题,俺就偷个懒,略过不提。

本节总结

在本节结尾,来稍微总结一下:
如果没有引入某种身份认证机制,必定会导致“中间人攻击”。这种情况下,加密算法搞得再强大,也是然并卵。

本文介绍了两种身份认证的思路,分别是:

  1. 基于私密的共享信息;
  2. 基于双方都信任的公证人。

前者【不】适合用于互联网通讯,所以必须采用后者。也就是如今广泛使用的 CA 证书体系。CA 就是上述所说的“双方都信任的公证人”。

第三节 密钥交换(密钥协商)算法及其原理

先插播一个好消息:
本月初俺发了一篇《老流氓 CNNIC 的接班人——聊聊“沃通/WoSign”的那些破事儿》。前2天看到新闻说,Mozilla 组织(Firefox)已经把沃通的根证书加入黑名单了,为期一年。一年之后看它的表现再决定是否永久性屏蔽。
对这种流氓公司,就应该给它点颜色看看(老实说,俺还觉得处罚偏轻了)

本文的上一节,咱们聊了“密钥交换的难点”以及“证书体系”的必要性。今天这篇来介绍一下实战中使用的“密钥协商算法”。

密钥交换/协商机制要达到啥【目的】?

前一篇介绍了 SSL/TLS 的身份认证机制。这个机制是为了防止攻击者通过【篡改】网络传输数据,来假冒身份,以达到“中间人攻击/MITM”的目的。
而今天要聊的“密钥协商机制”是:(在身份认证的前提下)如何规避【偷窥】的风险。
通俗地说,即使有攻击者在偷窥你与服务器的网络传输,客户端(client)依然可以利用【密钥协商机制】与服务器端(server)商量出一个用来加密的密钥(也称“会话密钥”)。

密钥交换/协商机制的几种【类型】

俺总结了一下,大致有如下几种类型:

类型1——依靠【非】对称加密算法

原理:
拿到公钥的一方先生成随机的会话密钥,然后利用公钥加密它;再把加密结果发给对方,对方用私钥解密;于是双方都得到了会话密钥。

举例:
RSA

类型2——依靠专门的密钥交换算法

原理:
这个原理比较复杂,一两句话说不清楚,待会儿聊到 DH 的那个章节会详谈。

举例:
DH 算法及其变种

类型3——依靠通讯双方事先已经共享的“秘密”

原理:
既然双方已经有共享的秘密(这个“秘密”可能已经是一个密钥,也可能只是某个密码/password),只需要根据某种生成算法,就可以让双方产生相同的密钥(并且密钥长度可以任意指定)
举例:
PSK 和 SRP(可能很多同学没听过这俩玩意儿。别担心,本文后续部分有介绍)

基于【RSA】的密钥协商

概述

这大概是 SSL 最古老的密钥协商方式——早期的 SSLv2 只支持一种密钥协商机制,就是它!(前一篇)介绍身份认证重要性的时候,也是拿 RSA 来演示。
(再次唠叨)RSA 是一种【非】对称加密算法。在本系列第1篇的背景知识介绍中,已经聊过这种算法的特点——加密和解密用使用【不同的】密钥。并且“非对称加密算法”既可以用来做“加密/解密”,还可以用来做“数字签名”。

密钥协商的步骤

(下列步骤只阐述原理。具体的协议细节,等到本系列的后续几篇再讲)

  1. 客户端连上服务端
  2. 服务端发送 CA 证书给客户端
  3. 客户端验证该证书的可靠性
  4. 客户端从 CA 证书中取出公钥
  5. 客户端生成一个随机密钥 k,并用这个公钥加密得到 k’
  6. 客户端把 k’ 发送给服务端
  7. 服务端收到 k’ 后用自己的私钥解密得到 k
  8. 此时双方都得到了密钥 k,协商完成。

如何防范偷窥(嗅探)

攻击方式1
攻击者虽然可以监视网络流量并拿到公钥,但是【无法】通过公钥推算出私钥(这点由 RSA 算法保证)

攻击方式2
攻击者虽然可以监视网络流量并拿到 k’,但是攻击者没有私钥,【无法解密】 k’,因此也就无法得到 k

如何防范篡改(假冒身份)

攻击方式1
如果攻击者在第2步篡改数据,伪造了证书,那么客户端在第3步会发现(这点由证书体系保证)

攻击方式2
如果攻击者在第6步篡改数据,伪造了k’,那么服务端收到假的k’之后,解密会失败(这点由 RSA 算法保证)。服务端就知道被攻击了。

基于【DH】的密钥协商

概述

DH 算法又称“Diffie–Hellman 算法”。这是两位数学牛人的名称,他们创立了这个算法。该算法用来实现【安全的】“密钥交换”。它可以做到——“通讯双方在完全没有对方任何预先信息的条件下通过不安全信道创建一个双方共享的私有密钥”。这句话比较绕口,通俗地说,可以归结为两个优点:

  1. 通讯双方事先【不】需要有共享的秘密。
  2. 用该算法协商密码,即使协商过程中被别人全程偷窥(比如“网络嗅探”),偷窥者也【无法】知道协商得出的密钥是啥。

但是 DH 算法本身也有缺点——它【不】支持认证。也就是说:它虽然可以对抗“偷窥”,却无法对抗“篡改”,自然也就无法对抗“中间人攻击/MITM”(在本文的上一节,俺已经强调过了——缺乏身份认证,【必定会】遭到“中间人攻击/MITM”)。
为了避免遭遇 MITM 攻击,DH 需要与其它签名算法(比如 RSA、DSA、ECDSA)配合——靠签名算法帮忙来进行身份认证。当 DH 与 RSA 配合使用,称之为“DH-RSA”,与 DSA 配合则称为“DH-DSA”,以此类推
反之,如果 DH 【没有】配合某种签名算法,则称为“DH-ANON”(ANON 是洋文“匿名”的简写)。此时会遭遇“中间人攻击/MITM”。(具体的中间人攻击手法,可以参见本系列前一篇)

关于该算法的更多介绍,可以参见维基百科(这个条目)。

数学原理

(如果你属于那种“看了数学公式就犯晕的人”,可以直接略过本小节,不影响你看后续的章节)

从概念上讲:DH 依赖的是:求解“离散对数问题”的复杂性。具体的算法如下:
通讯双方(张三、李四)需要先约定好算法参数(algorithm parameters):一个素数 p 作为模数,一个素数 g 作为基数(g 也称为“生成元”)。这两个算法参数是可以对外公开滴。
对于张三而言,需要先想好一个秘密的自然数 a 作为私钥(不能公开),然后计算 A = ga mod p 作为自己的公钥(可以公开)。
对李四而言也类似,先想好一个秘密的自然数 b 作为私钥(不能公开),然后计算 B = gb mod p 作为自己的公钥(可以公开)。
张三和李四互相交换各自的公钥。
然后张三计算出 k = Ba mod p,李四计算出 k = Ab mod p

该算法至少确保了如下几点:

  1. 张三和李四分别计算出来的 k 必定是一致的
  2. 张三和李四都无法根据已知的数来推算出对方的私钥(张三无法推算出 b,李四无法推算出 a)
  3. 对于一个旁观者(偷窥者),虽然能看到 p,g,A,B,但是无法推算出 a 和 b(就是说,旁观者无法推算出双方的私钥),自然也无法推算出 k

举例
前面说得都是符号,比较抽象。下面拿具体数字举例:
假设约定的算法参数:模数是 97,基数是 3
张三用的私钥是 6,李四用的私钥是 21,用 python 代码演示如下(注:python 语言用“两个连续星号”表示“幂运算”,用百分号表示“取模运算”):

p = 97
g = 3

a = 6
b = 21

A = (ga) % p
B = (g
b) % p

print((Ba) % p ) # 此处输出 47
print((A
b) % p) # 此处输出 47

最后打印出来的两个 47 就是双方都计算出了【相同的】结果(这个数值可以用作之后的“会话密钥”)

上面因为是举例,用的数字都比较小。在实战中需要注意如下几点,以降低被攻击的风险。

  1. p 必须是质数且足够大(【至少】300位)
  2. a 与 b 也要足够大(【至少】100位),且必须是随机生成。
  3. g 必须是质数,【不】需要很大,比如 2 或 3 或 5 都可以。g 如果太大并【不能】显著提升安全性,反而白白浪费了性能。

密钥协商的步骤

(下列步骤只阐述原理。具体的协议细节,等到本系列的后续几篇再讲)

  1. 客户端先连上服务端
  2. 服务端生成一个随机数 s 作为自己的私钥,然后根据算法参数计算出公钥 S(算法参数通常是固定的)
  3. 服务端使用某种签名算法把【算法参数(模数 p,基数 g)和服务端公钥 S】作为一个整体进行签名
  4. 服务端把【算法参数(模数 p,基数 g)、服务端公钥S、签名】发送给客户端
  5. 客户端收到后验证签名是否有效
  6. 客户端生成一个随机数 c 作为自己的私钥,然后根据算法参数计算出公钥 C
  7. 客户端把 C 发送给服务端
  8. 客户端和服务端(根据上述 DH 算法)各自计算出 k 作为会话密钥

如何防范偷窥(嗅探)

嗅探者可以通过监视网络传输,得到算法参数(模数 p,基数 g)以及双方的公钥,但是【无法】推算出双方的私钥,也【无法】推算出会话密钥(这是由 DH 算法在数学上保证的)

如何防范篡改(假冒身份)

攻击方式1
攻击者可以在第4步篡改数据(修改算法参数或服务端公钥)。但因为这些信息已经进行过数字签名。篡改之后会被客户端发现。

攻击方式2
攻击者可以在第7步篡改客户端公钥。这步没有签名,服务端收到数据后不会发现被篡改。但是,攻击者篡改之后会导致“服务端与客户端生成的会话密钥【不一致】”。在后续的通讯步骤中会发现这点,并导致通讯终止。
(本系列的后续几篇讲具体协议的时候会提到:协议初始化/握手阶段的末尾,双方都会向对方发送一段“验证性的密文”,这段密文用各自的会话密钥进行【对称】加密,如果双方的会话密钥不一致,这一步就会失败,进而导致握手失败,连接终止)

DH 的变种——基于【椭圆曲线】的 ECDH

DH 算法有一个变种,称之为 ECDH(全称是“Elliptic Curve Diffie-Hellman”)。维基条目在“这里”
它与 DH 类似,差别在于:
DH 依赖的是——求解“离散对数问题”的困难。
ECDH 依赖的是——求解“椭圆曲线离散对数问题”的困难。

ECDH 的数学原理比 DH 更复杂。考虑到本文读者大都【不是】数学系出身,俺就不展开啦。 ECDH 跟 DH 一样,也是【不支持】认证滴——同样需要与其它签名算法(比如 RSA、DSA、ECDSA)配合。

基于【PSK】的密钥协商

概述

PSK 是洋文“Pre-Shared Key”的缩写。顾名思义,就是【预先】让通讯双方共享一些密钥(通常是【对称加密】的密钥)。所谓的【预先】,就是说,这些密钥在 TLS 连接尚未建立之前,就已经部署在通讯双方的系统内了。 这种算法用的不多,它的好处是:

  1. 不需要依赖公钥体系,不需要部属 CA 证书。
  2. 不需要涉及非对称加密,TLS 协议握手(初始化)时的性能好于前述的 RSA 和 DH。 更多介绍可以参见维基百科,链接在“这里”。

密钥协商的步骤

(由于 PSK 用的不多,下面只简单介绍一下步骤,让大伙儿明白其原理)

在通讯【之前】,通讯双方已经预先部署了若干个共享的密钥。
为了标识多个密钥,给每一个密钥定义一个唯一的 ID
协商的过程很简单:客户端把自己选好的密钥的 ID 告诉服务端。
如果服务端在自己的密钥池子中找到这个 ID,就用对应的密钥与客户端通讯;否则就报错并中断连接。

如何防范偷窥(嗅探)

使用这种算法,在协商密钥的过程中交换的是密钥的标识(ID)而【不是】密钥本身。
就算攻击者监视了全过程,也无法知晓密钥啥。

如何防范篡改(假冒身份)

PSK 可以单独使用,也可以搭配签名算法一起使用。 对于单独使用
如果攻击者篡改了协商过程中传送的密钥 ID,要么服务端发现 ID 无效(协商失败),要么服务端得到的 ID 与客户端不一致,在后续的通讯步骤中也会发现,并导致通讯终止。
(本系列的后续几篇讲具体协议的时候,会提到:协议初始化/握手阶段的末尾,双方都会向对方发送一段“验证性的密文”,这段密文用各自的会话密钥进行【对称】加密,如果双方的会话密钥不一致,这一步就会失败,进而导致握手失败,连接终止)

对于搭配签名算法
如果攻击者篡改了协商过程中传送的密钥 ID,验证签名会失败

补充说明

PSK 与 RSA 具有某种相似性——既可以用来搞“密钥协商”,也可以用来搞“身份认证”。
所以,PSK 可以跟 DH(及其变种)进行组合。例如:DHE-PSK、ECDHE-PSK
关于 PSK 的更多细节,可以参见 RFC4279

基于【SRP】的密钥协商

概述

SRP 是洋文“Secure Remote Password”的缩写。这个算法有点类似于刚才提到的 PSK——只不过 client/server 双方共享的是比较人性化的密码(password)而不是密钥(key)。该算法采用了一些机制(盐/salt、随机数)来防范“嗅探/sniffer”或“字典猜解攻击”或“重放攻击”。
这个算法应该用得很少——OpenSSL 直到2012年才开始支持该算法。所以俺这里就不展开了。有兴趣的同学可以去看 RFC2945 的协议描述。

密钥协商的步骤

(由于 SRP 用的不多,俺偷懒一下,略去此小节)

扫盲一下【前向保密】(PFS)

【回溯性破解】及其危险性

从技术上讲,攻击者如果能够对通讯双方进行【嗅探】,也就能够把通讯双方的传输数据存储下来。如果攻击者比较牛逼,以至于能拿到通讯双方的私钥,那就【有可能】根据私钥推导出会话密钥,从而解密之前存储的历史数据。
有些同学可能会问:攻击者如何拿到私钥捏?
【常见】的情况有如下几种:
场景1——入侵双方的操作系统(搞定了操作系统,自然就能搞定系统中存储的私钥);
场景2——利用协议【设计】的漏洞(能达到这种水准的,通常是 NSA 之类的国家队,养了足够多的密码学大牛)
场景3——利用协议【实现】的安全漏洞(比如前几年惊艳全球的“心脏滴血漏洞”,【有可能】会导致私钥泄漏。协议本身没问题,是 OpenSSL 的【代码实现】出了 bug)
场景4——通过社会工程学(比如政府部门可以直接要求本国的网站交出私钥)。

容易遭受回溯破解的密钥协商算法

本文前面提到了几种密钥交换/协商算法,如下这几种【特别容易】遭到“回溯破解”。

RSA
攻击者事先存储了通讯的密文(历史数据)。
由于 RSA 的私钥是稳定的(长期不变)。假设有一天,攻击者拿到了 RSA 的私钥,就可以用这个私钥解密握手过程的密文,从而得到会话密钥(session key),然后用会话密钥解密会话的密文,得到会话的明文。

PSK(Pre-Shared Key)
攻击者事先存储了通讯的密文(历史数据)。
由于双方共享的 key 是稳定的(长期不变)。如果有一天,攻击者拿到了通讯双方共享的 key,就可以用这个 key 解密握手过程的密文,从而得到会话密钥(session key),然后用会话密钥解密会话的密文,得到会话的明文。

SRP(Secure Remote Password)
攻击者事先存储了通讯的密文(历史数据)。
由于双方共享的 password & salt 是稳定的(长期不变)。如果有一天,攻击者拿到了通讯双方共享的 password 和 salt,就可以用来解密握手过程的密文,从而得到会话密钥(session key),然后用会话密钥解密会话的密文,得到会话的明文。

解决方法——“前向保密/完美正向加密”

相比前面这几种密钥协商算法,DH 和 ECDH 是比较能抗“回溯破解”滴。为啥这么说捏?下面解释:
对于 DH 算法,通讯双方握手需要生成各自的私钥(前面提到的整数 a 和 b),然后根据 DH 算法计算得出会话密钥。换句话说,会话密钥依赖于双方的私钥 a 与 b。DH 算法的优势在于——双方的私钥(a & b)是可以【动态生成】滴!
为了对抗“回溯性破解”,可以强制要求双方每次都生成【随机的】私钥。而且每次生成的两个私钥用完就丢弃(销毁)。如此一来,攻击者就难以破解过往的历史数据。DH 算法经过如此改良之后叫做 DHE(追加的字母 E 表示【ephemeral】)。
与 DH 类似,ECDH 也可以做类似的改良,变成 ECDHE,以对抗“回溯破解”。

能够对抗“回溯破解”的密钥交换算法,被称为“前向保密”,洋文叫“forward secrecy”,缩写为 FS。它还有另一个称呼——“完美正向加密”(洋文是“perfect forward secrecy”,缩写为 PFS)。关于这方面的更多介绍,可以参见维基百科(链接在“这里”)。

各种算法组合的【一览表】

图 各种算法组合

第四节 历史版本的演变及 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

类型(type)

“类型”字段是个枚举值,协议允许的有效值参见下表(对表格中的每种类型,后续小节会有详细介绍)
表二

版本(version)

“版本”字段含两个字节,分别表示:主版本号 & 次版本号。其有效值如下:
表三
(注:从 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 各个版本实现【完整性】的方式,参见如下表格:
表四

填充(padding)

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

各种类型 Record 简介

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

握手(Handshake)

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

切换到加密方式(ChangeCipherSpec)

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

应用层数据(Application)

Record 协议的“类型”字段为 23(0x17),表示这条 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 协议的“类型”字段为 21(0x15),表示这条 record 是 Alert 类型。
这种类型的 record 用来发送警告或出错信息。
在通讯的过程(包括握手过程)中,有时候某一方会发现不对劲(比如收到的数据出现缺失或错误),这时候就要发送一条 Alert 类型的 record 给对方。
不对劲的情况分为两种,洋文分别称之为 Warning 和 Fatal。两者的差别在于:

Warning 表示通讯出现【不稳定】的情况(这种“不稳定”通常是【可恢复】滴)
Fatal 表示通讯出现【不可靠】的情况(比如:证书失效、数据被篡改。这种“不可靠”通常是【不可恢复】滴)

如果不对劲的情况属于 Warning,通讯可能会继续也可能会断开;如果不对劲的情况属于 Fatal,通讯会在发送 Alert 之后立即断开。
这种类型的 record,其“消息”字段仅有2字节,头一个字节表示告警的“级别/Level”(1表示 warning,2表示 fatal);后一个字节表示具体的描述(有一个对照表,用不同的整数表示不同的情况)。
如果在握手【之后】发送告警,此时双方已经建立起加密信道,则告警 record 的“消息”字段是【密文】的。
如果在握手【之前】发送告警,此书尚未建立加密信道,则告警 record 的“消息”字段是【明文】的。

心跳(Heartbeat)

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

文档信息

Search

    Table of Contents