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也是不可能的,因此,我们先从这种从末谋面的通信双方开始,为了讨论的方便,我们把加密通信双方成为
SS(即ServerServer)和CC(即ClientClient)而不是AA(即AliceAlice)和BB(即BobBob
),毕竟本文是在讲网络,而不是在聊安全。

我略过DHDH算法的介绍,以保证我能在一个小时内写完本文,在切入QUIC之前,只说QUIC使用了DHDH算法进行密钥协商。

*
Step 0:配置服务器SS密钥对

在SS生成一个素数pp和一个整数gg(gg是pp的一个原根,不懂可略过),同时随机生成一个数KpriKpri,计算:

Kpub=gKpri mod pKpub=gKpri mod p

将{p,g,Kpub}{p,g,Kpub}三元组打成一个config包。

*
Step 1:CC首次发起连接

CC简单地发送Client Hello到SS。

*
Step 2:SS首次回应CC

SS用config封装成一个数据包回复给CC,显然内含有{p,g,Kpub}{p,g,Kpub}元组。

*
Step 3:CC发送加密数据

CC收到{p,g,Kpub}{p,g,Kpub}后随机生成一个数Kc_priKc_pri做如下计算:
计算公钥:Kc_pub=gKc_pri mod pKc_pub=gKc_pri mod p
计算对称密钥:K1=KKc_pripub mod pK1=KpubKc_pri mod p

准备业务数据payload1,设加密函数为Enc(key,data)Enc(key,data),将下列元组D1D1发送给SS:
D1={Kc_pub,Enc(K1,payload1)}D1={Kc_pub,Enc(K1,payload1)}
注意,该阶段开始,payload便是加密的了。

*
Step 4:SS发送加密数据

SS收到D1D1后,做以下计算:
计算对称密钥:K1′=KKpric_pub mod pK1′=Kc_pubKpri mod p
可以证明,K1′K1′和CC端的K1K1是相等的:
K1′=KKpric_pub mod p=(gKc_pri mod p)Kprimod p=gKc_priKpri mod pK1′=Kc_pubKpri m
od p=(gKc_pri mod p)Kprimod p=gKc_priKpri mod p
K1=...=gKpriKc_pri mod p=gKc_priKprimod p=K1′K1=...=gKpriKc_pri mod p=gKc_priKp
rimod p=K1′

因此K1′K1′可解密密文Enc(K1,payload1)Enc(K1,payload1)获取明文payload。

也许你会觉得K1K1就可以做此后通信的对称密钥了吧,然而并不是。为了所谓的前向安全性,此时SS会继续生成第二个对称密钥K2K2

SS在发送自己的payload2之前,随机生成一个数Kn_priKn_pri,做如下计算:
计算新的通信公钥:Kn_pub=gKn_pri mod pKn_pub=gKn_pri mod p
计算新的通信对称密钥:K2=KKn_pric_pub mod pK2=Kc_pubKn_pri mod p
有了新的通信对称密钥K2K2,就可以将下面的元组发送给CC了:
D2={Kn_pub,Enc(K2,payload2)}D2={Kn_pub,Enc(K2,payload2)}
这个元组D2D2中除了包含SS的加密数据Enc(K2,payload2)Enc(K2,payload2)之外,还包括SS新生成的一个公钥。

*
Step 5:CC收到SS的D2D2

CC收到SS发来的D2D2后,解出其中的Kn_pubKn_pub,做如下运算:
计算新的通信密钥:K2′=KKc_prin_pub mod pK2′=Kn_pubKc_pri mod p(可以证明K2′=K2K2′=K2)
用K2′K2′可以正确解密出payload2。
此后的通讯,SS和CC便可以用K2K2做通信对称密钥了。

值得注意的是,这个K2K2是在1个RTT内新生成的,虽然耗费了1个RTT协商出了这个K2K2,但是在这个RTT中业务数据却依然可以加密通信的,只不过使用的是K
1K1,即使用CC记忆中的SS端配置协商出的一个“不安全”的密钥,该密钥仅仅加密一趟数据。

*
Step 6:CC和SS断开连接
SS和CC之间通信一会儿后,断开连接。


*
Step 7:CC直接发送加密数据
过了一会儿或者一段时间后,CC又想和SS通信,注意,此时CC已经有了SS的config元组{p,g,Kpub}{p,g,Kpub},也许是CC
缓存在内存了,也许是写入磁盘了,无论怎样,只要CC拥有{p,g,Kpub}{p,g,Kpub},它就可以直接从Step 3开始了,也就是说直接通过{p,g,Kp
ub}{p,g,Kpub}以及自己生成的随机数私钥计算出一个对称密钥,然后直接发送payload了。
嗯,这就是所谓的0RTT。

*
Step 8:SS发送加密数据
这里在SS收到CC的加密数据后,重复Step 4重新计算出一个新的“安全对称密钥”即可将之作为直至断开为止的对称密钥。

整个过程如下图所示:



好了,介绍完了。

所以说,QUIC的0RTT加密数据传输并非无条件的,然而请注意,QUIC的0RTT和一般意义上的Session重用思路完全不同:

* SS并没有保存CC的任何信息;
* 连接发起的0.50.5个RTT使用的密钥是临时的,在接下来的0.50.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的。

不多说。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信