转:基于TLS1.3的微信安全通信协议mmtls介绍
编者的话:近年来⽹络安全事件层出不穷,确保亿万⽤户的安全隐私是我们微信义不容辞的责任。当然,我们更要保证⽤户稳定、快速的聊天体验,所以我们有了mmtls。⽂章⼲货满满,建议⼤家点击阅读全⽂,仔细品味!
⼀、背景
随着近些年⽹络安全事情的频繁发⽣,使得⽤户对⽹络通信安全的意识越来越强。国内外的⽹络服务提供商都逐渐提供全站的安全通信服务,如国内的淘宝、百度先后宣布已经完成了全站部署https。微信现有的安全通信协议是基于⽤户登录的时候派发的SessionKey对应⽤数据进⾏加密的,该协议在⼯程实现上,已经过多次迭代优化,但是仍然有⼀些缺点:
1. 原有的加密通信协议是存在于业务层的。加密保护的是请求包包体部分,但是包头部分是明⽂,包头包含⽤户id和请求的业务id等信
息,这主要是为了在proxy做路由所需要的。这样会存在数据被截获后建⽴映射关联分析的风险。
2. 原有的加密通信协议使⽤的密码学协议和算法与业界最新成果有差距,安全强度有待加强。
鉴于上述原因,微信需要⼀套能够加密保护Client到Server之间所有⽹络通信数据、且加密通信保护必须对业务开发⼈员透明的安全通信协议,由此诞⽣了mmtls。
⼆、⽬标
考虑到系统安全性与可⽤性和性能等指标之间可能存在相互影响,某种程度上,安全性与这些指标是负相关的。因此在设计的时候对mmtls提出了以下要求:
1. 安全性。主要体现在防窃听,防篡改,防重放,防伪造。
2. 低延迟、低资源消耗。要保证数据在传输过程中不会增加明显的延迟;后台负载增加在可接受范围。
3. 可⽤性。在⼀些极端情况下(如rver负载过⾼),后台能够控制提供降级服务,但是要注意不能被攻击者利⽤,进⾏降级攻击。
4. 可扩展性。协议要可扩展、可升级,⽅便添加安全强度更⾼的密码学组件,⽅便禁⽌过时的密码学组件。
通过分析⼀些业界公开的安全通信协议发现,它们都不能完全满⾜我们的要求,例如TLS1.2中每次建⽴⼀个安全连接都需要额外的
2~1个RTT(全握⼿需要2-RTT),对于微信这么⼀个需要频繁⽹络通信的IM软件来说,这对⽤户体验的伤害是极⼤的,尤其是在⼤量短连接存在的情况下,额外的RTT对⽤户延迟的影响是相当明显的。
在TLS1.3草案标准中提出了0-RTT(不额外增加⽹络延时)建⽴安全连接的⽅法,另外TLS协议本⾝通过版本号、CipherSuite、Extension机制提供了良好的可扩展性。但是,TLS1.3草案标准仍然在制定过程中,基于标准的实现更是遥遥⽆期,并且TLS1.3是⼀个对所有软件制定的⼀个通⽤协议,如果结合微信⾃⼰的特点,还有很⼤的优化空间。因此我们最终选择基于TLS1.3草案标准,设计实现我们⾃⼰的安全通信协议mmtls。
三、mmtls协议设计
3.1 总体架构
业务层数据加上mmtls之后,由mmtls提供安全性,保护业务数据,这类似于http加上tls后,变成https,由tls保护http数据。mmtls 处于业务层和原有的⽹络连接层之间,不影响原有的⽹络策略。对于微信来说这⾥的⽹络连接层就是微信长连接(私有协议)和微信短连接(http协议),都是基于TCP的。
图1描述的是把mmtls看成⼀个整体,它所处的位置。进⼊mmtls内部,它包含三个⼦协议:Record协议、Handshake协议、Alert协议。这其实是和TLS类似的。它们之间的关系如下图:
Handshake和Alert协议是Record协议的上层协议,业务层协议(Application Protocol)也是record协议的上层协议,在Record协议包中有⼀个字段,⽤来区分当前这个Record数据包的上层协议是Handshake、Alert还是业务协议数据包。Handshake协议⽤于完成Client与Server的握⼿协商,协商出⼀个对称加密密钥Key以及其他密码材料,⽤于后续数据加密。Alert协议⽤于通知对端发⽣错误,希望对端关闭连接,⽬前mmtls为了避免rver存在过多TCP Time-Wait状态,Alert消息只会rver发送给client,由client主动关闭连接。
说明:在下⽂中,会出现多个对称密钥和多个⾮对称密钥对,在本⽂中我会给有些密钥取⼀个专有的名字,以⽅便理解避免混淆,
如:pre_master_key,pre_shared_key,cli_pub_key,cli_pri_key 等,凡是类似xxx_pub_key、xxx_pri_key这种名字的都是⼀对⾮对称公私钥,sign_key和verify_key是⼀对签名/验签的密钥,其他的以key结尾的xxx_key都是对称密钥。
3.2 Handshake协议 --- 安全地协商出对称加密密钥
Handshake协议其实做的最主要的事情就是完成加密密钥的协商,即让通信双⽅安全地获得⼀致的对称密钥,以进⾏加密数据传输。在此基础上,还完成了⼀些优化⼯作,如复⽤ssion以减少握⼿时间。
汽车漆
在这⾥说明⼀下,为什么mmtls以及TLS协议需要⼀个Handshake⼦协议和Record⼦协议?其实“认证密钥协商+对称加密传输”这种混合加密结构,是绝⼤多数加密通信协议的通⽤结构,在mmtls/TLS中Handshake⼦协议负责密钥协商, Record⼦协议负责数据对称加密传输。造成这种混合加密结构的本质原因还是因为单独使⽤公钥加密组件或对称加密组件都有不可避免的缺点。公钥加密组件计算效率往往远低于对称加密组件,直接使⽤公钥加密组件加密业务数据,这样的性能损耗任何Server都是⽆法承受的。
⽽如果单独使⽤对称加密组件进⾏⽹络加密通信,在Internet这种不安全的信道下,这个对称加密密钥如何获取往往是⼀个难以解决的问题,因此结合两类密码组件的优势,产⽣了“认证密钥协商+对称加密传输”这种混合加密结构。另外,mmtls/TLS这种安全性和扩展性都很强的安全通信协议,在解决实际安全通信问题的时候,会有⾮常多的细节问题,因此分离出两个⼦协议来隔离复杂性。
3.2.1 带认证的密钥协商
根据TLS1.3的描述,实际上有2种1-RTT的密钥协商⽅式(1-RTT ECDHE、 1-RTT PSK)和4种0-RTT的密钥协商⽅式(0-RTT PSK, 0-RTT ECDH, 0-RTT PSK-ECDHE, 0-RTT ECDH-ECDHE),mmtls结合微信的特点,在保证安全性和性能的前提下,只保留了三种密钥协商⽅式(1-RTT ECDHE, 1-RTT PSK, 0-RTT PSK),并做了⼀些优化,后⾯会详细分析如何产⽣这种决策的。
3.2.1.1 1-RTT密钥协商
1.ECDH密钥协商
⾸先看⼀个,会遭受到攻击的密钥协商过程。通信双⽅Alice和Bob使⽤ECDH密钥交换协议进⾏密钥协商,ECDH密钥交换协议拥有两个算法:
密钥⽣成算法ECDH_Generate_Key,输出⼀个公钥和私钥对(ECDH_pub_key, ECDH_pri_key),ECDH_pri_key需要秘密地保存,ECDH_pub_key可以公开发送给对⽅。
密钥协商算法ECDH_compute_key,以对⽅的公钥和⾃⼰的私钥作为输⼊,计算出⼀个密钥Key,ECDH_compute_key算法使得通信双⽅计算出的密钥Key是⼀致的。
这样⼀来Alice和Bob仅仅通过交换⾃⼰的公钥ECDH_pub_key,就可以在Internet这种公开信道上共享⼀个相同密钥Key,然后⽤这个Key作为对称加密算法的密钥,进⾏加密通信。
但是这种密钥协商算法仍然存在⼀个问题。当Bob将他的Bob_ECDH_pub_key发送给Alice时,攻击者可以截
获Bob_ECDH_pub_key,⾃⼰运⾏ECDH_Generate_Key算法产⽣⼀个公钥/私钥对,然后把他产⽣
的公钥发送给Alice。同理,攻击者可以截获Alice发送给Bob的Alice_ECDH_pub_key,再运⾏ECDH_Generate_Key算法产⽣⼀个公钥/私钥对,并把这个公钥发送给Bob。Alice和Bob仍然可以执⾏协议,产⽣⼀个密钥Key。但实际上,Alice产⽣的密钥Key实际上是和攻击者Eve协商的;Bob产⽣的密钥Key也是和攻击者协商Eve的。这种攻击⽅法被称为中间⼈攻击(Man In The Middle Attack)。羡慕的意思
那么,有什么解决办法中间⼈攻击呢?产⽣中间⼈攻击的本质原因是协商过程中的数据没有经过端点认证,通信两端不知道收到的协商数据是来⾃对端还是来⾃中间⼈,因此单纯的“密钥协商”是不够的,还需要“带认证的密钥协商”。对数据进⾏认证其实有对称和⾮对称的两种⽅式:基于消息认证码(Message Authentication Code)的对称认证和基于签名算法的⾮对称认证。消息认证码的认证⽅式需要⼀个私密的Key,由于此时没有⼀个私密的Key,因此ECDH认证密钥协商就是ECDH密钥协商加上数字签名算法。在mmtls中我们采⽤的数字签名算法为ECDSA。
双⽅密钥协商时,再分别运⾏签名算法对⾃⼰发出的公钥ECDH_pub_key进⾏签名。收到信息后,⾸先验证签名,如果签名正确,则继续进⾏密钥协商。注意到,由于签名算法中的公钥ECDSA_verify_key是⼀直公开的,攻击者没有办法阻⽌别⼈获取公钥,除⾮完全掐断发送⽅的通信。这样⼀来,中间⼈攻击就不存在了,因为Eve⽆法伪造签名。具体过程如图5所⽰:
事实上,在实际通信过程中,只需要通信中某⼀⽅签名它的协商数据就可以保证不被中间⼈攻击,mmtls就是只对Server做认证,不对Client做认证,因为微信客户端发布出去后,任何⼈都可以获得,只要能够保证客户端程序本⾝的完整性,就相当于保证了客户端程序是由官⽅发布的,为认证合法的客户端,⽽客户端程序本⾝的完整性不是mmtls协议保护的范畴。
在这⼀点上,TLS要复杂⼀些,TLS作为⼀个通⽤的安全通信协议,可能会存在⼀些需要对Client进⾏认证的场合,因此TLS提供了可选的双⽅相互认证的能⼒,通过握⼿协商过程中选择的CipherSuite是什么类型来决定是否要对Server进⾏认证,通过Server是否发送CertificateRequest握⼿消息来决定是否要对Client进⾏认证。由于mmtls不需要对Client做认证,在这块内容上⽐TLS简洁许多,更加轻量级。
2.PSK密钥协商
PSK是在⼀次ECDH握⼿中由rver下发的内容,它的⼤致数据结构为PSK{key,ticket{key}},即PSK包含⼀个⽤来做对称加密密钥的key明⽂,以及⽤ticket_key对key进⾏加密的密⽂ticket,当然PSK是在安全信道中下发的,也就是说在⽹络中进⾏传输的时候PSK是加密的,中间⼈是拿不到key的。其中ticket_key只有rver才知道,由rver负责私密保存。
泰山有多少台阶
PSK协商⽐较简单,Client将PSK的ticket{key}部分发送给Server,由于只有Server才知道ticket_key,
因此key是不会被窃听的。Server拿到ticket后,使⽤ticket_key解密得到key,然后Server⽤基于协商得到的密钥key,对协商数据计算消息认证码来认证,这样就完成了PSK认证密钥协商。PSK认证密钥协商使⽤的都是对称算法,性能上⽐ECDH认证密钥协商要好很多。
宣传栏内容 3.2.1.2 0-RTT密钥协商
上述的两种认证密钥协商⽅式(1-RTT ECDHE, 1-RTT PSK)都需要⼀个额外RTT去获取对称加密key,在这个协商的RTT中是不带有业务数据的,全部都是协商数据。那么是否存在⼀种密钥协商⽅式是在握⼿协商的过程中就安全地将业务数据传递给对端呢?答案是有的,TLS1.3草案中提到了0-RTT密钥协商的⽅法。
制定制度
1. 0-RTT ECDH密钥协商
0-RTT 握⼿想要达到的⽬标是在握⼿的过程中,捎带业务数据到对端,这⾥难点是如何在客户端发起协商请求的时候就⽣成⼀个可信的对称密钥加密业务数据。在1-RTT ECDHE中,Client⽣成⼀对公私钥(cli_pub_key, cli_pri_key),然后将公钥cli_pub_key传递给Server,然后Server⽣成⼀对公私钥(svr_pub_key, svr_pri_key)并将公钥svr_pub_key传递给Client,Client收到svr_pub_key后才能计算出对称密钥。
上述过程(svr_pub_key, svr_pri_key)由于是临时⽣成的,需要⼀个RTT将svr_pub_key传递给客户端,如果我们能够预先⽣成⼀对公私
钥(static_svr_pub_key, static_svr_pri_key)并将static_svr_pub_key预置在Client中,那么Client可以在发起握⼿前就通
过static_svr_pub_ke和cli_pub_key⽣成⼀个对称密钥SS(Static Secret),然后⽤SS加密第⼀个业务数据包(实际上是通过SS衍⽣的密钥对业务数据进⾏加密,后⾯详述),这样将SS加密的业务数据包和cli_pub_key⼀起传给Server,Server通
过cli_pub_key和static_rver_private_key算出SS,解密业务数据包,这样就达到了0-RTT密钥协商的效果。
这⾥说明⼀下:ECDH协商中,如果公私钥对都是临时⽣成的,⼀般称为ECDHE,因此1-RTT的ECDH协商⽅式被称为1-RTT ECDHE握⼿,0-RTT 中有⼀个静态内置的公钥,因此称为0-RTT ECDH握⼿。
资源开发利用
2. 0-RTT PSK密钥协商
0-RTT PSK握⼿⽐较简单,回顾1-RTT PSK握⼿,其实在进⾏1-RTT PSK握⼿之前,Client已经有⼀
个对称加密密钥key了,就直接拿这个对称加密密钥key加密业务数据,然后将其和握⼿协商数据ticket{key}⼀起传递给Server就可以了。
3.提⾼0-RTT密钥协商的安全性
PFS(perfect forward crecy),中⽂可叫做完全前向保密。它要求⼀个密钥被破解,并不影响其他密钥的安全性,反映的密钥协商过程中,⼤致的意思是⽤来产⽣会话密钥的长期密钥泄露出去,不会造成之前通信时使⽤的会话密钥的泄露;或者密钥协商⽅案中不存在长期密钥,所有协商材料都是临时⽣成的。
上⾯所述的0-RTT ECDH密钥协商加密的数据的安全性依赖于长期保存的密钥static_svr_pri_key,如果static_svr_pri_key泄露,那么所有基于0-RTT ECDH协商的密钥SS都将被轻松计算出来,它所加密的数据也没有任何保密性可⾔,为了提⾼前向安全性,我们在进⾏0-RTT ECDH协商的过程中也进⾏ECDHE协商,这种协商⽅式称为0-RTT ECDH-ECDHE密钥协商。如下图所⽰: 这样,我们基
于static_svr_pri_key保护的数据就只有第⼀个业务数据包AppData,后续的包都是基于ES(Ephemeral Secret)对业务数据进⾏保护的。这样即使static_svr_pri_key泄露,也只有连接的第⼀个业务数据包能够被解密,提⾼前向安全性。
同样的,0-RTT PSK密钥协商加密的数据的安全性依赖于长期保存密钥ticket_key,如果ticket_key泄露,那么所有基于ticket_key进⾏保护的数据都将失去保密性,因此同样可以在0-RTT PSK密钥协商的过程中,同时完成ECDHE密钥协商,提⾼前向安全性。
3.2.2 密钥协商需要关注的细节
根据前⾯的描述可以知道,要使得密钥协商过程不被中间⼈攻击,就必须要对协商数据进⾏认证。下⾯拿1-RTT ECDHE握⼿⽅式来说明在进⾏认证过程中需要注意的细节。在1-RTT ECDHE中的认证⽅式是使⽤ECDSA签名算法的⾮对称认证⽅式,整个过程⼤致如下:Server在收到客户端的cli_pub_key后,随机⽣成⼀对ECDH公私钥(svr_pub_key, svr_pri_key),然后⽤签名密
钥sign_key对svr_pub_key进⾏签名,得到签名值Signature,并把签名值Signature和svr_pub_key⼀起发送给客户端。客户端收到之后,⽤verify_key进⾏验签(verify_key和sign_key是⼀对ECDSA密钥),验签成功后才会继续⾛协商对称密钥的流程。
上⾯的认证过程,有三个值得关注的点:
Verify_Key如何下发给客户端?
这实际上是公钥派发的问题,TLS是使⽤证书链的⽅式来派发公钥(证书),对于微信来说,如果使
⽤证书链的⽅式来派发Server的公钥(证书),⽆论⾃建Root CA还是从CA处申请证书,都会增加成本且在验签过程中会存在额外的资源消耗。由于客户端是由我们⾃⼰发布的,我们可以将verify_key直接内置在客户端,这样就避免证书链验证带来的时间消耗以及证书链传输带来的带宽消耗。
如何避免签名密钥sign_key泄露带来的影响?
如果sign_key泄露,那么任何⼈都可以伪造成Server欺骗Client,因为它拿到了sign_key,它就可以签发任何内容,Client不动产租赁税率
⽤verify_key去验证签名必然验签成功。因此sign_key如果泄露必须要能够对verify_key进⾏撤销,重新派发新的公钥。这其实和前⼀问题是紧密联系的,前⼀问题是公钥派发问题,本问题是公钥撤销问题。TLS是通过CRL和OCSP两种⽅式来撤销公钥的,但是这两种⽅式存在撤销不及时或给验证带来额外延迟的副作⽤。由于mmtls是通过内置·verify_key·在客户端,必要时通过强制升级客户端的⽅式就能完成公钥撤销及更新。另外,sign_key是需要Server⾼度保密的,⼀般不会被泄露,对于微信后台来说,类似于sign_key这样,需要长期私密保存的密钥在之前也有存在,早已形成了⼀套⽅法和流程来应对长期私密保存密钥的问题。
⽤sign_key进⾏签名的内容仅仅只包含svr_pub_key是否有隐患?
回顾⼀下,上⾯描述的带认证的ECDH协商过程,似乎已经⾜够安全,⽆懈可击了,但是,⾯对成亿的客户端发起ECDH握⼿到成千上万台接⼊层机器,每台机器对⼀个TCP连接随机⽣成不同的ECDH公私钥对,这⾥试想⼀种情况,假设某⼀台机器某⼀次⽣成的ECDH私
钥svr_pri_key1泄露,这实际上是可能的,因为临时⽣成的ECDH公私钥对本⾝没有做任何保密保存的措施,是明⽂、短暂地存放在内存中,⼀般情况没有问题,但在分布式环境,⼤量机器⼤量随机⽣成公私钥对的情况下,难保某⼀次不被泄露。
这样⽤sign_key(sign_key是长期保存,且分布式环境共享的)对svr_pub_key1进⾏签名得到签名值Signature1,此时攻击者已经拿
到svr_pri_key1,svr_pub_key1和Signature1,这样他就可以实施中间⼈攻击,让客户端每次拿到的服务器ECDH公钥都是svr_pub_key1:客户端随机⽣成ECDH公私钥对(cli_pub_key, cli_pri_key)并将cli_pub_key发给Server,中间⼈将消息拦截下来,将client_pub_key替换成⾃⼰⽣成的client_pub_key’,并将svr_pub_key1和Signature1回给Client,这样Client就通过计算ECDH_Compute_Key(svr_pub_key1, cli_pri_key)=Key1, Server通过计算ECDH_Compute_Key(client_pub_key’, svr_pub_key)=Key’,中间⼈既可以计算出Key1和Key’,这样它就可以⽤Key1和Client通信,⽤Key’和Server进⾏通信。发⽣上述被攻击的原因在于⼀次握⼿中公汽车票网上订票
钥的签名值被⽤于另外⼀次握⼿中,如果有⼀种⽅法能够使得这个签名值和⼀次握⼿⼀⼀对应,那么就能解决这个问题。
解决办法也很简单,就是在握⼿请求的ClientHello消息中带⼀个Client_Random随机值,然后在签名的时候
将Client_Random和svr_pub_key⼀起做签名,这样得到的签名值就与Client_Random对应了。mmtls在实际处理过程中,为了避免Client 的随机数⽣成器有问题,造成⽣成不够随机的Client_Random,实际上Server也会⽣成⼀个随机数Server_Random,然后在对公钥签名的时候将Client_Random、Server_Random、svr_pub_key⼀起做签名,这样由Client_Random、Server_Random保证得到的签名值唯⼀对应⼀次握⼿。
3.2.3 mmtls对认证密钥协商的选择
上⾯⼀共介绍了2种1-RTT 密钥协商⽅式和4种0-RTT 密钥协商⽅式。
PSK握⼿全程⽆⾮对称运算,Server性能消耗⼩,但前向安全性弱,ECDHE握⼿有⾮对称运算,Server性能消耗⼤,但前向安全性相对更强,那么如何结合两者优势进⾏密钥协商⽅式的选择呢?
⾸先PSK是如何获得的呢?PSK是在⼀次成功的ECDH(E)握⼿中下发的(在上⾯的图7、图8没有画
出下发PSK的部分),如果客户端没有PSK,那么显然是只能进⾏ECDH(E)握⼿了。由于PSK握⼿和ECDH(E)握⼿的巨⼤性能差异,那么在Client有PSK的情况下,应该进⾏PSK握⼿。那么在没有PSK的情况下,上⾯的1-RTT ECDHE、0-RTT ECDH、0-RTT ECDH-ECDHE具体应该选择哪⼀种呢?在有PSK的情况下,应该选择1-RTT PSK、0-RTT PSK还是0-RTT PSK-ECDHE呢?
对于握⼿⽅式的选择,我们也是⼏经过修改,最后结合微信⽹络连接的特点,我们选择了1-RTT ECDHE握⼿、1-RTT PSK握⼿、0-RTT PSK握⼿。微信⽬前有两个数据传输通道:
1.基于HTTP协议的短连接
2.基于私有协议的长连接。
微信长连接有⼀个特点,就是在建⽴好TCP连接之后,会在此TCP连接上先发⼀个长连nooping包,⽬的是验证长连接的连通性(由于长连接是私有协议,部分中间路由会过滤掉这种私有协议的数据包),这就是说长连接在建⽴时的第⼀个数据包是不会发送业务数据的,因此使⽤1-RTT的握⼿⽅式,由第⼀个握⼿包取代之前的nooping包去探测长连的连通性,这样并不会增加长连的⽹络延时,因此我们选取在长连接情况下,使⽤1-RTT ECDHE和1-RTT PSK这两种密钥协商⽅式。
微信短连接为了兼容⽼版本的HTTP协议,整个通信过程就只有⼀个RTT,也就是说Client建⽴TCP
连接后,通过HTTP POST⼀个业务请求包到Server,Server回⼀个HTTP响应,Client处理后⽴马断掉TCP连接。对于短连接,我们应该尽量使⽤0-RTT的握⼿⽅式,因为⼀个短连接原来就只存在⼀个RTT,如果我们⼤量使⽤1-RTT的握⼿⽅式,那么直接导致短连接⾄少需要2个RTT才能完成业务数据的传输,导致时延加倍,⽤户体验较差。
这⾥存在两种情况:
(1)客户端没有PSK,为了安全性,这时和长连接的握⼿⽅式⼀样,使⽤1-RTT ECDHE;
(2)客户端有PSK,这时为了减少⽹络时延,应该使⽤0-RTT PSK或0-RTT PSK-ECDHE。
在这两种握⼿⽅式下,由于业务请求包始终是基于PSK进⾏保护的,同⼀个PSK多次协商出来的对称加密key是同⼀个,这个对称加密key 的安全性依赖于ticket_key的安全性,因此0-RTT情况下,业务请求包始终是⽆法做到前向安全性。0-RTT PSK-ECDHE这种⽅式,只能保证本短连接业务响应回包的前向安全性,这带来安全性上的优势是⽐较⼩的,但是与0-RTT PSK握⼿⽅式相⽐,0-RTT PSK-ECDHE在每次握⼿对rver会多2次ECDH运算和1次ECDSA运算。微信的短连接是⾮常频繁的,这对性能影响极⼤,因此综合考虑,在客户端有PSK 的情况下,我们选择使⽤0-RTT PSK握⼿。由于0-RTT PSK握⼿安全性依赖ticket_key,为了加强安全性,在实现上,PSK必须要限制过期时间,避免长期⽤同⼀个PSK来进⾏握⼿协商;ticket_key必须定期轮换,且具有⾼度机密的运维级别。
另外,为了提⾼系统可⽤性,实际上mmtls在⼀次成功的ECDH握⼿中会下发两个PSK,⼀个⽣命周期短保证安全性,⼀个⽣命周期长保证可⽤性。在⼀次ECDH握⼿中,请求会带上⽣命周期长的PSK(如果存在的话),后台可根据负载情况进⾏权衡,选择使⽤ECDH握⼿或者PSK握⼿。
3.3 Record协议 --- 使⽤对称加密密钥进⾏安全的通信
经过上⾯的Handshake过程,此时Client和Server已经协商出了⼀致的对称加密密钥pre_master_key,那么接下来就可以直接⽤这
个pre_master_key作为密钥,选择⼀种对称加密算法(如常⽤的AES-CBC)加密业务数据,将密⽂发送给Server。是否真的就这么简单呢?实际上如果真的按这个过程进⾏加密通信是有很多安全漏洞的。
3.3.1 认证加密(Authenticated Encryption)
“加密并不是认证”在密码学中是⼀个简单的共识,但对于我们很多程序员来说,并不知道这句话的意义。加密是隐藏信息,使得在没有正确密钥的情况下,信息变得难以读懂,加密算法提供保密性,上⾯所述的AES-CBC这种算法只是提供保密性,即防⽌信息被窃听。
在信息安全领域,消息认证(message authentication)或数据源认证(data origin authentication)
表⽰数据在传输过程中没有被修改(完整性),并且接收消息的实体能够验证消息的源(端点认证)。AES-CBC这种加密算法只提供保密性,但是并不提供完整性。这似乎有点违反直觉,好像对端发给我⼀段密⽂,如果我能够解密成功,通过过程就是安全的,实则不然,就拿AES-CBC加密⼀段数据,如果中间⼈篡改部分密⽂,只要不篡改padding部分,⼤部分时候仍旧能够正常解密,只是得到的明⽂和原始明⽂不⼀样。现实中也有对消息追加CRC校验来解决密⽂被篡改问题的,实际上经过精⼼构造,即使有CRC校验仍然能够被绕过。本质的原因是在于进⾏加密安全通信过程,只使⽤了提供保密性的对称加密组件,没有使⽤提供消息完整性的密码学组件。因此只要在⽤对称加密算法加密明⽂后,再⽤消息认证码算法对密⽂计算⼀次消息认证码,将密⽂和消息认证码发送给Server,Server进⾏验证,这样就能保证安全性了。
实际上加密过程和计算消息认证码的过程,到底应该如何组合,谁先谁后,在密码学发展的历史上先后出现了三种组合⽅式:
(1)Encrypt-and-MAC (2)MAC-then-Encrypt (3)Encrypt-then-MAC,根据最新密码学动态,⽬前学术界已经⼀致同意Encrypt-then-MAC是最安全的,也就是先加密后算消息认证码的⽅式。
鉴于这个陷阱如此险恶,因此就有⼈提出将Encrypt和MAC直接集成在⼀个算法内部,让有经验的密码专家在算法内部解决安全问题,不让算法使⽤者选择,这就是这就是AEAD(Authenticated-Encryp
tion With Addtional data)类的算法。TLS1.3彻底禁⽌AEAD以外的其他算法。mmtls经过综合考虑,选择了使⽤AES-GCM这种AEAD类算法,作为协议的认证加密组件,⽽且AES-GCM也是TLS1.3要求必须实现的算法。