最近很久以前在研究HTTPS,就谈一谈我对HTTPS的理解吧。欢迎大佬指错。

修订:2021年2月6日

HTTP与HTTPS

http指超文本传输协议,现在我们浏览网站靠的都是这种协议。我们可以用如下的图来表示HTTP建立连接与传输数据的过程。

HTTP是明文传输(其问题见下文)。为了提高安全性,出现了HTTPS(http-over-ssl),即HTTP的加密版本。

明文传输的问题

明文传输,顾名思义,就是在网络管道中通讯的内容可以被任何中间人读懂或修改。这样有什么危害呢?我们可以举几个现实生活中的例子。

比如,你的朋友问你信用卡密码等信息,你直接写在纸上,不进行任何加密,然后托别人送给你朋友。结果,路上,送信人被早就想要获取你隐私信息的坏人抓住,那个坏人拿走了送信人的信件,你的密码就让坏人知道了。

如果你和你朋友在商量国家大事(假设),并且此时坏人行事足够小心,坏人就可以在你们完全不知道的情况下篡改你们之间的通讯,从而使得国家毁灭。甚至更严重一点,如果你的送信人突然起了坏心...

在网络中,同样地,如果给你提供互联网服务的商家(比如电信)起了坏心,或网络通讯的必经之路(如路由器)被劫持,你将会面临巨大的安全风险。(我的经历可以证实电信起过坏心:通过手机移动数据访问我的某网址时,页面被植入移动套餐相关广告,但改用 HTTPS 后问题不复存在)

也正是因为这个原因,Google Chrome 和一些主流浏览器才将未加密的HTTP网站标识为“不安全”。

▲ HTTP 网站“不安全”标识

同时,Google Chrome 类浏览器会禁止 HTTP 网站使用用户的隐私数据。

▲ HTTP 网站始终被禁止的权限

在大概 2018 年 8 月的时候有一个谣言说法,说 Chrome 可能不出半年就会“封杀”未加密的 HTTP 网站。事实证明,这不是真的,但这足以说明,未加密的网站在互联网中已经难以立足了。因此,是时候了解并启用 HTTPS 了。

问题1:加密 HTTP 确实可以防止数据被偷看,那可以防止被篡改吗?

废话。传输加密后的数据,相当于在用另一种攻击者看不懂的语言进行交流。既然攻击者都不懂这个语言,他怎么能用这个语言来表达虚假信息呢?

那么,废话讲完了,我们就可以进入正题——HTTPS 的设计 了。

为了一步步清晰思路,我们必须先假装自己是 Netscape(发明 HTTPS 的公司)的研究人员,并且正在试图发明 HTTPS。

原则:不要小看攻击者

攻击者一般利用具有自动性的程序或脚本进行攻击。因此,请不要小看攻击者抓住攻击机会的能力。HTTPS 的设计应当阻止除暴力猜测外的任何破解手段。在我们下面设计 HTTPS 的过程中,我们也不能心存侥幸,小看攻击者。

加密HTTP

要加密HTTP,我们离不开加密算法。

加密算法可以分为对称加密和非对称加密。当一串数据被用密钥 K 加密后,使用密钥 K 就能解密出原来的数据,那么该加密体系就是对称加密。简单地说,你有一把钥匙,你的朋友也有一把钥匙,你把信件放进盒子,用你的钥匙锁上,你的朋友收到后用同样的钥匙打开。

然而,容易想到的对称加密算法也容易破解。生产环境中使用的对称加密体系还必须具有“抗推测性”:即使攻击者截获了一次或若干次通讯的密文,然后准确无误地猜出了原文,也无法准确推出密钥。AES 加密——一种常用的对称加密算法,就具有这样的性质。

现在,我们已经有了对称加密的理论,于是现在我们可以尝试用对称加密来加密 HTTP。思维敏锐的同学应该很快就会发现问题——在上面的例子中,你和你的朋友都有一把钥匙。我手里的这把和我朋友手上的那个相同的钥匙,是我和朋友某一次见面时,朋友给我的。

