QUIC作为HTTP2.0形成草案,提上日程以来最重要的(我认为是最重要的,如果你非要说TCP,就当我什么都没说)传输协议,它有很多可以快速秒掉TCP的特质,本文来介绍其中一个,即0RTT。
首先解释一下什么是0RTT。
所谓的0RTT就是,通信双方发起通信连接时,第一个数据包便可以携带有效的业务数据。而我们知道,这个使用传统的TCP是完全不可能的,除非你使能了TCP Fast Open特性,而这个很难,因为几乎没人愿意为了这个收益去对操作系统的网络协议栈大动手脚。未使能Fast Open的TCP传输第一笔数据前,至少要等1个RTT:
此外,对于HTTPS这种应用而言,由于还需要额外的TLS握手,0RTT就更不可能了。
如果碰到Certificate过大过长的,握手完成还不止3个RTT…
但是QUIC就可以。本文来解释一下它是怎么做的。
首先声明一点,如果一对使用QUIC进行加密通信的双方此前从来没有通信过,那么0RTT是不可能的,即便是QUIC也是不可能的,因此,我们先从这种从末谋面的通信双方开始,为了讨论的方便,我们把加密通信双方成为S
S(即Server
Server)和C
C(即Client
Client)而不是A
A(即Alice
Alice)和B
B(即Bob
Bob),毕竟本文是在讲网络,而不是在聊安全。
我略过DH
DH算法的介绍,以保证我能在一个小时内写完本文,在切入QUIC之前,只说QUIC使用了DH
DH算法进行密钥协商。
- Step 0:配置服务器S
- S密钥对
- 在S
- S生成一个素数p
- p和一个整数g
- g(g
- g是p
- p的一个原根,不懂可略过),同时随机生成一个数K
- pri
- Kpri,计算:
- K
- pub
- =g
- K
- pri
- mod p
- Kpub=gKpri mod p
- 将{p,g,K
- pub
- }
- {p,g,Kpub}三元组打成一个config包。
- Step 1:C
- C首次发起连接
- C
- C简单地发送Client Hello到S
- S。
- Step 2:S
- S首次回应C
- C
- S
- S用config封装成一个数据包回复给C
- C,显然内含有{p,g,K
- pub
- }
- {p,g,Kpub}元组。
- Step 3:C
- C发送加密数据
- C
- C收到{p,g,K
- pub
- }
- {p,g,Kpub}后随机生成一个数K
- c_pri
- Kc_pri做如下计算:
- 计算公钥:K
- c_pub
- =g
- K
- c_pri
- mod p
- Kc_pub=gKc_pri mod p
- 计算对称密钥:K
- 1
- =K
- K
- c_pri
- pub
- mod p
- K1=KpubKc_pri mod p
- 准备业务数据payload1,设加密函数为Enc(key,data)
- Enc(key,data),将下列元组D
- 1
- D1发送给S
- S:
- D
- 1
- ={K
- c_pub
- ,Enc(K
- 1
- ,payload1)}
- D1={Kc_pub,Enc(K1,payload1)}
- 注意,该阶段开始,payload便是加密的了。
- Step 4:S
- S发送加密数据
- S
- S收到D
- 1
- D1后,做以下计算:
- 计算对称密钥:K
- 1
- ′=K
- K
- pri
- c_pub
- mod p
- K1′=Kc_pubKpri mod p
- 可以证明,K
- 1
- ′
- K1′和C
- C端的K
- 1
- K1是相等的:
- K
- 1
- ′=K
- K
- pri
- c_pub
- mod p=(g
- K
- c_pri
- mod p)
- K
- pri
- mod p=g
- K
- c_pri
- K
- pri
- mod p
- K1′=Kc_pubKpri mod p=(gKc_pri mod p)Kprimod p=gKc_priKpri mod p
- K
- 1
- =...=g
- K
- pri
- K
- c_pri
- mod p=g
- K
- c_pri
- K
- pri
- mod p=K
- 1
- ′
- K1=...=gKpriKc_pri mod p=gKc_priKprimod p=K1′
- 因此K
- 1
- ′
- K1′可解密密文Enc(K
- 1
- ,payload1)
- Enc(K1,payload1)获取明文payload。
也许你会觉得K
1
K1就可以做此后通信的对称密钥了吧,然而并不是。为了所谓的前向安全性,此时S
S会继续生成第二个对称密钥K
2
K2
- S
- S在发送自己的payload2之前,随机生成一个数K
- n_pri
- Kn_pri,做如下计算:
- 计算新的通信公钥:K
- n_pub
- =g
- K
- n_pri
- mod p
- Kn_pub=gKn_pri mod p
- 计算新的通信对称密钥:K
- 2
- =K
- K
- n_pri
- c_pub
- mod p
- K2=Kc_pubKn_pri mod p
- 有了新的通信对称密钥K
- 2
- K2,就可以将下面的元组发送给C
- C了:
- D
- 2
- ={K
- n_pub
- ,Enc(K
- 2
- ,payload2)}
- D2={Kn_pub,Enc(K2,payload2)}
- 这个元组D
- 2
- D2中除了包含S
- S的加密数据Enc(K
- 2
- ,payload2)
- Enc(K2,payload2)之外,还包括S
- S新生成的一个公钥。
- Step 5:C
- C收到S
- S的D
- 2
- D2
- C
- C收到S
- S发来的D
- 2
- D2后,解出其中的K
- n_pub
- Kn_pub,做如下运算:
- 计算新的通信密钥:K
- 2
- ′=K
- K
- c_pri
- n_pub
- mod p
- K2′=Kn_pubKc_pri mod p(可以证明K
- 2
- ′=K
- 2
- K2′=K2)
- 用K
- 2
- ′
- K2′可以正确解密出payload2。
- 此后的通讯,S
- S和C
- C便可以用K
- 2
- K2做通信对称密钥了。
- 值得注意的是,这个K
- 2
- K2是在1个RTT内新生成的,虽然耗费了1个RTT协商出了这个K
- 2
- K2,但是在这个RTT中业务数据却依然可以加密通信的,只不过使用的是K
- 1
- K1,即使用C
- C记忆中的S
- S端配置协商出的一个“不安全”的密钥,该密钥仅仅加密一趟数据。
- Step 6:C
- C和S
- S断开连接
- S
- S和C
- C之间通信一会儿后,断开连接。
- …
- Step 7:C
- C直接发送加密数据
- 过了一会儿或者一段时间后,C
- C又想和S
- S通信,注意,此时C
- C已经有了S
- S的config元组{p,g,K
- pub
- }
- {p,g,Kpub},也许是C
- C缓存在内存了,也许是写入磁盘了,无论怎样,只要C
- C拥有{p,g,K
- pub
- }
- {p,g,Kpub},它就可以直接从Step 3开始了,也就是说直接通过{p,g,K
- pub
- }
- {p,g,Kpub}以及自己生成的随机数私钥计算出一个对称密钥,然后直接发送payload了。
- 嗯,这就是所谓的0RTT。
- Step 8:S
- S发送加密数据
- 这里在S
- S收到C
- C的加密数据后,重复Step 4重新计算出一个新的“安全对称密钥”即可将之作为直至断开为止的对称密钥。
整个过程如下图所示:
好了,介绍完了。
所以说,QUIC的0RTT加密数据传输并非无条件的,然而请注意,QUIC的0RTT和一般意义上的Session重用思路完全不同:
- S
- S并没有保存C
- C的任何信息;
- 连接发起的0.5
- 0.5个RTT使用的密钥是临时的,在接下来的0.5
- 0.5RTT使用的密钥会被重新计算。
整个过程中,我们可以领略DH算法的一些特质。从Step 2和Step 3,我们可以看到这个算法是真正的按需协商的,也就是只有到你真正需要密钥的当即,才会进行实际的运算,这就大大减少或者说基本杜绝了离线攻击的机会,此外就是,DH算法非常简单易用。
然而,DH算法仅仅可以用来做密钥协商,对于通信双方而言,身份认证是必须的,因此,实际中的QUIC远没有上面的流程那么简单,实际涉及到的领域包括X.509证书认证,算法套件管理等等复杂的内容,很显然,本文并没有包含这些东西,本人也不是很精通这些,但我认识超级精通这些的人,而且是好几个。
据说,QUIC作为一个试验场,很多idear都会被平移到更加规范的标准中,比如BBR之于TCP(不过我是非常不看好TCP的,BBR在QUIC上持续持久发展难道不更好吗?)。同样这个0RTT的思路也将会被吸纳到在途的TLS1.3版本中,非常期待。
这一切非常感谢Google,一家伟大的公司。从看到HTTP的弊端到SPDY,然后再到HTTP2.0,进而又看到了TCP的弊端,因此从SPDY/HTTP2.0直接衍生出QUIC,子啊QUIC本身的进化过程中,对于TCP也是择其善者而从之,其不善者而改之,这就是我们现在接触到的QUIC协议,集HTTP2.0,TCP于大成的QUIC协议。
不管怎么说,我个人是比较看好QUIC的。
不多说。
本文暂时没有评论,来添加一个吧(●'◡'●)