Https
为什么要有https?
由于 HTTP 天生“明文”的特点,整个传输过程完全透明,任何人都能够在链路中截获、修改或者伪造请求 / 响应报文,数据不具有可信性。
我们在浏览网页时,现代chrome浏览器的较新的版本会在地址栏左侧有一个不安全的提示,有时候也会告诉你是有风险的链接,必须点击高级选项才让你访问。
1.使用“代理服务”,它作为 HTTP 通信的中间人,在数据上下行的时候可以添加或删除部分头字段,也可以使用黑白名单过滤 body 里的关键字,甚至直接发送虚假的请求、响应,而浏览器和源服务器都没有办法判断报文的真伪。
在新闻、视频、搜索等高流量网站来说,由于互联网上的恶意用户、恶意代理越来越多,也很容易遭到“流量劫持”的攻击,在页面里强行嵌入广告,或者分流用户,导致各种利益损失。
比如:只要你是安卓设备访问简书移动端web(可能不止简书),就会唤起一堆app。
- UC
- sjbk(xxx)
- yk(优酷)
- wph(xxx)
- jdjr(京东金融)
- pdd(拼多多)
- zfb(支付宝)
- jd(京东)
- jrtt(xxx)
- douyin(抖音)
- ......
在V2EX论坛上一直有人去分析原因,各路大神一顿脑暴,发现事情并不简单,甚至有点被恶心到了...有兴趣的自己去查一下把。随便说一句,安卓没人权,哈哈哈。
2.在网络购物、网上银行、证券交易等需要高度信任的应用场景来说是非常致命的。如果没有基本的安全保护,使用互联网进行各种电子商务、电子政务就根本无从谈起。
3.对于普通网民来说,HTTP 不安全的隐患就更大了,上网的记录会被轻易截获,网站是否真实也无法验证,黑客可以伪装成银行网站,盗取真实姓名、密码、银行卡等敏感信息,威胁人身安全和财产安全。说句难听点的,很像在裸奔,哈哈哈。
怎样的通信才安全?
在看到了上述种种不安全的场景,怎么才能让我的网络通信变安全?
通常认为,如果通信过程具备了四个特性,就可以认为是“安全”的,这四个特性是:机密性、完整性,身份认证和不可否认。
1.机密性(Secrecy/Confidentiality)是指对数据的“保密”,只能由可信的人访问,对其他人是不可见的“秘密”,简单来说就是不能让不相关的人看到不该看的东西。
比如小明和小红私下聊天,但“隔墙有耳”,被小强在旁边的房间里全偷听到了,这就是没有机密性。我们之前一直用的 Wireshark ,实际上也是利用了 HTTP 的这个特点,捕获了传输过程中的所有数据。
2.完整性(Integrity,也叫一致性)是指数据在传输过程中没有被篡改,不多也不少,“完完整整”地保持着原状。
机密性虽然可以让数据成为“秘密”,但不能防止黑客对数据的修改,黑客可以替换数据,调整数据的顺序,或者增加、删除部分数据,破坏通信过程。
比如,小明给小红写了张纸条:“明天公园见”。小强把“公园”划掉,模仿小明的笔迹把这句话改成了“明天广场见”。小红收到后无法验证完整性,信以为真,第二天的约会就告吹了。
3.身份认证(Authentication)是指确认对方的真实身份,也就是“证明你真的是你”,保证消息只能发送给可信的人。
如果通信时另一方是假冒的网站,那么数据再保密也没有用,黑客完全可以使用冒充的身份“套”出各种信息,加密和没加密一样。
比如,小明给小红写了封情书:“我喜欢你”,但不留心发给了小强。小强将错就错,假冒小红回复了一个“白日做梦”,小明不知道这其实是小强的话,误以为是小红的,后果可想而知。
4.不可否认(Non-repudiation/Undeniable),也叫不可抵赖,意思是不能否认已经发生过的行为,不能“说话不算数”“耍赖皮”。
使用前三个特性,可以解决安全通信的大部分问题,但如果缺了不可否认,那通信的事务真实性就得不到保证,有可能出现“老赖”。
比如,小明借了小红一千元,没写借条,第二天矢口否认,小红也确实拿不出借钱的证据,只能认倒霉。另一种情况是小明借钱后还了小红,但没写收条,小红于是不承认小明还钱的事,说根本没还,要小明再掏出一千元。
所以,只有同时具备了机密性、完整性、身份认证、不可否认这四个特性,通信双方的利益才能有保障,才能算得上是真正的安全。
实现通信安全的过程:
1.机密性
实现机密性最常用的手段是“加密”(encrypt),就是把消息用某种方式转换成谁也看不懂的乱码,只有掌握特殊“钥匙”的人才能再转换出原始文本。
这里的“钥匙”就叫做“密钥”(key),加密前的消息叫“明文”(plain text/clear text),加密后的乱码叫“密文”(cipher text),使用密钥还原明文的过程叫“解密”(decrypt),是加密的反操作,加密解密的操作过程就是“加密算法”。
由于 HTTPS、TLS 都运行在计算机上,所以“密钥”就是一长串的数字,但约定俗成的度量单位是“位”(bit),而不是“字节”(byte)。比如,说密钥长度是 128,就是 16 字节的二进制串,密钥长度 1024,就是 128 字节的二进制串。
对称加密: AES,DES。运行快,但有密钥安全传输的问题。
DES和AES都属于分组密码,它们只能加密固定长度的明文。如果需要加密任意长度的明文,就需要对分组密码进行迭代,而分组密码的迭代方法就称为分组密码的“分组模式”。 主要模式: ECB模式:Electronic Code Book mode(电子密码本模式) CBC模式:Cipher Block Chaining mode(密码分组链接模式)(推荐使用) CFB模式:Cipher FeedBack mode(密文反馈模式) OFB模式:Output FeedBack mode(输出反馈模式) CTR模式:CounTeR mode(计数器模式)(推荐使用)
非对称加密: RSA,ECDHE。基于复杂的数学难题(RSA基于整数分解,ECDHE基于椭圆曲线离散对数),所以运行速度很慢。公钥公开,私钥保密。
对称和非对称指的是加解密所用的密钥是否是同一个,对称加密同一个,非对称则不同。
所以TLS使用对称加密和非对称加密的混合加密,即用非对称加密会话密钥保证密钥传输安全,用对称加密产生会话密钥来加密会话内容。两者结合只保证了机密性,对于通信的安全性还只是第一步,还差得远。
黑客虽然拿不到会话密钥,无法POJIE密文,但可以通过窃听收集到足够多的密文,再尝试着修改、重组后发给网站。因为没有完整性保证,服务器只能“照单全收”,然后他就可以通过服务器的响应获取进一步的线索,最终就会POJIE出明文。
另外,黑客也可以伪造身份发布公钥。如果你拿到了假的公钥,混合加密就完全失效了。你以为自己是在和“某宝”通信,实际上网线的另一端却是黑客,银行卡号、密码等敏感信息就在“安全”的通信过程中被窃取了。
2.完整性
摘要算法
摘要算法(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 字节的摘要。
实现完整性
摘要算法保证了“数字摘要”和原文是完全等价的。所以,我们只要在原文后附上它的摘要,就能够保证数据的完整性。这个摘要的专业术语哈希消息认证码(HMAC)。
比如:A给B发送了一个:“今天我花1000买了个东西${xxxxx}”,xxxxx就是摘要。B收到这条消息后,首先计算摘要,看是否与xxxxx一致,如果一致,说明消息是完整的,没有被篡改过,是可信的。、
当然,摘要需要建立在机密性之上,所以混合加密也会对摘要进行加密。
3.身份认证和不可否认
数字签名
现代世界有各种验证身份的方式,最常见的是身份证,然后是建立在身份证绑定的手机号等等。
那么什么是通信的“身份证”?想来想去只有非对称加密的私钥符合“身份证”的属性,使用私钥再加上摘要算法,就能够实现“数字签名”,同时实现“身份认证”和“不可否认”。
数字签名的原理其实很简单,就是把公钥私钥的用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。但又因为非对称加密效率太低,所以私钥只加密原文的摘要,这样运算量就小的多,而且得到的数字签名也很小,方便保管和传输。这个过程也叫“签名”。
签名和公钥一样完全公开,任何人都可以获取。但这个签名只有用私钥对应的公钥才能解开,拿到摘要后,再比对原文验证完整性,就可以像签署文件一样证明消息确实是你发的。这个则叫“验签”
只要你和网站互相交换公钥,就可以用“签名”和“验签”来确认消息的真实性,因为私钥保密,黑客不能伪造签名,就能够保证通信双方的身份。
比如,你用自己的私钥签名一个消息“我是小明”。网站收到后用你的公钥验签,确认身份没问题,于是也用它的私钥签名消息“我是某宝”。你收到后再用它的公钥验一下,也没问题,这样你和网站就都知道对方不是假冒的,后面就可以用混合加密进行安全通信了。
https的背后
HTTPS 其实是一个“非常简单”的协议,RFC 文档很小,只有短短的 7 页,里面规定了新的协议名“https”,默认端口号 443,至于其他的什么请求 - 应答模式、报文结构、请求方法、URI、头字段、连接管理等等都完全沿用 HTTP,没有任何新的东西。
秘密就在于 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 自然就“手到擒来”。
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就是SSL更名而来
TLS1.2组成
目前应用的最广泛的 TLS 是 1.2,而之前的协议(TLS1.1/1.0、SSLv3/v2)都已经被认为是不安全的,各大浏览器即将在 2020 年左右停止支持,所以这里说的是 TLS1.2。
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)**,它非常简单,就是一个“通知”,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。
这里不得不提一嘴:两次POJIE美国安全密码的王小云团队,他们POJIE了MD5,对密码学的发展做出了巨大贡献!
浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为“密码套件”(cipher suite,也叫加密套件)。
TLS 的密码套件命名非常规范,格式也很固定。基本的形式是“密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法”,
比如:ECDHE-RSA-AES256-GCM-SHA384。这个密码套件的意思就是:
握手时使用ECDHE算法进行密钥交换,签名和身份认证使用RSA算法,握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM,摘要算法 SHA384 用于消息认证和产生随机数。
1.ECDHE 握手保证密钥安全