在网络上,网络服务器就相当于你的朋友。然而,由于你访问网站前很可能没有和站长见过面,你的浏览器中就很可能不会有一个(和服务器上的那个钥匙相同)的钥匙。要具备相同钥匙,必须进行密钥传递,即“密钥交换”。

如何传递?显然站长还是不可能线下去找你,因此只能通过网络给。那么,根据当前的想法,我们可以设计出加密通信的过程:

皆大欢喜?No。

不难发现,直到 [2] 处,加密的连接才被建立。那么,“密钥是 ...”这条消息必须明文传输。但是,根据我们的原则,我们不能小看攻击者抓住机会的能力,而一旦这条消息被截获,那么攻击者就可以截获此后所有的,对称加密的消息,并通过得到的密钥解密它们。这样,刚才设计的加密方式就不存在安全性了。

那么我们将“密钥是 ...”这条消息也对称加密如何?不行!我们画一下流程图,很快就发现问题了。

鲁迅曾经说过,世间本没有安全信道,密钥交换后,安全信道才得以建立。加密算法的问题在于,必有一次密钥交换要以明文形式进行。因此,在上面的设计中,我们只是无谓地增加了一次 请求/响应 的过程。如果攻击者截获密钥1,他就可以解密出密钥2,进而解密出 HTTP 请求和响应的所有内容。

看来,仅仅有对称加密是不行的。然而,有对称加密,必有“不对称的加密”。利用“不对称的加密”,或许可以解决这个问题。

非对称加密

对称加密,是指一个数据,用密钥 K 加密,必定能用密钥 K 解密出相同的数据。

那么,我们猜测,非对称加密,是指一个数据 P,用密钥 K_e 加密得到密文 C 后,就不能再用密钥 K_e 解密出原先的数据,而必须使用另一个密钥 K_d。而且,密钥 K_dK_e 之间存在着某些内在联系。

形式化地说,一个完整的非对称加密体系包含几个函数:

  • 私钥生成函数 \text{Gen}(l,S)。其中 S 称为“种子”(数据类型无关紧要,依具体实现而定),而 l 是一个正整数(可以有一个固定的取值列表,并非一定要可以任意取值)。输出内容是一个长度为 l 的二进制数(可以有前导 0),称为私钥,即 K_d = \text{Gen}(l,S)
    • 这个函数要有关于 l 的多项式级时间复杂度。
    • 对于相同的 Sl,该函数的输出一定相同。
    • 能够用这个函数生成的私钥 K_d 称为合法私钥l 称为私钥的长度
  • 公钥导出函数 \text{Pub}(K_d)。其中 K_d 可以是任何一个合法私钥。输出内容是一个二进制数(长度只与 K_d 的长度有关,可以有前导0),称为公钥,即 K_e = \text{Pub}(K_d)
    • 这个函数要有关于 K_d的长度 的多项式级时间复杂度。
    • 对于相同的 K_d,该函数的输出一定也相同,反之亦然。
    • 不存在时间复杂度关于 K_e的长度 为多项式的函数 \text{Rev}(K_e),能反推出对应的 K_d。也就是说,如果原有的 K_d 足够长,那么已知 K_e 推导出 K_d 是不可行的。
    • 能够用这个函数生成的公钥 K_e 称为合法公钥
  • 加密算法 \text{Ec}(P,K_e)。其中 P 是任意数据(具体实现中可以对长度加以限制),K_e 是一个合法公钥。输出内容是一串数据,称为密文,即 C = \text{Ec}(P,K_e)
    • 这个函数要有关于 K_e的长度 或 P的长度 的多项式级时间复杂度。
    • 对于相同的 PK_e,该函数的输出一定相同。如果 P 不同而 K_e 相同,该函数的输出一定不同。
    • 不存在时间复杂度关于 K_e的长度 或 C的长度 为多项式的函数 \text{RevDc}(C,K_e),能解密出原数据 P
    • 能够用这个函数生成的数据 C 称为合法密文
  • 解密算法 \text{Dc}(C,K_d)。其中 C合法密文K_d 是一个合法私钥。输出内容是一串数据。
    • 这个函数要有关于 K_d的长度 或 C的长度 的多项式级时间复杂度。
    • 该函数必须是加密算法的逆过程,即 \text{Dc}(\text{Ec}(P,\text{Pub}(K_d)),K_d) = P 恒成立。其中 K_d合法私钥

