HTTP 协议仅仅制定了互联网传输的标准,简化了直接使用 TCP 协议进行通信的难度。
图片来自 Pexels
less is more 的概念本身很好,但是过于简单也会承担一些后果:
①通信使用明文,内容可能会窃听
HTTP 本身不具备加密的功能,所以也无法做到对通信整体(使用 HTTP 协议通信的请求和响应的内容)进行加密。即,HTTP 报文使用明文(指未经过加密的报文)方式发送。
②报文是否是正经用户发出的报文无法得知,可能被篡改
HTTP 协议无法证明通信的报文完整性,因此,在请求或响应送出之后直到对方接收之前的这段时间内,即使请求或响应的内容遭到篡改,也没有办法获悉。
换句话说,没有任何办法确认,发出的请求/响应和接收到的请求/响应是前后相同的。
③不验证发送方身份,可能遭遇伪装
HTTP 协议中的请求和响应不会对通信方进行确认。在 HTTP 协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发起请求。
另外,服务器只要接收到请求,不管对方是谁都会返回一个响应(但也仅限于发送端的 IP 地址和端口号没有被 Web 服务器设定限制访问的前提下)。
HTTP 协议无法验证通信方身份,任何人都可以伪造虚假服务器欺骗用户,实现钓鱼欺诈,用户无法察觉。
因为有以上问题且随着时代发展,黑客的技术能力也越来越强,非加密的 HTTP 请求很容易引起相关的网络安全问题,对于安全性能的攻防控制需求也越来越强烈。
什么是安全
既然 HTTP 不安全,那什么样的通信过程才是安全的呢?
通常认为,如果通信过程具备了四个特性,就可以认为是安全的:
- 机密性(Secrecy/Confidentiality):是指对数据的保密,只能由可信的人访问,对其他人是不可见的秘密,简单来说就是不能让不相关的人看到不该看的东西。
- 完整性(Integrity,也叫一致性):是指数据在传输过程中没有被窜改,不多也不少,完完整整地保持着原状。
- 身份认证(Authentication):是指确认对方的真实身份,也就是证明你真的是你,保证消息只能发送给可信的人。如果通信时另一方是假冒的网站,那么数据再保密也没有用,黑客完全可以使用冒充的身份套出各种信息,加密和没加密一样。
- 不可否认(Non-repudiation/Undeniable),也叫不可抵赖,意思是不能否认已经发生过的行为,不能说话不算数,耍赖皮。
使用前三个特性,可以解决安全通信的大部分问题,但如果缺了不可否认,那通信的事务真实性就得不到保证,有可能出现老赖。
比如,小明借了小红一千元,没写借条,第二天矢口否认,小红也确实拿不出借钱的证据,只能认倒霉。
另一种情况是小明借钱后还了小红,但没写收条,小红于是不承认小明还钱的事,说根本没还,要小明再掏出一千元。
所以,只有同时具备了机密性、完整性、身份认证、不可否认这四个特性,通信双方的利益才能有保障,才能算得上是真正的安全。
HTTPS 解决什么问题
HTTPS 为 HTTP 增加了刚才所说的四大安全特性。
HTTPS 其实是一个非常简单的协议,RFC 文档只有短短的 7 页,里面规定了新的协议名 HTTPS,默认端口号 443。
至于其他的什么请求,应答模式、报文结构、请求方法、URI、头字段、连接管理等等都完全沿用 HTTP,没有任何新的东西。
也就是说,除了协议名 HTTP 和端口号 80 这两点不同,HTTPS 协议在语法、语义上和 HTTP 完全一样,优缺点也照单全收(当然要除去明文和不安全 )。
HTTPS 凭什么就能做到机密性、完整性这些安全特性呢?
秘密就在于 HTTPS 名字里的 S,它把 HTTP 下层的传输协议由 TCP/IP 换成了 SSL/TLS,由 HTTP over TCP/IP 变成了 HTTP over SSL/TLS,让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文不再使用 Socket API,而是调用专门的安全接口。
HTTPS 本身并没有什么惊世骇俗的本事,全是靠着后面的 SSL/TLS 撑腰。只要学会了 SSL/TLS,HTTPS 自然就手到擒来。
HTTPS 协议的主要功能基本都依赖于 TLS/SSL 协议,TLS/SSL 的功能实现主要依赖于三类基本算法:散列函数 、对称加密和非对称加密,利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。
什么是 SSL/TLS
现在我们就来看看 SSL/TLS,它到底是个什么来历。
SSL 即安全套接层(Secure Sockets Layer),在 OSI 模型中处于第 5 层(会话层),由网景公司于 1994 年发明,有 v2 和 v3 两个版本,而 v1 因为有严重的缺陷从未公开过。
SSL 发展到 v3 时已经证明了它自身是一个非常好的安全通信协议,于是互联网工程组 IETF 在 1999 年把它改名为 TLS(传输层安全,Transport Layer Security),正式标准化,版本号从 1.0 重新算起,所以 TLS1.0 实际上就是 SSLv3.1。
到今天 TLS 已经发展出了三个版本,分别是 2006 年的 1.1、2008 年的 1.2 和去年(2018)的 1.3,每个新版本都紧跟密码学的发展和互联网的现状,持续强化安全和性能,已经成为了信息安全领域中的权威标准。
目前应用的最广泛的 TLS 是 1.2,而之前的协议(TLS1.1/1.0、SSLv3/v2)都已经被认为是不安全的,各大浏览器即将在 2020 年左右停止支持,所以接下来的讲解都针对的是 TLS1.2。
TLS 由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。
浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为密码套件(cipher suite,也叫加密套件)。
TLS 的密码套件命名非常规范,格式很固定。基本的形式是密钥交换算法+签名算法+对称加密算法+摘要算法。
比如刚才的密码套件的意思就是:握手时使用 ECDHE 算法进行密钥交换,用 RSA 签名和身份认证,握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM,摘要算法 SHA384 用于消息认证和产生随机数。
OpenSSL
说到 TLS,就不能不谈到 OpenSSL,它是一个著名的开源密码学程序库和工具包,几乎支持所有公开的加密算法和协议,已经成为了事实上的标准,许多应用软件都会使用它作为底层库来实现 TLS 功能,包括常用的 Web 服务器 Apache、Nginx 等。
OpenSSL 是从另一个开源库 SSLeay 发展出来的,曾经考虑命名为 OpenTLS,但当时(1998 年)TLS 还未正式确立,而 SSL 早已广为人知,所以最终使用了 OpenSSL 的名字。
OpenSSL 目前有三个主要的分支,1.0.2 和 1.1.0 都将在今年(2019)年底不再维护,最新的长期支持版本是 1.1.1。
由于 OpenSSL 是开源的,所以它还有一些代码分支,比如 Google 的 BoringSSL、OpenBSD 的 LibreSSL。
这些分支在 OpenSSL 的基础上删除了一些老旧代码,也增加了一些新特性,虽然背后有大金主,但离取代 OpenSSL 还差得很远。
机密性实现:加密
实现机密性最常用的手段是加密(encrypt),就是把消息用某种方式转换成谁也看不懂的乱码,只有掌握特殊钥匙的人才能再转换出原始文本。
这里的钥匙就叫做密钥(key),加密前的消息叫明文(plain text/clear text),加密后的乱码叫密文(cipher text),使用密钥还原明文的过程叫解密(decrypt)。
所有的加密算法都是公开的,任何人都可以去分析研究,而算法使用的密钥则必须保密。
这个关键的密钥又是什么呢?由于 HTTPS、TLS 都运行在计算机上,所以密钥就是一长串的数字,但约定俗成的度量单位是位(bit),而不是字节(byte)。
比如,说密钥长度是 128,就是 16 字节的二进制串,密钥长度 1024,就是 128 字节的二进制串。
按照密钥的使用方式,加密可以分为两大类:
- 对称加密
- 非对称加密
对称加密
很好理解,就是指加密和解密时使用的密钥都是同一个,是对称的,只要保证了密钥的安全,那整个通信过程就可以说具有了机密性。
举个例子,你想要登录某网站,只要事先和它约定好使用一个对称密码,通信过程中传输的全是用密钥加密后的密文,只有你和网站才能解密。
黑客即使能够窃听,看到的也只是乱码,因为没有密钥无法解出明文,所以就实现了机密性。
TLS 里有非常多的对称加密算法可供选择,比如 RC4、DES、3DES、AES、ChaCha20 等,但前三种算法都被认为是不安全的,通常都禁止使用,目前常用的有 AES-128、AES-192、AES-256 和 ChaCha20。
DES 的全称是 Data Encryption Standard(数据加密标准) ,它是用于数字数据加密的对称密钥算法。
尽管其 56 位的短密钥长度使它对于现代应用程序来说太不安全了,但它在加密技术的发展中具有很大的影响力。
AES 的意思是高级加密标准(Advanced Encryption Standard),AES-128,AES-192 和 AES-256 都是属于 AES 。
密钥长度可以是 128、192 或 256。它是 DES 算法的替代者,安全强度很高,性能也很好,而且有的硬件还会做特殊优化,所以非常流行,是应用最广泛的对称加密算法。
ChaCha20 是 Google 设计的另一种加密算法,密钥长度固定为 256 位,纯软件运行性能要超过 AES,曾经在移动客户端上比较流行,但 ARMv8 之后也加入了 AES 硬件优化,所以现在不再具有明显的优势。
分组模式
对称算法还有一个分组模式的概念,它可以让算法用固定长度的密钥加密任意长度的明文,把小秘密(即密钥)转化为大秘密(即密文)。
最早有 ECB、CBC、CFB、OFB 等几种分组模式,但都陆续被发现有安全漏洞,所以现在基本都不怎么用了。
最新的分组模式被称为 AEAD(Authenticated Encryption with Associated Data),在加密的同时增加了认证的功能,常用的是 GCM、CCM 和 Poly1305。
把上面这些组合起来,就可以得到 TLS 密码套件中定义的对称加密算法。
比如,AES128-GCM,意思是密钥长度为 128 位的 AES 算法,使用的分组模式是 GCM;ChaCha20-Poly1305 的意思是 ChaCha20 算法,使用的分组模式是 Poly1305。
非对称加密
对称加密看上去好像完美地实现了机密性,但其中有一个很大的问题:如何把密钥安全地传递给对方,术语叫密钥交换。
因为在对称加密算法中只要持有密钥就可以解密。如果你和网站约定的密钥在传递途中被黑客窃取,那他就可以在之后随意解密收发的数据,通信过程也就没有机密性可言了。
这个问题该怎么解决呢?一般来说除非是双方私下约定好了密钥,如果是每次通信都会发生变化的密钥是不能在通信过程中带给对端,这样你就陷入了要给密钥加一次密的尴尬境地。所以,就出现了非对称加密(也叫公钥加密算法)。
它有两个密钥,一个叫公钥(public key),一个叫 私钥(private key)。两个密钥是不同的,不对称,公钥可以公开给任何人使用,而私钥必须严格保密。
公钥和私钥有个特别的单向 性,虽然都可以用来加密解密,但公钥加密后只能用私钥解密,反过来,私钥加密后也只能用公钥解密。
非对称加密可以解决密钥交换的问题。网站秘密保管私钥,在网上任意分发公钥,你想要登录网站只要用公钥加密就行了,密文只能由私钥持有者才能解密。
而黑客因为没有私钥,所以就无法破解密文。
非对称加密算法的设计要比对称算法难得多,在 TLS 里只有很少的几种,比如 DH、DSA、RSA、ECC 等。
RSA 可能是其中最著名的一个,几乎可以说是非对称加密的代名词,它的安全性基于整数分解的数学难题,使用两个超大素数的乘积作为生成密钥的材料,想要从公钥推算出私钥是非常困难的。
10 年前 RSA 密钥的推荐长度是 1024,但随着计算机运算能力的提高,现在 1024 已经不安全,普遍认为至少要 2048 位。
ECC(Elliptic Curve Cryptography)是非对称加密里的后起之秀,它基于椭圆曲线离散对数的数学难题,使用特定的曲线方程和基点生成公钥和私钥,子算法 ECDHE 用于密钥交换,ECDSA 用于数字签名。
ECDHE 即使用椭圆曲线(ECC)的 DH 算法,优点是能用较小的素数(256 位)实现 RSA 相同的安全等级。
缺点是算法实现复杂,用于密钥交换的历史不长,没有经过长时间的安全攻击测试。
目前比较常用的两个曲线是 P-256(secp256r1,在 OpenSSL 称为 prime256v1)和 x25519。
P-256 是 NIST(美国国家标准技术研究所)和 NSA(美国国家安全局)推荐使用的曲线,而 x25519 被认为是最安全、最快速的曲线。
比起 RSA,ECC 在安全强度和性能上都有明显的优势。160 位的 ECC 相当于 1024 位的 RSA,而 224 位的 ECC 则相当于 2048 位的 RSA。
因为密钥短,所以相应的计算量、消耗的内存和带宽也就少,加密解密的性能就上去了,对于现在的移动互联网非常有吸引力。
混合加密
看到这里你是不是认为可以抛弃对称加密,只用非对称加密来实现机密性呢?
很遗憾,虽然非对称加密没有 密钥交换 的问题,但因为它们都是基于复杂的数学难题,运算速度很慢,即使是 ECC 也要比 AES 差上好几个数量级。
如果仅用非对称加密,虽然保证了安全,但通信速度有如乌龟、蜗牛,实用性就变成了零。
是不是能够把对称加密和非对称加密结合起来呢,两者互相取长补短,即能高效地加密解密,又能安全地密钥交换。
这就是现在 TLS 里使用的混合加密方式,其实说穿了也很简单:在通信刚开始的时候使用非对称算法,比如 RSA、ECDHE,首先解决密钥交换的问题。
然后用随机数产生对称算法使用的 「会话密钥」(session key),再用公钥加密。因为会话密钥很短,通常只有 16 字节或 32 字节,所以慢一点也无所谓。
对方拿到密文后用私钥解密,取出会话密钥。这样,双方就实现了对称密钥的安全交换,后续就不再使用非对称加密,全都使用对称加密。
这样混合加密就解决了对称加密算法的密钥交换问题,而且安全和性能兼顾,完美地实现了机密性。
完整性
摘要算法
实现完整性的手段主要是摘要算法(Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)。
你可以把摘要算法近似地理解成一种特殊的压缩算法,它能够把任意长度的数据压缩成固定长度、而且独一无二的摘要字符串,就好像是给这段数据生成了一个数字指纹。
换一个角度,也可以把摘要算法理解成特殊的单向加密算法,它只有算法,没有密钥,加密后的数据无法解密,不能从摘要逆推出原文。
摘要算法实际上是把数据从一个 大空间映射到了小空间,所以就存在冲突(collision,也叫碰撞)的可能性,就如同现实中的指纹一样,可能会有两份不同的原文对应相同的摘要。
好的摘要算法必须能够抵抗冲突,让这种可能性尽量地小。因为摘要算法对输入具有单向性和雪崩效应,输入的微小不同会导致输出的剧烈变化,所以也被 TLS 用来生成伪随机数(PRF,pseudo random function)。
你一定在日常工作中听过、或者用过 MD5(Message-Digest 5)、SHA-1(Secure Hash Algorithm 1),它们就是最常用的两个摘要算法,能够生成 16 字节和 20 字节长度的数字摘要。
但这两个算法的安全强度比较低,不够安全,在 TLS 里已经被禁止使用了。目前 TLS 推荐使用的是 SHA-1 的后继者:SHA-2。
SHA-2 实际上是一系列摘要算法的统称,总共有 6 种,常用的有 SHA224、SHA256、SHA384,分别能够生成 28 字节、32 字节、48 字节的摘要。
摘要算法保证了数字摘要和原文是完全等价的。所以,我们只要在原文后附上它的摘要,就能够保证数据的完整性。
比如,你发了条消息:有内鬼,终止交易,然后再加上一个 SHA-2 的摘要。网站收到后也计算一下消息的摘要,把这两份指纹做个对比,如果一致,就说明消息是完整可信的,没有被修改。
如果黑客在中间哪怕改动了一个标点符号,摘要也会完全不同,网站计算比对就会发现消息被窜改,是不可信的。
不过摘要算法不具有机密性,如果明文传输,那么黑客可以修改消息后把摘要也一起改了,网站还是鉴别不出完整性。
所以,真正的完整性必须要建立在机密性之上,在混合加密系统里用会话密钥加密消息和摘要,这样黑客无法得知明文,也就没有办法动手脚了。
这有个术语,叫哈希消息认证码(HMAC)。
数字签名
加密算法结合摘要算法,我们的通信过程可以说是比较安全了。但这里还有漏洞,就是通信的两个端点(endpoint)。
就像一开始所说的,黑客可以伪装成网站来窃取信息。而反过来,他也可以伪装成你,向网站发送支付、转账等消息,网站没有办法确认你的身份,钱可能就这么被偷走了。
现实生活中,解决身份认证的手段是签名和印章,只要在纸上写下签名或者盖个章,就能够证明这份文件确实是由本人而不是其他人发出的。
回想一下上面的介绍在 TLS 里有什么东西和现实中的签名、印章很像,只能由本人持有,而其他任何人都不会有呢?只要用这个东西,就能够在数字世界里证明你的身份。
没错,这个东西就是非对称加密里的私钥,使用私钥再加上摘要算法,就能够实现数字签名,同时实现身份认证和不可否认。
数字签名的原理其实很简单,就是把公钥私钥的用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。
但又因为非对称加密效率太低,所以私钥只加密原文的摘要,这样运算量就小的多,而且得到的数字签名也很小,方便保管和传输。
签名和公钥一样完全公开,任何人都可以获取。但这个签名只有用私钥对应的公钥才能解开,拿到摘要后,再比对原文验证完整性,就可以像签署文件一样证明消息确实是你发的。
刚才的这两个行为也有专用术语,叫做签名和验签。
只要你和网站互相交换公钥,就可以用签名和验签来确认消息的真实性,因为私钥保密,黑客不能伪造签名,就能够保证通信双方的身份。
比如,你用自己的私钥签名一个消息马冬梅你别跑。网站收到后用你的公钥验签,确认身份没问题,于是也用它的私钥签名消息十年翻身同学会,拆散一对是一对。
你收到后再用它的公钥验一下,也没问题,这样你和网站就都知道对方不是假冒的,后面就可以用混合加密进行安全通信了。
数字证书和 CA:身份认证
到现在,综合使用对称加密、非对称加密和摘要算法,我们已经实现了安全的四大特性,是不是已经完美了呢?
不是的,这里还有一个公钥的信任问题。因为谁都可以发布公钥,我们还缺少防止黑客伪造公钥的手段,也就是说,怎么来判断这个公钥就是你的公钥呢?
我们可以用类似密钥交换的方法来解决公钥认证问题,用别的私钥来给公钥签名,显然,这又会陷入俄罗斯套娃。
但这次实在是没招了,要终结这个死循环,就必须引入外力,找一个公认的可信第三方,让它作为信任的起点,递归的终点,构建起公钥的信任链。
这个第三方就是我们常说的CA(Certificate Authority,证书认证机构)。它就像网络世界里的公安局、教育部、公证中心,具有极高的可信度,由它来给各个公钥签名,用自身的信誉来保证公钥无法伪造,是可信的。
CA 对公钥的签名认证也是有格式的,不是简单地把公钥绑定在持有者身份上就完事了,还要包含序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成数字证书(Certificate)。
知名的 CA 全世界就那么几家,比如 DigiCert、VeriSign、Entrust、Let’s Encrypt 等,它们签发的证书分 DV、OV、EV 三种,区别在于可信程度。
DV 是最低的,只是域名级别的可信,背后是谁不知道。EV 是最高的,经过了法律和审计的严格核查,可以证明网站拥有者的身份(在浏览器地址栏会显示出公司的名字,例如 Apple、GitHub 的网站)。
不过,CA 怎么证明自己呢?这还是信任链的问题。
小一点的 CA 可以让大 CA 签名认证,但链条的最后,也就是「Root CA」,就只能自己证明自己了,这个就叫 「自签名证书」(Self-Signed Certificate)或者 「根证书」(Root Certificate)。
你必须相信,否则整个证书信任链就走不下去了。
有了这个证书体系,操作系统和浏览器都内置了各大 CA 的根证书,上网的时候只要服务器发过来它的证书,就可以验证证书里的签名。
顺着证书链(Certificate Chain)一层层地验证,直到找到根证书,就能够确定证书是可信的,从而里面的公钥也是可信的。
证书体系的弱点
证书体系(PKI,Public Key Infrastructure)虽然是目前整个网络世界的安全基础设施,但绝对的安全是不存在的,它也有弱点,还是关键的信任二字。
如果 CA 失误或者被欺骗,签发了错误的证书,虽然证书是真的,可它代表的网站却是假的。
还有一种更危险的情况,CA 被黑客攻陷,或者 CA 有恶意,因为它(即根证书)是信任的源头,整个信任链里的所有证书也就都不可信了。
这两种事情并不是耸人听闻,都曾经实际出现过。所以需要再给证书体系打上一些补丁。
针对第一种,开发出了 CRL(证书吊销列表,Certificate revocation list)和 OCSP(在线证书状态协议,Online Certificate Status Protocol),及时废止有问题的证书。
对于第二种因为涉及的证书太多,就只能操作系统或者浏览器从根上下狠手了,撤销对 CA 的信任,列入黑名单,这样它颁发的所有证书就都会被认为是不安全的。
我们来看一下 Github 的数字证书长什么样子:
证书上的信息可以得知:类型是 EV,拥有最高权威认证;过期时间;证书所属组织;证书签发机构。
TLS 协议的组成
当你在浏览器地址栏里键入 HTTPS 开头的 URI,再按下回车,会发生什么呢?浏览器首先要从 URI 里提取出协议名和域名。
因为协议名是 HTTPS,所以浏览器就知道了端口号是默认的 443,它再用 DNS 解析域名,得到目标的 IP 地址,然后就可以使用三次握手与网站建立 TCP 连接了。
在 HTTP 协议里,建立连接后,浏览器会立即发送请求报文。但现在是 HTTPS 协议,它需要再用另外一个握手过程,在 TCP 上建立安全连接,之后才是收发 HTTP 报文。
这个握手过程与 TCP 有些类似,是 HTTPS 和 TLS 协议里最重要、最核心的部分。
在讲 TLS 握手之前,先简单介绍一下 TLS 协议的组成。
TLS 包含几个子协议,你也可以理解为它是由几个不同职责的模块组成,比较常用的有:记录协议、警报协议、握手协议、变更密码规范协议等。
记录协议(Record Protocol)规定了 TLS 收发数据的基本单位:记录(record)。
它有点像是 TCP 里的 segment,所有的其他子协议都需要通过记录协议发出。
但多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。
警报协议(Alert Protocol)的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。
比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接。
握手协议(Handshake Protocol)是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。
变更密码规范协议(Change Cipher Spec Protocol),它非常简单,就是一个通知,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。
下面的这张图简要地描述了 TLS 的握手过程,其中每一个框都是一个记录,多个记录组合成一个 TCP 包发送。
所以,最多经过两次消息往返(4 个消息)就可以完成握手,然后就可以在安全的通信环境里发送 HTTP 报文,实现 HTTPS 协议。
在 TCP 完成三次握手建立连接之后, HTTPS 开始加密认证握手流程。TLS 的握手过程如下:
以上过程我们可以使用 wireShark 抓包工具看到。
在 TCP 建立连接之后,浏览器会首先发一个 client_hello 消息,里面有客户端的版本号、支持的密码套件,还有一个随机数(Client Random),用于后续生成会话密钥:
可以看到客户端发送给服务端他所支持的密码套件有 16 套之多,另外客户端使用的 TLS 版本是 1.2。
服务器收到 Client Hello 后,会返回一个 Server Hello 消息。把版本号对一下,也给出一个随机数(Server Random)。
然后从客户端的列表里选一个作为本次通信使用的密码套件。在这里它选择了 Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)。
然后服务器为了证明自己的身份,就把证书也发给了客户端(Server Certificate)。
接下来是一个关键的操作,因为服务器选择了 ECDHE 算法,所以它会在证书后发送 Server Key Exchange 消息,里面是椭圆曲线的公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。
- Handshake Protocol: Server Key Exchange
- EC Diffie-Hellman Server Params
- Curve Type: named_curve (0x03)
- Named Curve: x25519 (0x001d)
- Pubkey: 3b39deaf00217894e...
- Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
- Signature: 37141adac38ea4...
这相当于说:刚才我选的密码套件有点复杂,所以再给你个算法的参数,和刚才的随机数一样有用别丢了。为了防止别人冒充,我又盖了个章。
之后是 Server Hello Done 消息,服务器说:我的信息就是这些,打招呼完毕。
这样第一个消息往返就结束了(两个 TCP 包),结果是客户端和服务器通过明文共享了三个信息:Client Random、Server Random 和 Server Params。
客户端这时也拿到了服务器的证书,那这个证书是不是真实有效的呢?下面客户端就开始鉴定证书的真伪。校验过程如下:
①首先读取证书所有者有效期等信息进行校验,查找内置的收信人证书发布机构 CA 与证书 CA 相对比,校验是否是合法机构颁发。
这一步会做如下操作:
- 证书链的可信性 (trusted certificate path)校验,发行服务器证书的 CA 是否可靠。
- 证书是否吊销 (revocation),有两类方式离线 CRL 与在线 OCSP,不同的客户端行为会不同。
- 有效期 (expiry date),证书是否在有效时间范围。
- 域名 (domain),核查证书域名是否与当前的访问域名匹配,匹配规则后续分析。
②第一步检验通过之后产生随机数 Pre-master,并用证书公钥加密,发送给服务器,作为以后对称加密的密钥。
客户端向服务器发送 Client Key Exchange。最后客户端与服务器互发 Change Cipher Spec,Encrypted Handshake Message:
接下来服务器接收到客户端发送的 Pre-master,解密出被加密的 Pre-master,然后通知客户端:握手阶段结束,随后所有的通信将使用对称加密的方式进行。
双向认证
上面已经讲完了 TLS 握手,从上面的流程不难看出只是客户端认证了服务器的身份,而服务器是没有认证客户端身份的,我们简称单向认证。
通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。
但为了防止账号、密码被盗,有的时候(比如网上银行)还会使用 U 盾给用户颁发客户端证书,实现双向认证,这样会更加安全。
双向认证的流程也没有太多变化,只是在 Server Hello Done 之后,Client Key Exchange 之前,客户端要发送 Client Certificate 消息,服务器收到后也把证书链走一遍,验证客户端的身份。
不过 TLS1.2 已经是 10 年前(2008 年)的老协议了,虽然历经考验但毕竟 岁月不饶人,在安全、性能等方面已经跟不上如今的互联网。
经过四年近 30 个草案的反复打磨,TLS1.3 在 2018 年推出,再次确立了信息安全领域的新标准。
最大化兼容性
由于 1.1、1.2 等协议已经出现了很多年,很多应用软件、中间代理(官方称为MiddleBox)只认老的记录协议格式,更新改造很困难,甚至是不可行(设备僵化)。
在早期的试验中发现,一旦变更了记录头字段里的版本号,也就是由 0x303(TLS1.2)改为 0x304(TLS1.3)的话,大量的代理服务器、网关都无法正确处理,最终导致 TLS 握手失败。
为了保证这些被广泛部署的老设备能够继续使用,避免新协议带来的冲击,TLS1.3 不得不做出妥协,保持现有的记录格式不变,通过伪装来实现兼容,使得 TLS1.3 看上去像是 TLS1.2。
那么,该怎么区分 1.2 和 1.3 呢?
这要用到一个新的扩展协议(Extension Protocol),它有点 补充条款 的意思,通过在记录末尾添加一系列的扩展字段来增加新的功能,老版本的 TLS 不认识它可以直接忽略,这就实现了后向兼容。
在记录头的 Version 字段被兼容性固定的情况下,只要是 TLS1.3 协议,握手的 Hello 消息后面就必须有 supported_versions 扩展,它标记了 TLS 的版本号,使用它就能区分新旧协议。
你可以看一下 Client Hello 消息后面的扩展,只是因为服务器不支持 1.3,所以就后向兼容降级成了 1.2。
TLS1.3 利用扩展实现了许多重要的功能,比如supported_groups、key_share、signature_algorithms、server_name等,这些等后面用到的时候再说。
强化安全
TLS1.2 在十来年的应用中获得了许多宝贵的经验,陆续发现了很多的漏洞和加密算法的弱点,所以 TLS1.3 就在协议里修补了这些不安全因素。
比如:
- 伪随机数函数由 PRF 升级为 HKDF(HMAC-based Extract-and-Expand Key Derivation Function)。
- 明确禁止在记录协议里使用压缩。
- 废除了 RC4、DES 对称加密算法。
- 废除了 ECB、CBC 等传统分组模式。
- 废除了 MD5、SHA1、SHA-224 摘要算法。
- 废除了 RSA、DH 密钥交换算法和许多命名曲线。
经过这一番减肥瘦身之后,TLS1.3 里只保留了 AES、ChaCha20 对称加密算法,分组模式只能用 AEAD 的 GCM、CCM 和 Poly1305。
摘要算法只能用 SHA256、SHA384,密钥交换算法只有 ECDHE 和 DHE,椭圆曲线也被砍到只剩 P-256 和 x25519 等 5 种。
算法精简后带来了一个意料之中的好处:原来众多的算法、参数组合导致密码套件非常复杂,难以选择,而现在的 TLS1.3 里只有 5 个套件,无论是客户端还是服务器都不会再犯选择困难症了。
这里还要特别说一下废除 RSA 和 DH 密钥交换算法的原因。在 RSA 密钥交换中,共享密钥由客户端生成,然后客户端利用服务器的公钥(从证书中提取)将共享密钥加密并将其发送到服务器。
DH 算法由 Diffie 和 Hellman 于 1976 年发明,即所谓的 Diffie-Hellman 密钥交换。
在 Diffie-Hellman 中,客户端和服务器都从创建 DH 参数对开始。然后,他们将其 DH 参数的公共部分发送给另一方。
当双方都收到对方方的公共参数时,它们将它与自己的私钥组合在一起,最终计算出同一个值:前主密钥。然后,服务器使用数字签名来确保交换未被篡改。
如果客户端和服务器都为每次密钥交换选择一个新的 DH 参数,则该密钥交换称为 “Ephemeral”(DHE)。
DH 是一个功能强大的工具,但并非所有 DH 参数都可以 “安全” 使用。DH 的安全性取决于称为数学中离散对数问题的难度。
如果可以解决一组参数的离散对数问题,就可以提取私钥并破坏协议的安全性。
一般来说,使用的数字越大,解决离散对数问题就越困难。因此,如果您选择较小的 DH 参数,就有可能遭受攻击。
上两种模式都会使客户端和服务器具有共享密钥,但 RSA 模式有一个严重的缺点,这是因为它不具有前向安全(Forward Secrecy)。
假设有这么一个很有耐心的黑客,一直在长期收集混合加密系统收发的所有报文。
如果加密系统使用服务器证书里的 RSA 做密钥交换,一旦私钥泄露或被破解(使用社会工程学或者巨型计算机),那么黑客就能够使用私钥解密出之前所有报文的 Pre-Master,再算出会话密钥,破解所有密文。
而 ECDHE 算法在每次握手时都会生成一对临时的公钥和私钥,每次通信的密钥对都是不同的,也就是一次一密,即使黑客花大力气破解了这一次的会话密钥,也只是这次通信被攻击,之前的历史消息不会受到影响,仍然是安全的。
所以现在主流的服务器和浏览器在握手阶段都已经不再使用 RSA,改用 ECDHE,而 TLS1.3 在协议里明确废除 RSA 和 DH 则在标准层面保证了前向安全。
RSA 密钥交换在一段时间内一直存在问题,其原因不仅仅是因为它支持前向保密。而是因为想要正确的实现 RSA 密钥交换也是不容易的。
1998 年,Daniel Bleichenbacher 在 SSL 中使用 RSA 加密时发现了一个漏洞并创建了所谓的 “百万消息攻击”。
它允许攻击者通过发送数百万条消息或一些特定的消息给服务器,根据服务器响应的不同错误码计算加密密钥,进而解密消息。
多年来,这种攻击得到了改进,在某些情况下只需要数千次就可破解,这使得在笔记本电脑上都可以破解。
最近发现,许多大型网站(包括 facebook.com)在 2017 年也受到 Bleichenbacher 变种漏洞的影响,即 ROBOT 攻击。
为了降低非前向加密连接和 Bleichenbacher 漏洞所带来的风险,RSA 加密已从 TLS 1.3 中删除,将 Diffie-Hellman Ephemeral 作为唯一的密钥交换机制。
提升性能
HTTPS 建立连接时除了要做 TCP 握手,还要做 TLS 握手,在 1.2 中会多花两个消息往返(2-RTT),可能导致几十毫秒甚至上百毫秒的延迟,在移动网络中延迟还会更严重。
现在因为密码套件大幅度简化,也就没有必要再像以前那样走复杂的协商流程了。
TLS1.3 压缩了以前的 Hello 协商过程,删除了 Key Exchange 消息,把握手时间减少到了 1-RTT,效率提高了一倍。
那么它是怎么做的呢?其实具体的做法还是利用了扩展。
客户端在 Client Hello 消息里直接用 supported_groups 带上支持的曲线,比如 P-256、x25519,用 key_share 带上曲线对应的客户端公钥参数,用 signature_algorithms 带上签名算法。
服务器收到后在这些扩展里选定一个曲线和参数,再用 key_share 扩展返回服务器这边的公钥参数,就实现了双方的密钥交换,后面的流程就和 1.2 基本一样了。
除了标准的 1-RTT 握手,TLS1.3 还引入了 0-RTT 握手,用 pre_shared_key 和 early_data 扩展,在 TCP 连接后立即就建立安全连接发送加密消息,不过这需要有一些前提条件,限于篇幅这里暂且不讲解。
HTTPS 使用成本
HTTPS 截止到目前为止国内的大中型企业基本都支持并已经使用。
一般来讲,使用 HTTPS 前大家可能会非常关注如下问题:
- 证书费用以及更新维护:费用主要是证书的申请,而且现在也有了免费的证书机构,比如著名的 mozilla 发起的免费证书项目:let’s encrypt 就支持免费证书安装和自动更新。
- HTTPS 降低用户访问速度:HTTPS 对速度会有一定程度的降低,但是只要经过合理优化和部署,HTTPS 对速度的影响完全可以接受。
在很多场景下,HTTPS 速度完全不逊于 HTTP,如果使用 SPDY,HTTPS 的速度甚至还要比 HTTP 快。
作者:rickiyang
编辑:陶家龙
出处:转载自微信公众号 rickiyang