端对端加密(E2EE)-参考文章
Signal protocol是真正的端到端的通讯加密协议,号称是世界上最安全的通讯协议,任何第三方包括服务器都无法查看通讯内容,热门应用facebook messenger,whatsapp,singal app都采用的此协议。而我们所熟知的telegram的默认会话模式并不是端到端加密,服务器是可以获取用户私钥并解密聊天内容的,虽然目前报道表明telegram向任何机构出售用户聊天内容,但是用户的隐私安全由一家公司的职业操守来保障,总是不太稳妥。
signal protocol可应用在双方通讯,群组通讯中,能保证传输的消息,图片,音频,视频等文件的加密传输。即使某个消息的密钥泄露,黑客也无法解密以前的消息和之后的消息,所以signal protocol能提供前向安全和后向安全。
为了使此篇文章尽量简洁易懂,有些细节没有详述甚至被忽略,请各位看官谅解。
迪菲-赫尔曼密钥交换协议
迪菲-赫尔曼密钥交换协议(Diffie–Hellman key exchange,后文简称DH协议)可以使双方无需预先沟通,在不安全的网络中即可确定一个“协商密钥”,这个密钥可以在后续的通讯中作为对称密钥来加密消息内容。这样避免了双方网上协商密钥带来的泄露风险。假设Alice和Bob要确定一个消息密钥,DH协议的原理可以用下面的公式来表示:
DH(A的私钥,B的公钥) = 协商密钥S = DH(B的私钥,A的公钥)
DH协议算法需要2个参数:自己的私钥和对方的公钥。对于Alice和Bob来说,他们只需知道对方的公钥,计算出的密钥S就是一样的。
DH协议的应用流程如下:
Alice和Bob各自创建符合DH协议的密钥对,假设Alice密钥对为(私钥A,公钥A),Bob密钥对为(私钥B,公钥B);
双方发送自己的公钥给对方,即使有黑客监听,他只能得到公钥A,公钥B;
Alice用自己的私钥和Bob的公钥计算消息密钥为S,即DH(私钥A,公钥B)=密钥S;
Bob用自己的私钥和Alice的公钥计算消息密钥也为S;即DH(私钥B,公钥A)=密钥S;
双方同时确定了协商密钥S,后续可以通过S衍生出消息密钥,进行加密通讯。
黑客只知道公钥A和公钥B,因为不知道任意一方的私钥,所以无法计算出密钥S。
综上可知,2个密钥对可以安全地创建一个“协商密钥”。在加密通讯中使用DH协议创建密钥,是非常安全的方法。
X3DH ——DH协议的3倍扩展版
signal protocol采用的是X3DH协议创建消息密钥。X3DH协议基于DH协议,但是引入更多的公钥参数以提高安全性。在X3DH协议下,有3个角色:
会话发起者,本例我们假设是Alice;
会话接收者,本例我们假设是Bob;
服务器: 用于存储所有用户的各种公钥。
在X3DH协议里,也许是为了提高安全性,每个人都要创建3种密钥对,分别如下:
身份密钥对(Identity Key Pair) —— 一个长期的符合DH协议的密钥对,用户注册时创建,与用户身份绑定;
已签名的预共享密钥(Signed Pre Key) ——一个中期的符合DH协议的密钥对,用户注册时创建,由身份密钥签名,并定期进行轮换,此密钥可能是为了保护身份密钥不被泄露;
一次性预共享密钥(One-Time Pre Keys) —— 一次性使用的 Curve25519 密钥对队列,安装时生成,不足时补充。
所有人都要将这3种密钥对的公钥上传到服务器上,以便其他人发起会话时使用。
假如Alice要给Bob发送消息,首先要和Bob确定消息密钥,流程大致如下
Alice 要创建一个临时密钥对(ephemeral key),我们设成EPK-A,此密钥对是为了后面棘轮算法准备,在此处作用不大;
Alice从服务器获取Bob的三种密钥对的公钥:身份密钥对IPK-B;已签名的预共享密钥SPK-B;一次性预共享密钥OPK-B
Alice开始使用DH协议计算协商密钥,要引入参数包括:自己创建的2个密钥对的私钥,以及Bob的三个公钥。然后用类似排列组合的方式,将自己的私钥与对方的公钥分别带入DH算法计算,
DH1 = DH(IPK-A, SPK-B)
DH2 = DH(EPK-A, IPK-B)
DH3= DH(EPK-A, SPK-B)
DH4 = DH(IPK-A, OPK-B)
如图所示
然后将计算得到的四个值,前后连接起来,就得到了初始密钥,如下
DH = DH1 || DH2 || DH3 || DH4
注:“||”代表连接符,比如 456||123=456123
但是DH这个密钥太长,不适合作为消息密钥,所以对这个初始密钥进行一次KDF计算(KDF是密钥衍生算法的一种,可以看成加强版的hash),以衍生出固定长度的消息密钥S
S = KDF(DH1 || DH2 || DH3 || DH4)
这一步,Alice终于计算出了消息密钥S。
Alice使用消息密钥S对消息进行加密,连同自己的身份公钥IPK-A和临时公钥EPK-A一同发给Bob
Bob收到Alice的信息后,取出Alice的2个公钥,连同自己的密钥,使用与Alice相同的算法计算消息密钥S。
Bob和Alice使用消息密钥进行加密通讯。
由上可知,X3DH实际是复杂版的DH协议,解决了在不安全的网络里如何确定消息密钥的问题。但是仍然不够安全,你可以想一下,一旦消息密钥被破解(虽然概率很小,但是也有可能发生),那么Alice和Bob所有的消息就都能解密了,泄露一个密钥,所有的聊天就会被破解,这代价有些太大了。
如果可以做到每发一条消息,就换一次消息密钥,那么即使某个消息密钥被破解了,黑客也只能解密这一条消息,其它消息仍然无法解密。这样简直就完美了,于是,棘轮算法就诞生了。
棘轮算法
棘轮(ratchet)是一种特殊齿轮,这种齿轮有个特点,就是只能向一个方向旋转,而不能倒转。下图就是一个棘轮
Signal Protocol采用棘轮算法来生成消息密钥,使用1个棘轮算法,能实现每条消息使用不同的密钥,即使一条消息的密钥被破解了,只能推算后面消息的密钥,而不能向前推算之前消息的密钥,我们称之为前向安全。
如果再加上一个棘轮算法,就可以再前向安全的基础上保障后向安全,即一条消息的密钥被破解,之前和之后的消息密钥都无法推算,这种算法被称为“双棘轮算法”
Signal Protocol在双方通讯中采用的双棘轮算法是“KDF链棘轮”+“DH棘轮”。以保证消息的前向安全和后向安全。
“KDF链”棘轮
KDF是一种密钥导出函数,通过附加一些数据(数据被称为“盐”,附加数据又称“加盐”),将原始密钥导出新的密钥,提高原始密钥的保密性。公式表达为
KDF (原密钥,盐) = 导出密钥
KDF算法可用于更安全地保存用户密码,普通的密码管理方式是服务器保存用户密码的哈希值,以避免服务器被攻击后黑客拿到用户密码原文,但是一些简单密码的哈希值仍然可以通过少量的碰撞破解出来,比如123456的哈希值就很容易被碰撞出来。更加安全的做法是在用户哈希值附加其它信息(比如用户注册时间,用户住址等等),通过KDF算法导出,得出的密钥具有非常强的随机性,就很难被碰撞出来。比如原始密码是123456的哈希值为"hash (123456)",使用KDF算法得出最终密钥
KDF(hash (123456),用户注册时间)=最终密钥
服务器只保存最终密钥。这样的密码管理方式的好处是,不管用户设置的密码多么简单,服务器保存的密钥都是非常随机的,很难被碰撞出来。
“KDF链棘轮”就是运用KDF算法,设计出一种密钥不断变化的效果,的流程如下:
第一步里,将初始密钥使用KDF算法导出新的密钥,新的密钥被切成2部分,前半部分作为下一次KDF计算的输入,后半部分作为消息密钥。每迭代一次(也可以说 棘轮步进一次),就会生成新的消息密钥。
假设每发一条消息,就棘轮步进一次,那么每条消息的密钥都会不同,而且由于KDF算法的单向性,通过这条消息的密钥无法倒推出上一条消息密钥的。这就保证了密钥的前向安全。
但是这种设计不能保证后向安全,黑客一旦破解了某条密钥,并掌握了盐的内容,那么它就可以按照这种算法计算出以后所有的消息密钥。
所以,为了保证后向安全,就要设计一种算法,使每次迭代时引入的盐是随机的,从而保证每次的消息密钥是不可以向后推算的。Signal Protocol 通过增加“DH棘轮”来保证盐的随机性。
DH棘轮算法
“DH棘轮”算法能保证每次计算引入的盐的随机性。由前文可知,2对密钥对可以通过DH协议生成一个安全的协商密钥,如果更换其中一个密钥对,新的协商密钥也会变化。DH棘轮算法就是通过轮流更换一个密钥对,每次生成不同的协商密钥,作为KDF棘轮算法的盐。每进行一个消息轮回,DH棘轮就更新一次临时密钥对,盐就被更新,KDF棘轮算法生成的消息密钥就具有后向安全性。
在前文提过,Alice要给Bob发送消息,根据X3DH协议,Alice要创建一个临时密钥对(EK-A),用于创建初始消息密钥S。其实这个临时密钥对还有另外一个用途,就是用于生成第一个盐。
继续接前文Alice和Bob通过X3DH协议建立了会话,Bob要给Alice回复消息,那么DH棘轮大概的流程如下:
由上图可知,每当收到对方的消息,并回应时,自己都要新生成一个随机DH密钥对,用以生成新的DH密钥作为新盐。从而保证了每次生成的消息密钥都是完全随机的。
更复杂一点的情况,在上例中第三回合,假如Bob没有回复Alice,Alice又发了一条消息给Bob,此时消息密钥是如何计算的呢?此时因为消息还没有进行一次轮回(就像打乒乓球,球发过去了,但是对方没打回来,不算一个回合),DH密钥对不进行更新,也就是说KDF棘轮引入的盐不变,但是消息密钥仍然是变化的。流程如下
这种设计可以保证在乱序接收消息时,接收方仍能正确解密消息。篇幅有限,具体原因就不详述了。
综上所述,双棘轮算法提供加密的前向和后向安全。同时我们也知道,在Signal Protocol中,与每一个人的单独对话,都会保存单独的KDF链棘轮和DH棘轮,所以相对于普通会话,加密对话会消耗更多的运算和存储空间。
Signal Protocol在群组聊天中的设计又有所不同,由于群聊的保密性要求相对低一些,只采用了KDF链棘轮以保障加密的前向安全。
Signal Protocol 的群组聊天设计
Signal Protocol的群组聊天是通过KDF棘轮算法+公钥签名来进行加密通讯的。通讯流程是这样的,
(1) 每个群组成员都要首先生成随机 32 字节的KDF链密钥(Chain Key),用于生成消息密钥,以保障消息密钥的前向安全性,同时还要生成一个随机Curve25519 签名密钥对,用于消息签名。
(2) 每个群组成员用向其它成员单独加密发送链密钥(Chain Key)和签名公钥。此时每一个成员都拥有群内所有成员的链密钥和签名公钥。
(3) 当一名成员发送消息时,首先用KDF链棘轮算法生成的消息密钥加密消息,然后使用私钥签名,再将消息发给服务器,由服务器发送给其它成员。
(4) 其它成员收到加密消息后,首先使用发送人的签名公钥验证,验证成功后,使用相应的链密钥生成消息密钥,并用消息密钥解密。
(5) 当群组成员离开时,所有的群组成员都清除自己链密钥和签名公钥并重新生成,再次单独发给每一位成员。这样操作,离开的成员就无法查看群组内的消息了。
由上可知,一个人在不同的群组里,会生成不同的链密钥和签名密钥对,以保障群组之间的隔离。在每个群组中,每个成员还要存储其它成员的KDF链和签名公钥,如果群组成员过多,加解密运算量非常大,会影响发送和接收速度,同时密钥管理数据库也会非常大,读取效率也会降低。所以,群组聊天使用signal Protocol协议,群人数不宜太多。
以上介绍了Signal Protocol在双方通讯和群组聊天中的加密设计,可以看出,Signal Protocol是真正端到端加密通讯协议,提供了消息的前向安全和后向安全,是最安全的加密协议之一。
本文参考来源
[1] 《双棘轮算法》,烂磁头,https://blog.lancitou.net/double-ratchet-algorithm/
[2] 《【翻译】WhatsApp 加密概述(技术白皮书)》, p农民伯伯q, http://www.cnblogs.com/over140/p/8683171.html
[3] 《The X3DH Key Agreement Protocol》,Moxie Marlinspike, Trevor Perrin (editor),https://www.signal.org/docs/specifications/x3dh/
[4] 《The Double Ratchet Algorithm》,Trevor Perrin (editor), Moxie Marlinspike,https://www.signal.org/docs/specifications/doubleratchet/