实际上这种算法已经有人发明了。所有的非对称加密算法都基于一个数学难题(以此实现 \text{Pub} 的不可逆性)。例如最常用的算法 RSA,利用的是生成大质数、将两个大质数相乘很容易,将乘积分解却不可行的原理。

另外,某些非对称加密算法可以用来签名(见下文)。这种算法的加密算法与解密算法还必须具有以下性质:

  • \text{Dc}(C,K_d)C 可以是任意数据(具体实现中可以对长度加以限制)。这一解密的过程被称为“签名”。
  • 加密算法必须是解密算法的逆过程,即 \text{Ec}(\text{Dc}(C,K_d),\text{Pub}(K_d)) = C 恒成立。其中 K_d合法私钥。这一过程被称为“验证”。

那么现在有能用于签名的非对称加密算法吗?其实 RSA 就是。

然而,目前的非对称加密算法还有以下缺点:

  • 速度通常很慢,一般为对称加密的百分之一至千分之一。
  • 处理的数据(\text{Ec} 中的 P\text{Dc} 中的 C)长度不能超过私钥 K_d

尽管存在如此大的缺点,非对称加密可能还是可以拯救刚才那个失败的 HTTP 加密方法的。

改进失败的 HTTP 加密设计

非对称加密速度太慢,而且处理的数据长度也有限制。显然,如果用它来加密 HTTP 请求 和 HTTP 响应 是不行的。

那咋办呢?

回去看一下某个用对称加密保护密钥交换的失败尝试,我们也许就有了灵感(不用回去看,我搬下来了)。

我们尝试用非对称加密取代上图中的“对称加密1”。

这样,即使攻击者截获了“公钥是 ...”这条消息,也无助于攻击者破解出密钥2。安全性得到了保障。(真的吗?)

然而,我们很快发现这并不安全。攻击者可以假装自己是真正的“服务器”,然后边和服务器通信,边和客户端通信,并在密钥交换过程中通过发送假公钥破解出密钥2。这就是臭名昭著的“HTTPS 中间人攻击”。具体过程如下:

如果我们将客户端或服务器经历的事件单独提出来,我们会发现,这些事件与没有发生攻击时长得一模一样。客户端和服务器都被蒙骗了:客户端以为中间人就是服务器,服务器以为中间人就是客户端。结果,中间人掌握了所有通信内容。

引入证书

在数字世界里,一切信息都是可以被复制的。正因如此,中间人假扮成服务器也毫无难度。

从上面的攻击过程中可以看出,只要要求服务器证明自己就是真正的服务器,问题就可以得到解决。但由于信息的可复制性,我们不能指望服务器“自证清白”,而必须相信权威,让受信任第三方进行认证。一种方法就是要求服务器出示第三方颁发的证书。

▲ 浏览器因发现网站证书存在问题而拒绝连接

证书由第三方机构颁发,因此证书中需要包含第三方机构的信息;证书用于证明服务器对应某个特定网站,因此必须包含接收证书的网站的信息;证书只能由合法持有证书的服务器使用,因此上述过程中使用的公钥应当与证书捆绑在一起,并且对应私钥仅由服务器持有。

根据这些要求,我们设计出了第一版证书:

[Certificate]
subject = example.com,*.example.com   # 表示这是网站example.com和*.example.com的证书
issuer = ca.xxtest.org   # 证书的颁发机构
subject.name = 测试网站
issuer.name = 新新测试证书颁发机构
pk = ...   # 公钥

