[网络安全] 我对https加密的理解

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

HTTP与HTTPS

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

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

明文传输的问题

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

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

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

在网络中,同样的, 如果给你提供互联网服务的商家(比如电信)起了坏心,或你网络通讯的必经之路(如路由器)被劫持,你将会面临巨大的安全风险。(电信起坏心我是看到过的,即电信有时候会在HTTP网页上植入他们的广告。路由器被劫持我没有遇到过,但是显然后果相当严重)

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

(对,这就是我们学校的家长的学生成绩查询系统。如果你恰好是我们学校的,并且获得了家中路由器管理密码,同时还对HTTP有一定的了解,你可以试试盗取家长的密码)

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

在大概去年8月的时候有一个谣言说法,说Chrome可能不出半年就会“封杀”未加密的HTTP网站(然而实际并没有那么可怕)

不过,加密 HTTP 确实可以防止数据被偷看,那可以防止被篡改吗?

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

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

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

原则:不要小看黑客

黑客一般利用具有自动性的程序或脚本进行攻击。因此,请不要小看黑客抓住攻击机会的能力。HTTPS目前理论上能断绝黑客除暴力攻击之外的攻击途径。在我们下面设计HTTPS的过程中,我们也不能心存侥幸,小看黑客。

加密HTTP

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

加密算法?这好像很简单啊,谁都可以发明一个吧。

那你就只知其一不知其二了。其实很容易发明的那种加密算法只是加密算法家族中的一类——“对称加密”,当一串数据被用密码 A 加密后,使用密码 A 能够解密出原来的数据。简单地说,你有一把钥匙,你的朋友也有一把钥匙,你把信件放进盒子,用你的钥匙锁上,你的朋友收到后用同样的钥匙打开。

然而,容易想到的对称加密算法也容易破解。实际上,如果攻击者截获了你某次(或若干次)通讯的密文,并且准确无误的猜出你的原文,一个好的对称加密算法不应该能让黑客据此算出密码。

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

在网络上,网络服务器就相当于你的朋友。然而,由于你访问网站前很可能没有和站长见过面,你的浏览器中就很可能不会有一个(和服务器上的那个钥匙相同)的钥匙。

那服务器得把这个钥匙给我啊!

怎么给?显然站长还是不可能线下去找你,因此只能通过网络给。那么,根据当前的设计,我们可以画出下面的示意图:

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

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

实际上我们只是无谓地增加了一次 请求/响应 的过程,而没有解决刚才的问题。因为,如果黑客截获密钥1,他就可以解密出密钥2,进而解密出 HTTP 请求和响应的内容。

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

非对称加密

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

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

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

  • 私钥生成函数 gen(l,s)。其中 s 称为“种子”(数据类型无关紧要,看具体实现),而 l 是一个正整数(可以有一个固定的取值列表,并非一定要可以任意取值)。输出内容是一个长度为 l 的二进制数(可以有前导0),称为私钥。
    这个函数要有关于 l 的多项式级时间复杂度。
    对于相同的 sl,该函数的输出一定相同。
    能够用这个函数生成的私钥称为合法私钥l 称为合法私钥的长度
  • 公钥导出函数 pub(A)。其中 A 可以是任何一个合法私钥。输出内容是一个二进制数(长度只与 A 的长度有关,可以有前导0),称为公钥。
    这个函数要有关于 A的长度 的多项式级时间复杂度。
    对于相同的 A,该函数的输出一定也相同。
    B = pub(A)。如果一个人只知道 B,那么他必须要使用指数级时间复杂度的算法,才能够获得 A。也就是说,如果原有的 A 足够长,那么他几乎无法从 B 得出 A
    能够用这个函数生成的公钥称为合法公钥
  • 加密算法 enc(x,B)。其中 x 是任意数据,B 是一个合法公钥。输出内容是一串数据,称为密文。
    这个函数要有关于 B的长度 的多项式级时间复杂度。
    对于相同的 xB,该函数的输出一定相同。如果 x 不同而 B 相同,该函数的输出一定不同。
    y = enc(x,B)。如果一个人只知道 yB,那么他必须要使用指数级时间复杂度的算法,才能够获得 x
    能够用这个函数生成的数据称为合法密文
  • 解密算法 dec(y,A)。其中 y合法密文A 是一个合法私钥。输出内容是一串数据。
    这个函数要有关于 A的长度 的多项式级时间复杂度。
    B = pub(A)y = enc(x,B)(其中 x 为任意数据),那么这个函数必定满足 x = dec(y,A)

看起来很难对吧?实际上这种算法已经有人发明了,而且目前存在好几个。RSA 就是其中一个。

另外,某些非对称加密算法可以用来签名(见下文)。这种算法的 enc(x,B)dec(y,A) 还需满足以下性质:

  • dec(y,A)y 的范围从合法密文扩大到任意数据
  • A 是一个合法私钥B = pub(A)y 是任意数据,那么必定有 y = enc(dec(y,A),B)

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

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

  • 速度被对称加密吊起来打
  • 加密的数据(enc 中的 xdec 中的 y)长度不能超过私钥。

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

改进失败的HTTP加密设计

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

那咋办呢?

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

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

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

