简单介绍下什么是QUIC,这是从Google官方文档抄录的一段话
QUIC (Quick UDP Internet Connections) is a new transport protocol for the internet, developed by Google.
QUIC solves a number of transport-layer and application-layer problems experienced by modern web applications, while requiring little or no change from application writers. QUIC is very similar to TCP+TLS+HTTP2, but implemented on top of UDP. Having QUIC as a self-contained protocol allows innovations which aren’t possible with existing protocols as they are hampered by legacy clients and middleboxes.
简单来说,就是QUIC是Google提出的一种基于UDP改进的通信协议,其目的是降低网络通信的延迟,提供更好的用户交互体验。
原本的TCP+TLS的RTT耗时大,主要是由于TCP需要三次握手,以及TLS需要多次交换数据来生成安全的密钥
而QUIC则使用了Diffie-Hellman算法(迪菲-赫尔曼算法)来保证数据交互的安全性并合并了它的加密和握手过程来减小连接建立过程中的往返次数,以此来达到0RTT的目的
Diffie-Hellman算法
DH算法,由W.Diffie和M.E.Hellman在1976年公布的一种密钥一致性算法,该算法是一种建立密钥的方法,并非加密方法,但其产生的密钥可用于加密、密钥管理或任何其它的加密方式,这种密钥交换技术的目的在于使两个用户间能安全地交换密钥(KEY)以便用于今后的报文加密。
离散对数:定义素数p的原始根为满足如下条件的整数,如果g为p的原始根,则:g mod p,g^ mod p,…,g^(p-1) mod p是各不相同的整数,且以某种排列方式组成了从1到p-1的所有整数。
对于任意数b及素数p的原始根g,可以找到一个唯一的指数i,满足:b=(g^ i )mod p,其中0≤i≤p-1,那么指数i 称为b的以g为基数的模p的离散对数。
Diffie-Hellman算法的有效性依赖于计算离散对数的难度,当已知大素数p和它的一个原根a后,对于给定的b,要计算出i 被认为是很困难的,而给定i 计算b却相对容易。
其协商密钥的流程如下:
1)双方都共享一个大素数p和它的原始根g
2)各自产生一个随机数X,通过g^X mod p 计算出一个public value Y,将其发送给对方
3)各自在使用接收到的Y(public value)计算出相同的密钥K (K = Y^X mod p)
最终双方计算出一致的密钥,就可以用于加解密了
将这种发送给对方的key称为public key,保留在自己手上的key为private key
这里Ya,Yb就是public key,A和B就是private key
QUIC Handshake
QUIC协议依赖于合并加密和数据传输过程中的握手来创建一个安全的连接。 在连接建立成功后, 客户端会缓存起来原始的连接信息等。 在接下来与相同的服务器建立连接的过程中, 客户端能够在不增加额外RTT的情况下建立一个加密的连接,数据要发送的数据可以在握手的包中捎带着发送过去,而不用等待服务器的回复,从而实现0RTT。
所以,所谓QUIC的0RTT是指在建立连接之后,后续发送数据都不需要增加额外的RTT时间,最开始的握手还是需要1RTT的时间消耗的
注意这里的Diffie-Hellman key不单单分为public key和private key,还有各自不同的生命周期long-term key 和ephemeral key,前者用于生成初始密钥,后者用于生成后续加密的密钥
Initial Handshake:
开始握手时,客户端会像服务端发送Inchoate CHLO的命令,服务器会返回REJ消息,它包括:
- 服务端的配置server config,里面包含服务端的DH算法的long-term public value
- 对服务器进行身份认证的certificate chain
- server config签名(使用证书链的叶子证书的public key加密)
- source-address token:认证加密的block,包含了客户端的公开可见的IP地址和服务端的一个时间戳。客户端在接下来的握手中发送这个token到服务端,验证其IP地址的所有权。
接收到这个REJ消息后,客户端会进行解析认证,缓存必要的配置,之后客户端再向服务端发送一个COMPLETE CHLO, 会在其中带上客户端的ephemeral DH public value
Final (and repeat) handshake
一个连接的所有key都是通过Diffie-Hellman算法来创建的。
发送Complete CHLO之后, 客户端会产生自己的initial key (通过服务端的long-term Diffie-Hellman public value以及客户端的ephemeral Diffie-Hellman private value)
有了密钥,此时客户端就能使用initial key加密应用数据发送到服务端了(为了实现这里的0RTT,客户端不会等待COMPLETE CHLO的回复,而是直接发送加密的数据包)。 如果握手成功了, 服务端返回一个server hello(SHLO)消息。 这个消息用initial keys进行了加密, 并且含有服务端ephemeral的Diffie-Hellman的public value。
这样客户端和服务端就都有了对方的ephemeral的Diffie-Hellman的public value,这样就会生成第二个密钥 forward-secure key.服务端会使用这个密钥来加密自己对客户请求的response结果,而客户端后面的数据包也会使用forward-secure key来加密
QUIC提供了两个层面的安全性保证:
最初的handshake通过initial keys加密,后续的客户端信息和服务端信息通过forward-secure 加密
这样就可以保证密钥的前向安全性,之后也可以在通信的过程中就实现对密钥的更新。接收方意识到有新的密钥要更新时,会尝试使用新旧两种密钥对数据进行解密,直到成功才会正式更新密钥,否则会一直保留旧密钥。
客户端会缓存server config和source-address token,如果再次向相同的服务器建立连接就会直接发送Complete CHLO命令,实现0-RTT
如果source-address token或者server config过期,服务端会发送REJ消息 ,就会像Initial handshake一样,重新进行连接的建立
我认为之所以将发送连接拒绝的情形也认为是0RTT,是因为在拒绝连接的同时也返回了建立连接所需的REJ消息,这样客户端在下次交互时就可以直接传输有效的数据包给服务端而不需要额外的RTT时间
版本协商:
Quic 的客户端会在第一个packet里声明自己的version ,如果server端并不使用这个client version,那么服务端会发送一个带有所有server端支持的版本信息的Version Negotiation packet,以供客户端选择合适的版本
最后是一个Google官网关于QUIC握手的一个流程图
(1) 客户端判断本地是否已有服务器的全部配置参数,如果有则直接跳转到(5)发送client hello
(2) 客户端向服务器发送inchoate client hello(CHLO)消息,尝试建立连接
(3) 服务器收到CHLO,回复rejection(REJ)消息,其中包含服务器的server config ,long-term DH public key 等
(4) 客户端收到REJ,提取并认证和存储服务器server config
(5) 客户端向服务器发送client hello消息,开始准备发送数据包,消息中包括客户端的DH public vlaue。此时客户端可以根据server config参数和自己的public value,计算出初始密钥。
(6) 服务器收到 client hello,如果不同意连接就回复REJ,同(3);如果同意连接,根据客户端的public value计算出初始密钥initial key,并回复server hello(SHLO)消息,SHLO使用initial key加密,并且其中包含服务器的临时的DH public value
(7) 客户端收到服务器的回复,如果是REJ则情况同(4);如果是SHLO,则尝试用初始密钥解密,提取出ephemeral DH public value
(8) 客户端和服务器根据得到的ephemeral public key, 计算出新的forward-secure key
(9) 双方更换为使用 forward-secure key 通信,QUIC握手过程完毕。之后会话密钥更新的流程与以上过程类似。
参考链接:
The QUIC Transport Protocol:Design and Internet-Scale Deployment