只有持有私钥的网络节点才是证书的持有者。私钥并不包含在证书中,所以可以放心地将证书向大众展示。

权威的力量来源于信任。受信任的第三方颁发机构必须遵循严格的程序,在确认证书申请人对目标网站确实有控制权时,才将证书和私钥交给申请人。

然后,我们看一看前面利用非对称加密的 HTTPS 设计。我们将“请求公钥”地操作操作换成“请求证书”。客户端收到证书后,先验证证书是否有效、是否匹配当且访问的网站。如果证书不对,客户端直接断开连接、终止操作。否则,客户端就使用证书上的公钥继续操作。

我们再看看中间人攻击。中间人对目标网站并没有控制权,因此无法从第三方机构获得有效的证书;中间人亦不是证书的“合法持有者”,没有证书对应的私钥,不能解密非对称加密的密钥2。攻击无法进行。

证书的具体实现

看看我们刚才的证书。

[Certificate]
subject = example.com,*.example.com   # 表示这是网站example.com和*.example.com的证书
issuer = ca.xxtest.org   # 证书的颁发机构
subject.name = 测试网站
issuer.name = 新新测试证书颁发机构
pk = ...   # 公钥

显然,这个证书十分脆弱。它既不能证明自己由“新新测试证书颁发机构”颁发,也不能证明自己没有被篡改过。攻击者截获通信后,想要篡改证书上的公钥,易如反掌。

而能用于保护证书完整性与可靠性的,正是非对称加密的“签名”功能。

权威颁发机构要有权威,首先得有档案。我们同样用一张证书表示证书颁发机构的档案(下面这张证书的颁发机构为其本身,称为“自签名证书”。自签名证书代表的证书颁发机构叫“根颁发机构”)。

[Certificate]
subject = ca.xxtest.org
issuer = ca.xxtest.org
subject.name = 新新测试证书颁发机构
issuer.name = 新新测试证书颁发机构
pk = ...

那么,何谓“权威”?保证按照严格程序颁发证书,经过大众认证,然后被操作系统开发者以补丁形式加入受信任颁发机构库的,即是权威证书颁发机构。

同样地,颁发机构的证书也有一个公钥,且持有对应私钥者才是证书的合法持有者。

要证明证书未被篡改,需要利用哈希算法。这类算法可以将任何数据缩短为一段长度固定的“数据摘要”(记作 h = \text{Hash}(P))。显然,不可能保证对于不同的 Ph 一定不同。因此用于密码学的“密码哈希函数”还需要满足以下条件:

  • 抗修改:一旦 P 改动一点,h 就会截然不同。
  • 无法逆推:已知 h,反推出一个可能的 P 是不可行的。
  • 非定向抗碰撞:很难找到两个不同的 P_1, P_2,使得 h_1 = h_2
  • 定向抗碰撞:已知 P_1,很难找到 P_2,使得 h_1 = h_2

提示:“不可行”“很难”表示消耗时间极长,几乎无法实现,而不代表“不可能”。

我们向证书中添加一个叫“hash”的值。这个值,是证书内其他部分的 SHA-256(其他较强的哈希算法也可以)值 ( 用颁发机构的私钥签名(即解密)后 ) 的结果(此处用到了非对称加密的签名功能)。

一旦证书遭到篡改,其 hash 值就会改变,因此只需判断证书上的 hash 是否正确,就能判断证书是否被篡改,至此“证书未被篡改”得到证明。而要算出新的 hash 并修改证书,必须知道颁发机构的私钥,因此我们确定只有颁发机构对应证书的合法持有者可以以这个颁发机构的名义签发证书。至此,“证书由权威机构颁发”这件事也得到了证明。

最后的证书大致这样。