前面说到,HTTPS的全称是“HTTP over SSL/TLS”,也就是运行在 SSL/TLS 协议上的 HTTP。所以,它还是需要先进行TCP三次握手。然后再进行一次握手,这次握手的目的是交换密钥,建立安全通信。
1.Client Hello
浏览器会首先发一个Client Hello消息,也就是跟服务器“打招呼”。里面有客户端的版本号、支持的密码套件,还有一个随机数(Client Random),用于后续生成会话密钥。
白话: hello,我这边支持这几种密钥套件,TLS版本是1.2,和有用的随机数
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Random: 1cbf803321fd2623408dfe…
Cipher Suites (17 suites)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
2.Server Hello
服务器收到“Client Hello”后,会返回一个Server Hello消息。把版本号对一下,也给出一个随机数(Server Random),然后从客户端的列表里选一个作为本次通信使用的密码套件,在这里它选择了“TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384”。
白话:你好呀,我们确定TLS版本为1.2,并以xxx为加密的套件,并塞给你一个有用的随机数。
Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Random: 0e6320f21bae50842e96…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
3.Server Certificate
服务端为了验证自己的身份,发送了证书(Server Certificate)。如果你安装了谷歌浏览器,按下F12,可以在安全security中看到https网站的证书。
4.Server Key Exchange
紧接着,因为服务器选择了 ECDHE 算法,所以它会在证书后发送Server Key Exchange消息,里面是椭圆曲线的公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。完毕之后,发送Server Hello Done消息。
白话:喏,给你几个椭圆曲线的参数和公钥,为了防篡改我盖章了。好了,我第一步的东西都发完了,拜拜。
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...
ECDHE算法,全称叫椭圆曲线迪菲-赫尔曼金钥交换(英语:Elliptic Curve Diffie–Hellman key exchange,缩写为ECDH),是一种匿名的密钥合意协议(Key-agreement protocol),这是迪菲-赫尔曼密钥交换的变种,采用椭圆曲线密码学来加强性能与安全性。在这个协定下,双方利用由椭圆曲线密码学建立的公钥与私钥对,在一个不安全的通道中,建立起安全的共有加密资料。临时ECDH(ECDH Ephemeral,ECDHE)能够提供前向安全性。 ------ 来自维基百科
5.验证证书
客户端开始走证书链逐级验证,确认证书的真实性,再用证书公钥验证签名,就确认了服务器的身份。
白话:“刚才跟我打招呼的不是骗子,可以接着往下走。”
6.Client Key Exchange
客户端按照密码套件的要求,也生成一个椭圆曲线的公钥(Client Params),用Client Key Exchange消息发给服务器。
Handshake Protocol: Client Key Exchange
EC Diffie-Hellman Client Params
Pubkey: 8c674d0e08dc27b5eaa…
7.Pre-Master
现在客户端和服务器手里都拿到了密钥交换算法的两个参数(Client Params、Server Params),就用 ECDHE 算法计算,算出了一个新的东西,叫“Pre-Master”,其实也是一个随机数。至于具体的计算原理和过程,因为太复杂就不细说了,但算法可以保证即使黑客截获了之前的参数,也是绝对算不出这个随机数的。
现在客户端和服务器手里有了三个随机数:Client Random、Server Random 和 Pre-Master。用这三个作为原始材料,就可以生成用于加密会话的主密钥,叫“Master Secret”。而黑客因为拿不到“Pre-Master”,所以也就得不到主密钥。
为什么非得这么麻烦,非要三个随机数呢?这就必须说 TLS 的设计者考虑得非常周到了,他们不信任客户端或服务器伪随机数的可靠性,为了保证真正的“完全随机”“不可预测”,把三个不可靠的随机数混合起来,那么“随机”的程度就非常高了,足够让黑客难以猜测。
你一定很想知道“Master Secret”究竟是怎么算出来的吧,贴一下 RFC 里的公式:
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)这里的“PRF”就是伪随机数函数,它基于密码套件里的最后一个参数,比如这次的 SHA384,通过摘要算法来再一次强化“Master Secret”的随机性。
主密钥有 48 字节,但它也不是最终用于通信的会话密钥,还会再用 PRF 扩展出更多的密钥,比如客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)等等,避免只用一个密钥带来的安全隐患。
8.Change Cipher Spec与Finished
客户端发一个“Change Cipher Spec”,然后再发一个“Finished”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。
服务器也是同样的操作,发“Change Cipher Spec”和“Finished”消息,双方都验证加密解密 OK,握手正式结束。到这里还是明文传输,后面就收发被加密的 HTTP 请求和响应了。
过程中的思考与总结
1.在传输过程中,中间人可以收到Client Random ,Client Params ,Server Random ,Server Params,但它为什么无法获取到pre-master?
ECDHE流程如下(维基百科也有):
(1):客户端随机生成随机值Ra,计算Pa(x, y) = Ra * Q(x, y),Q(x, y)为全世界公认的某个椭圆曲线算法的基点。将Pa(x, y)发送至服务器。
(2):服务器随机生成随机值Rb,计算Pb(x,y) = Rb * Q(x, y)。将Pb(x, y)发送至客户端。
(3):客户端计算Sa(x, y) = Ra * Pb(x, y) = Ra * Rb * Q(x, y);服务器计算Sb(x, y) = Rb *Pa(x, y) = Rb * Ra * Q(x, y); (4):算法保证了Sa = Sb = S,提取其中的S的x向量作为密钥(预主密钥)。
版权声明:本文为CSDN博主「Mrpre」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/mrpre/java/…
这里的服务端和客户端内部生成的随机数对应了算法里的Ra与Rb(私钥),与前面明文发送的** Random**是俩码事。
** Random**参与了SSL握手的 master key的计算、KDF计算、server_key_exchange的签名值的计算,属于混淆的一部分。
随机数Random,一共32字节,其中前4个字节使用系统当前时间,后28字节使用伪随机函数生成的随机数。4个字节以Unix时间格式记录了客户端的协调世界时间(UTC)。协调世界时间是从1970年1月1日开始到当前时刻所经历的秒数,那么时间是不断的上涨的,通过前4字节填写时间方式,有效的避免了周期性的出现一样的随机数。使得“随机”更加“随机”。随机数用来干什么?随机数是用来生成对称密钥的,我们后续讲到生成对称密钥的时候,会再次提到随机数。现在只需要记住“客户端生成了一个随机数,然后发送到的服务器”
版权声明:本文为CSDN博主「Mrpre」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/mrpre/java/…
后面明文发送出去的其实是Pa与Pb(公钥),在流程图中应该叫Params。除非黑客能够解决椭圆曲线离散对数问题,要不然他无法通过Pa与Pb推导出Ra与Rb。算法能够保证Sa = Sb = S,就可以相信中间人无法得到密钥。
如果攻击者拥有大型量子计算机,那么他可以使用秀尔算法解决离散对数问题,从而POJIE私钥和共享秘密。目前的估算认为:POJIE256位素数域上的椭圆曲线,需要2330个量子比特与1260亿个托佛利门。[4]相比之下,使用秀尔算法POJIE2048位的RSA则需要4098个量子比特与5.2万亿个托佛利门。因此,椭圆曲线会更先遭到量子计算机的POJIE。目前还不存在建造如此大型量子计算机的科学技术,因此椭圆曲线密码学至少在未来十年(或更久)依然是安全的。但是密码学家已经积极展开了后量子密码学的研究。其中,超奇异椭圆曲线同源密钥交换(SIDH)有望取代当前的常规椭圆曲线密钥交换(ECDH)。 ----维基百科
2.“Hello”消息用于交换随机数,“key Exchange”消息用于交换“pre-Master”,"Change Cipher Spec"与“Finished”消息核验之前的明文消息,结束握手,进入密文消息转发。
2.RSA结合摘要算法进行签名
RSA与ECDHE一样也是属于非对称加密的算法,后者基于椭圆曲线离散对数的数学问题。而前者基于“整数分解”的数学难题,使用两个超大素数的乘积作为生成密钥的材料,想要从公钥推算出私钥是非常困难的,解决了对称加密的问题,但缺点也很明显,慢。
对比了 AES 和 RSA 这两种算法的性能,下面列出了一次测试的结果:
aes_128_cbc enc/dec 1000 times : 0.97ms, 13.11MB/s
rsa_1024 enc/dec 1000 times : 138.59ms, 93.80KB/s
rsa_1024/aes ratio = 143.17
rsa_2048 enc/dec 1000 times : 840.35ms, 15.47KB/s
rsa_2048/aes ratio = 868.13
可以看到,RSA 的运算速度是非常慢的,2048 位的加解密大约是 15KB/S(微秒或毫秒级),而 AES128 则是 13MB/S(纳秒级),差了几百倍。
好在我们找得到了最适合用它的场景,结合摘要算法,其所需要的加密的明文长度并不大。
签名,使用摘要算法得出摘要,用私钥加密摘要。
验签,用公钥揭开拿到摘要后对比原文验证完整性就OK了。
这个过程,签名与公钥是完全公开的,任何人都可以获取。只要你和网站互相交换公钥,就可以用“签名”和“验签”来确认消息的真实性,因为私钥保密,黑客不能伪造签名,就能够保证通信双方的身份。
3.数字证书
签名还有一个公钥的信任问题。因为谁都可以发布公钥,我们还缺少防止黑客伪造公钥的手段,也就是说,怎么来判断这个公钥就是你或者某宝的公钥呢?
就像建立在身份证绑定的手机号这种基础上,你为什么会相信某个身份证绑定的手机号是完全正确的,答案很清楚。
这里解决公钥认证的逻辑其实是一致的,我们需要引入一个可信任的第三方,来结束这个信任链的终点。
这个“第三方”就是我们常说的 CA(Certificate Authority,证书认证机构)。它就像网络世界里的公安局、教育部、公证中心,具有极高的可信度,由它来给各个公钥签名,用自身的信誉来保证公钥无法伪造,是可信的。
CA 对公钥的签名认证也是有格式的,不是简单地把公钥绑定在持有者身份上就完事了,还要包含序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成“数字证书”(Certificate)。
证书体系(PKI,Public Key Infrastructure)虽然是目前整个网络世界的安全基础设施,但绝对的安全是不存在的,它也有弱点,还是关键的“信任”二字。
如果 CA 失误或者被欺骗,签发了错误的证书,虽然证书是真的,可它代表的网站却是假的。
还有一种更危险的情况,CA 被黑客攻陷,或者 CA 有恶意,因为它(即根证书)是信任的源头,整个信任链里的所有证书也就都不可信了。
这两种事情并不是“耸人听闻”,都曾经实际出现过。所以,需要再给证书体系打上一些补丁。
针对第一种,开发出了 CRL(证书吊销列表,Certificate revocation list)和 OCSP(在线证书状态协议,Online Certificate Status Protocol),及时废止有问题的证书。
对于第二种,因为涉及的证书太多,就只能操作系统或者浏览器从根上“下狠手”了,撤销对 CA 的信任,列入“黑名单”,这样它颁发的所有证书就都会被认为是不安全的。
TLS1.3
TLS1.2其实是2008年的“老古董”了,已经跟不上现代互联网的高速发展,很多加密方式已经被认为不安全。个人认为,一个加密通信协议永远需要改进安全策略.
TLS1.3主要的改进方向是:安全,兼容与性能。
1.不得已而为之的兼容
由于 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 的版本号,使用它就能区分新旧协议。
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Extension: supported_versions (len=11)
Supported Version: TLS 1.3 (0x0304)
Supported Version: TLS 1.2 (0x0303)
TLS1.3 利用扩展实现了许多重要的功能,比如“supported_groups”“key_share”“signature_algorithms”“server_name” 等。
2.加强安全
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 做密钥交换,一旦私钥泄露或被POJIE(使用社会工程学或者巨型计算机),那么黑客就能够使用私钥解密出之前所有报文的“Pre-Master”,再算出会话密钥,POJIE之前的所有密文。这就是所谓的今日截获,明日POJIE。
而 ECDHE 算法在每次握手时都会生成一对临时的公钥和私钥,每次通信的密钥对都是不同的,也就是“一次一密”,即使黑客花大力气POJIE了这一次的会话密钥,也只是这次通信被攻击,之前的历史消息不会受到影响,仍然是安全的。所以现在主流的服务器和浏览器在握手阶段都已经不再使用 RSA,改用 ECDHE,而 TLS1.3 在协议里明确废除 RSA 和 DH 则在标准层面保证了“前向安全”。
在密码学中,前向保密(英语:Forward Secrecy,FS),有时也被称为完全前向保密(英语:Perfect Forward Secrecy,PFS)[1],是密码学中通讯协议的安全属性,指的是长期使用的主密钥泄漏不会导致过去的会话密钥泄漏。[2]前向保密能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。[3]如果系统具有前向保密性,就可以保证在私钥泄露时历史通讯的安全,即使系统遭到主动攻击也是如此。 --- 维基百科
更加通俗的来讲,“前向安全”,就是能保持之前的通信安全,不会因为之后的被POJIE了,会威胁到之前的通信。
3.提升性能
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.在 TCP 建立连接之后,浏览器首先还是发一个“Client Hello”。
因为 1.3 的消息兼容 1.2,所以开头的版本号、支持的密码套件和随机数(Client Random)结构都是一样的(不过这时的随机数是 32 个字节)。
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Random: cebeb6c05403654d66c2329…
Cipher Suites (18 suites)
Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
Extension: supported_versions (len=9)
Supported Version: TLS 1.3 (0x0304)
Supported Version: TLS 1.2 (0x0303)
Extension: supported_groups (len=14)
Supported Groups (6 groups)
Supported Group: x25519 (0x001d)
Supported Group: secp256r1 (0x0017)
Extension: key_share (len=107)
Key Share extension
Client Key Share Length: 105
Key Share Entry: Group: x25519
Key Share Entry: Group: secp256r1
注意: “supported_versions”表示这是 TLS1.3,“supported_groups”是支持的曲线,“key_share”是曲线对应的参数。
2.服务器收到“Client Hello”同样返回“Server Hello”消息,还是要给出一个随机数(Server Random)和选定密码套件。
Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Random: 12d2bce6568b063d3dee2…
Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
Extension: supported_versions (len=2)
Supported Version: TLS 1.3 (0x0304)
Extension: key_share (len=36)
Key Share extension
Key Share Entry: Group: x25519, Key Exchange length: 32
这时只交换了两条消息,客户端和服务器就拿到了四个共享信息:Client Random 和 Server Random、Client Params 和 Server Params,两边就可以各自用 ECDHE 算出“Pre-Master”,再用 HKDF 生成主密钥“Master Secret”,效率比 TLS1.2 提高了一大截。
3.在算出主密钥后,服务器立刻发出“Change Cipher Spec”消息,比 TLS1.2 提早进入加密通信,后面的证书等就都是加密的了,减少了握手时的明文信息泄露。
这里 TLS1.3 还有一个安全强化措施,多了个“Certificate Verify”消息,用服务器的私钥把前面的曲线、套件、参数等握手数据加了签名,作用和“Finished”消息差不多。但由于是私钥签名,所以强化了身份认证和和防窜改。
4.这两个“Hello”消息之后,客户端验证服务器证书,再发“Finished”消息,就正式完成了握手,开始收发 HTTP 报文。
优化HTTPS
在有上述知识储备后,我们能够从流程图中发现HTTPS中耗费时间的点:一种是用于加解密的计算消耗时长,一种是访问其他服务的消耗时长。
- 产生用于密钥交换的临时公私钥对(ECDHE);
- 验证证书时访问 CA 获取 CRL 或者 OCSP;
- 非对称加密解密处理来计算“Pre-Master”;
1.硬件氪金
挑选更昂贵的服务器,最好有内置的AES优化,计算能力更强,优劣不言而喻。
或者来一张SSL加速卡,加解密时调用它的API,让专门的硬件来做非对称加解密,分担CPU的计算压力。
再就是购买SSL加速服务器,用专门的服务器集群来完成握手时的加解密计算,反正一分钱一分货,自然要比加速卡强很多。
当然,氪金了你还得肝到奖励到账才行,靠简单花钱还不够,还要有一些开发适配工作,有一定的实施难度。比如,“加速服务器”中关键的一点是通信必须是“异步”的,不能阻塞应用服务器,否则加速就没有意义了。
2.更常见的软件优化
-
软件升级
软件升级实施起来比较简单,就是把现在正在使用的软件尽量升级到最新版本,比如把 Linux 内核由 2.x 升级到 4.x,把 Nginx 由 1.6 升级到 1.16,把 OpenSSL 由 1.0.1 升级到 1.1.0/1.1.1。
由于这些软件在更新版本的时候都会做性能优化、修复错误,只要运维能够主动配合,这种软件优化是最容易做的,也是最容易达成优化效果的。
但对于很多大中型公司来说,硬件升级或软件升级都是个棘手的问题,有成千上万台各种型号的机器遍布各个机房,逐一升级不仅需要大量人手,而且有较高的风险,可能会影响正常的线上服务。
所以,在软硬件升级都不可行的情况下,我们最常用的优化方式就是在现有的环境下挖掘协议自身的潜力。
-
协议优化
肯定优先选择1.3而不是1.2,前者大幅度简化了握手过程,并且废除了很多不安全的加密算法。
如果暂时不能升级到 1.3,只能用 1.2,那么握手时使用的密钥交换协议应当尽量选用椭圆曲线的 ECDHE 算法。它不仅运算速度快,安全性高,还支持“False Start”,能够把握手的消息往返由 2-RTT 减少到 1-RTT,达到与 TLS1.3 类似的效果。
另外,椭圆曲线也要选择高性能的曲线,最好是 x25519,次优选择是 P-256。对称加密算法方面,也可以选用“AES_128_GCM”,它能比“AES_256_GCM”略快一点点。
-
证书优化
在证书传输中,服务器的证书可以选择椭圆曲线(ECDSA)证书而不是 RSA 证书,因为 224 位的 ECC 相当于 2048 位的 RSA,所以椭圆曲线证书的“个头”要比 RSA 小很多,即能够节约带宽也能减少客户端的运算量,可谓“一举两得”。
在验证证书时,在经历CRL,OCSP后的多多少少的问题,新出的“OCSP Staping”(OCSP装订)它可以让服务器预先访问 CA 获取 OCSP 响应,然后在握手时随着证书一起发给客户端,免去了客户端连接 CA 服务器查询的时间。
3.通用方案:“缓存”
回顾HTTPS 建立连接的过程:先是 TCP 三次握手,然后是 TLS 一次握手。这后一次握手的重点是算出主密钥“Master Secret”,而主密钥每次连接都要重新计算,未免有点太浪费了,如果能够把“辛辛苦苦”算出来的主密钥缓存一下“重用”,不就可以免去了握手和计算的成本了吗? 嗯,没错,计算机世界通用方案:缓存。在TLS中称为会话复用(TLS session resumption)。
会话复用分两种,第一种叫“Session ID”:
就是客户端和服务器首次连接后各自保存一个会话的 ID 号,内存里存储主密钥和其他相关的信息。当客户端再次连接时发一个 ID 过来,服务器就在内存里找,找到就直接用主密钥恢复会话状态,跳过证书验证和密钥交换,只用一个消息往返就可以建立安全通信。
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Session ID: 13564734eeec0a658830cd…
Cipher Suites Length: 34
Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Session ID: 13564734eeec0a658830cd…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
服务器在“ServerHello”消息后直接发送了“Change Cipher Spec”和“Finished”消息,复用会话完成了握手。
“Session ID”是最早出现的会话复用技术,也是应用最广的,但它也有缺点,服务器必须保存每一个客户端的会话数据,对于拥有百万、千万级别用户的网站来说存储量就成了大问题,加重了服务器的负担。
第二种“Session Ticket”方案:
它有点类似 HTTP 的 Cookie,存储的责任由服务器转移到了客户端,服务器加密会话信息,用“New Session Ticket”消息发给客户端,让客户端保存。
重连的时候,客户端使用扩展“session_ticket”发送“Ticket”而不是“Session ID”,服务器解密后验证有效期,就可以恢复会话,开始加密通信。
不过“Session Ticket”方案需要使用一个固定的密钥文件(ticket_key)来加密 Ticket,为了防止密钥被POJIE,保证“前向安全”,密钥文件需要定期轮换,比如设置为一小时或者一天。
TLS1.3的预共享密钥(PSK):
“False Start”“Session ID”“Session Ticket”等方式只能实现 1-RTT,而 TLS1.3 更进一步实现了“0-RTT”,原理和“Session Ticket”差不多,但在发送 Ticket 的同时会带上应用数据(Early Data),免去了 1.2 里的服务器确认步骤,这种方式叫“Pre-shared Key”,简称为“PSK”。

但“PSK”也不是完美的,它为了追求效率而牺牲了一点安全性,容易受到“重放攻击”(Replay attack)的威胁。黑客可以截获“PSK”的数据,像复读机那样反复向服务器发送。
解决的办法是只允许安全的 GET/HEAD 方法,在消息里加入时间戳、“nonce”验证,或者“一次性票证”限制重放。
声明
此篇为学习极客时间专栏《透视HTTP协议》,自己整理的笔记,才疏学浅,难免有误。如果有侵权,请联系我删除。
此篇为学习极客时间专栏《透视HTTP协议》,自己整理的笔记,才疏学浅,难免有误。如果有侵权,请联系我删除。
此篇为学习极客时间专栏《透视HTTP协议》,自己整理的笔记,才疏学浅,难免有误。如果有侵权,请联系我删除。