然而,我们很快发现这并不安全。黑客可以假装自己是真正的“服务器”,然后边和服务器通信,边和你通信。这就是臭名昭著的“HTTPS中间人攻击”。具体过程如下:

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

引入证书

连非对称加密都不行,这还怎么搞?

其实非对称加密的问题并没有对称加密那么严重。还记得“中间人攻击”吗?那只是客户端被蒙骗了,认为中间人是真正的服务器。

既然如此,那么支持加密的服务器(以网站example.com的服务器为例)就需要有一个“证书”,证明自己确实是example.com的服务器。如果客户端发现对方不能证明自己就是客户端要找的服务器,就拒绝连接。(下图:客户端拒绝连接)

有了“证书”的概念之后,我们要保证证书的可靠性。也就是说,我们只能相信权威机构颁发的证书。这些权威机构并非一定要收取费用,但是,它们必须保证,仅当它们能够证明来申请证书的人就是某个网站的站长(或者对这个网站拥有根本的控制权)时,才将这个网站的证书交给申请人。

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

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

服务器可以公开这个证书。另外,服务器自身还有一个对应证书上公钥的私钥(这个私钥通常是证书颁发机构一起给的),不可外传。

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

我们再看看中间人攻击。中间人对目标网站并没有控制权,因此无法从权威机构获得有效的证书;而中间人如果将服务器的证书直接给客户端,就起不到“替换公钥”的作用。攻击无法进行。

问题1:公钥为什么不能现场生成,而必须嵌入到证书中?

因为如果可以现场生成公钥,那么中间人可以现场生成假公钥,然后和服务器本身的证书一起发给客户端。这样,中间人攻击仍然能够进行。

证书的具体实现

看看我们刚才的证书。

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

这个证书无法真正证明自己是被“新新测试证书颁发机构”签发的(没有任何证据)。因此,中间人收到证书后,可以直接把pk改掉,进行攻击。

为了实现证书的可靠性,我们需要利用非对称加密的“签名”功能。

我们同样用一张证书表示证书颁发机构(下面这张证书是自己签发自己的,我们成为“自签名”。这样的证书颁发机构叫“根颁发机构”)。

[Certificate]
subject = ca.xxtest.org
issuer = ca.xxtest.org
subject.name = 新新测试证书颁发机构
issuer.name = 新新测试证书颁发机构
pk = ...   # 颁发机构也有一个公钥。其对应的私钥由颁发机构管理,不外传。

我们再添加一个叫“hash”的值。这个值,是证书内其他部分的SHA-256(其他较强的哈希算法也可以)值 ( 用颁发机构的私钥解密后 ) 的结果(此处要求解密算法能够接受合法密文以外的数据)。

有了这个“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 进行加密。然后,为了保护密钥交换环节,引入了非对称加密。最终,为了防止中间人攻击,我们设计出了证书和“权威机构”的概念。

性能比较

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

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

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

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

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

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

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

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

事实上,在 2013 年左右,你是否发现大多数主流网站,即使是不涉及用户敏感数据的那种,也都采用了 HTTPS?事实证明,是的。

你有感觉到速度明显变慢吗?你有感觉到过速度变慢吗?如果你的回答是“有”,那请先检查一下隔壁老王是不是在蹭网。

那么,你现在打开一个较大的、没有使用 HTTPS 的网站,和一个使用了 HTTPS 的大网站,你觉得哪个快?事实证明,答案竟然是 HTTPS 快。

原因主要有以下几个:

  1. HTTPS 本身经过很多优化。在它得到普及之前,它就已经有了可以和 HTTP 媲美的速度。
  2. 自古以来,运营商(或国家)就会收集和审查未加密的网络数据,甚至会试图向其中插入广告。这会大幅度拖慢速度。而 HTTPS 让运营商无计可施。
  3. 目前一些较新的网络技术都只适用于 HTTPS,比如最近新出的 HTTP/2(SPDY)和 HTTP/3(QUIC)。这使得 HTTPS 的速度进一步赶超了 HTTP。

个人选择

那么,如果自己建设网站,是否需要使用 HTTPS 呢?

我的个人建议是使用。我有这么几个理由:

  • 能够使读者放心留下名字和邮箱,进行评论,甚至注册。如果有需要,还可以接受信用卡付款(行业上不允许使用较差的 HTTPS 或未加密 HTTP 的网站接受信用卡付款)。
  • 能够加快速度
  • 能够保护自己的隐私

当然,反方也有自己的理由:

  • 容易出锅
    答:没有的事。那是你自己出锅了
  • 会慢
    答:都9102年了
  • 会使得一些古董设备无法访问
    答:都9102年了,网站放弃一些古董设备纯属正常。
  • 证书要氪金,续费难管理
    答:TrustAsia / Let's Encrypt / Cloudflare 了解一下? 如果真的要续费,只要妥善管理好续费的日期,实际上及时续费也不是什么难事。

结语

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

点赞
  1. Darkside说道:
    Google Chrome Windows 7

    HTTP/2 理论上来说是可以支持 http 的,但是目前的所有实现都基于 https。(杠精警告)(逃)

发表评论

电子邮件地址不会被公开。必填项已用 * 标注