[Certificate]
subject = ca.xxtest.org
issuer = ca.xxtest.org
subject.name = 新新测试证书颁发机构
issuer.name = 新新测试证书颁发机构
pk = ...   # 颁发机构也有一个公钥。其对应的私钥由颁发机构管理,不外传。
hash.type = SHA-256
hash = ...   # 自签名证书的hash应该用自己对应的私钥解密。
date = 2019/10/27 21:03:00 - 2036/10/26 21:03:00   # 签发日期 - 有效期至
[Certificate]
subject = example.com,*.example.com   # 表示这是网站example.com和*.example.com的证书
issuer = ca.xxtest.org   # 证书的颁发机构
subject.name = 测试网站
issuer.name = 新新测试证书颁发机构
pk = ...   # 公钥
hash.type = SHA-256
hash = ...
ocsp = http://ca.xxtest.org/api/ocsp/   # 颁发者向证书内加入的一个网址
date = 2019/10/27 21:04:00 - 2020/10/27 21:04:00

(目前,这是我们设计的初级版证书。实际的网络证书比这个要复杂,并且编码格式不同)

现在,检查证书是否有效的步骤如下:

  1. 客户端根据证书中的 issuer,在操作系统中的可信颁发机构库中找到颁发机构的证书。如果找不到,即认为无效。
  2. 客户端用颁发机构的公钥重新加密证书中的 hash 参数,并按照 hash.type 参数指定的算法对证书的其余部分进行哈希。如果两者不匹配,即认为无效。
  3. 客户端检查证书中指定的网址是否与访问的网址匹配。如果不匹配,即认为无效。
  4. 客户端根据系统时间(可见系统时间正确的重要性)判断此证书是否“尚未签发”,或已过期。如果当前不在有效期内,即认为无效。
  5. 客户端使用未加密的 HTTP 协议请求 ocsp 指定的网址。如果该网址显示当前证书已经被吊销,即认为无效。
  6. 认为证书有效并继续操作。

这样,除非攻击者黑进了证书颁发机构(概率极小),或者服务器使用的证书私钥泄露且忘记吊销(此时站长后果自负),或者私钥泄露并且同时OCSP被劫持(概率极小),攻击者无法进行中间人攻击。

现在,我们可以给出加密 HTTP 的完整方案了(为了体现操作次数的多,将DNS也加入了其中(功能是将好记的域名转化成计算机可访问的IP地址)):

总结

在本文中,我们首先尝试了用对称加密算法对 HTTP 进行加密。然后,为了保护密钥交换环节,引入了非对称加密。最终,为了防止中间人攻击,我们设计出了证书和“权威机构”的概念,并利用非对称加密的签名功能证明了证书的有效性。

细节:证明控制权

我们说过,只有证书申请者证明控制权后,颁发机构才可以对其发放该网站的证书。证明控制权的方法主要有以下几个。大多数情况下,这些方法会同时存在,申请者只要通过其中一个验证即可拿到证书。

提示:这些自动化的方法只适用于 DV 证书,也就是仅仅证明服务器身份的证书。能同时证明企业身份的 OV 和 EV 证书必须通过人工验证来取得。

1 文件证明法

证明时,颁发机构给出一文件路径(如 //.well-known/acme-challenge/51a2b8695e5ca9755cdb8445431327c9)和另一串随机字符串 26148d621ef74844918af182d63976b6。确认后,颁发机构通过未加密的 HTTP 访问网站上的对应路径。若返回了与另一随机字符串相同的文件,则验证通过。

该方法考察用户对特定文件夹中内容的控制权。该文件夹通常都位于 //.well-known/ 下。因此建立 Web 服务器后分配权限时,这块区域的权限需要谨慎控制。

许多 Web 服务器对静态文件请求的响应以文件为依据,故这种方法被称为“文件证明法”。

优点:文件内容设定后即时生效,验证不会因为无法掌控的缓存而失败。

缺点:权限控制不当可能导致不该申请证书的人拿到证书;不一定适合所有服务端环境。

2 DNS 记录证明法

证明时,颁发机构给出一 DNS 记录名称(如 acme-challenge-51a2b8695e5ca975)和另一串随机字符串 26148d621ef74844918af182d63976b6。确认后,颁发机构检查该 DNS 记录。若类型为 TXT(有的机构允许 CNAME)且内容与随机字符串匹配,则验证通过。

优点:权限容易控制;操作简单,自动化容易;适合任何服务端。

缺点:证书申请者无法控制的 DNS 缓存可能导致验证失败,因此修改记录后通常需要等一段时间再确认。

3 邮件证明法

支持该方法的颁发机构并不多。

颁发机构向代表统治地位的 [email protected] [email protected] 的邮箱中的其中一个发送链接,点击链接后完成验证。

优点:操作方便;适合几乎所有服务端。

缺点:不利于自动化;安全性不高。

提示:证书颁发机构服务器的通信通道一般受到严格保护,因此即使使用未加密的协议与目标服务器联络,也很难受到劫持。

性能比较与个人选择

HTTPS 的全过程图就在上面,这就不搬下来了。我们再看看 HTTP 的全过程图:

观察流程发现,实际上除去 ACK 后,实线箭头和虚线箭头可以一一配对。我们将一个实线箭头和其下面的虚线箭头组成一对,称为一个 RTT。

HTTP 在开始传输数据之前,经历了两个 RTT。

HTTPS 在开始传输数据之前,经历了六个 RTT。而用户第一次访问网站时,浏览器默认访问 HTTP 而非 HTTPS,因此还要跳转到 HTTPS,此时就总计有八个 RTT。

但在开始传输数据之后,两种协议的 请求-响应 过程均只占一个 RTT,只不过 HTTPS 多了一个加密过程(即使使用百兆宽带,这个时间也不到总传输时间的 5%)。

那么,总体下来,HTTP 速度岂不是吊打 HTTPS?

\text{\huge{你错了!}}

\text{都 1202 年了还说 HTTPS 慢}

早在 3102 时,大部分主流网站就已经强制使用 HTTPS。然而,绝大多数人都没有感觉到网络变慢了。时至今日,HTTPS 的速度常常超过 HTTP。

原因主要有以下几个:

  1. HTTPS 本身经过很多优化。在它得到普及之前,它就已经有了可以和 HTTP 媲美的速度。
  2. 自古以来,运营商(或国家)就会收集和审查未加密的网络数据,甚至会试图向其中插入广告。这会大幅度拖慢速度。而 HTTPS 让运营商无计可施。
    (网络加密的魅力正是在于:无论你是平民,是高官,是锟斤拷,还是锟斤拷,你都无法查看加密后的数据)
  3. 目前一些较新的网络技术都只适用于 HTTPS,比如最近新出的 HTTP/2(SPDY)和 HTTP/3(QUIC)。这使得 HTTPS 的速度进一步赶超了 HTTP。(实际上很多只适用于 HTTPS 的技术理论上同样可以用于 HTTP,但是如今,浏览器和服务器通常只为 HTTPS 实现了这些技术)

那么,如果自己建设网站,是否需要使用 HTTPS 呢?答案显然是肯定的。

  • 浏览器的种种限制,使得未加密的网站在互联网中难以立足,而 HTTPS 的绿锁标志允许任何人放心地留下自己的邮箱并设置密码。
  • 妥善管理的 HTTPS 使得攻击者失去机会,大大降低受攻击的概率。
  • 隐私窥探与广告植入仍然存在。使用 HTTPS 可以杜绝被这类行为拖慢速度的可能。
  • 使用 HTTPS 也能保护自己的隐私。
  • 自 Let's Encrypt 项目带来清流起,HTTPS 证书已经不再是“证明站长有钱”的工具了。经过合适的调整,免费 HTTPS 证书方案可以做到一劳永逸。

结语

感谢阅读。如果发现有问题,欢迎留言。


undefined