大家好,又见面了,我是你们的朋友全栈君。
本文是一个以 whatsapp 为案例的,针对端对端聊天加密通讯协议整理的一个学习笔记,仅供大家学习。Signal protocol 是真正的端到端的通讯加密协议,号称是世界上最安全的通讯协议,任何第三方包括服务器都无法查看通讯内容。全篇都是围绕着 Signal protocol 进行梳理和解释,学习的内容大致分为三个大部分:1、术语解析 2、了解5577850怎么来的3、WhatsAPP 的通讯流程。
术语解析
要了解整个的加密通讯协议,首先就是要了解一些不常见的密钥交换方法和算法,可以先看第二部分,有不懂的点再回头看上面的介绍。
DH 密钥交换 (Diffie–Hellman key exchange)
DH 密钥交换是 1976 年由 Diffie 和 Hellman 共同发明的一种算法。使用这种算法,通信双方仅通过交换一些可以公开的信息就能够生成出共享的密码数字,而这一密码数字就可以被用作对称密码的密钥。DH 只是一种密钥交换的方法而不是密钥的加密算法。
虽然这种方法的名字叫 “密钥交换”,但实际上双方并没有真正交换密钥,而是通过计算生成出一个相同的共享密钥。因此,这种方法也称 DH 密钥协商。
交换的流程
流程图参考网上的一个资源,感觉是画的最好的一个。
以下是对图示内容的讲解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 1 Alice向Bob发送两个质数P和G P必须是一个非常大的质数,而G则是一个和P相关的数,称为生成元。G可以是一个较小的数字。 P和G不需要保密,被窃听者获取了也没关系。 P和G可以由Alice和Bob中的任意一方生成。 2 Alice生成一个随机数A A是一个1~P-2之间的整数。这个数是一个只有Alice知道的秘密数字,没有必要告诉Bob,也不能让窃听者知道。 3 Bob生成一个随机数B B是一个1~P-2之间的整数。这个数是一个只有Bob知道的秘密数字,没有必要高数Alice,也不能让窃听者知道。 4 Alice将 G的A次方 mod P 这个数发送给Bob 这个数让窃听者知道也没关系。 5 Bob 将 G的B次方 mod P 这个数发送给Alice 这个数让窃听者知道也没关系。 6 Alice用Bob发过来的数计算A次方并求 mod P 这个数就是共享密钥。 Alice计算的密钥 = (G^B mod(P))^A mod(P) = G^(A*B) mod(P) (百度了好久 用markdown编辑数学公式都失败了 有知道的大佬可以指点我一下) 上面将mod P的“G的B次方的A次方” 改写成了“G的A*B次方” 7 Bob用Alice发过来的数计算B次方并求 mod P Bob计算的密钥 = (G^A mod(P))^B mod(P) = G^(A*B) mod(P) 于是Alice和Bob就计算出相等的共享密钥了。 |
---|
ECDH
ECDH 是 EC 是” elliptic curves” 的意思,DH 是” Diffie-Hellman” 的意思,就是 ECC 算法和 DH 的结合使用.ECC 是建立在基于椭圆曲线的离散对数问题上的密码体制,给定椭圆曲线上的一个点 P,一个整数 k,求解 Q=kP 很容易;给定一个点 P、Q,知道 Q=kP,求整数 k 确是一个难题。ECDH 即建立在此数学难题之上。密钥磋商过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 假设密钥交换双方为Alice、Bob,其有共享曲线参数(椭圆曲线E、阶N、基点G(ji公钥))。 1 Alice生成随机整数a,计算A=a*G。 #生成Alice公钥 2 Bob生成随机整数b,计算B=b*G。 #生产Bob公钥 3 Alice将A传递给Bob。A的传递可以公开,即攻击者可以获取A 4 Bob将B传递给Alice。同理,B的传递可以公开 5 Bob收到Alice传递的A,计算Q =b*A#Bob通过自己的私钥和Alice的公钥得到对称密钥Q6) Alice收到Bob传递的B,计算Q'=a*B#Alice通过自己的私钥和Bob的公钥得到对称密钥Q' Alice、Bob双方即得Q=b*A=b*(a*G)=(b*a)*G=(a*b)*G=a*(b*G)=a*B=Q' (交换律和结合律),即双方得到一致的密钥Q 由于椭圆曲线的离散对数问题是难题,所以攻击者不可以通过A、G计算出a(这里不对ecc算法做具体的研究可以自行了解,是个比较复杂的数据问题 |
---|
Curve25519
Curve25519 是目前最高水平的 Diffie-Hellman 函数,适用于广泛的场景,由 Daniel J. Bernstein 教授设计。在密码学中,Curve25519 是一个椭圆曲线提供 128 位安全性,设计用于椭圆曲线 Diffie-Hellman(ECDH)密钥协商方案。它是最快的 ECC 曲线之一,并未被任何已知专利所涵盖。
给定一个用户的 32 字节密钥,curve25519 计算该用户的 32 字节公钥。给定该用户的 32 字节密钥和另一个用户的 32 字节公钥,curve25519 计算一个 32 字节的共享密钥提供给这两个用户使用。然后可以使用这个秘密对两个用户进行身份验证和信息加密。
X3DH ——DH 协议的 3 倍扩展版
whatsAPP 采用的是 X3DH 协议,更确切的说,是 X3ECDH 协议。
X3DH 协议基于 DH 协议,但是引入更多的公钥参数以提高安全性。在 X3DH 协议下,有 3 个角色:
1、会话发起者,本例我们假设是 Alice;
2、会话接收者,本例我们假设是 Bob;
3、服务器: 用于存储所有用户的各种公钥。
在 X3DH 协议里,也许是为了提高安全性,每个人都要创建 3 种密钥对,分别如下:
1、身份密钥对(Identity Key Pair)—— 一个长期的符合 DH 协议的密钥对,用户注册时创建,与用户身份绑定;
2、已签名的预共享密钥(Signed Pre Key)——一个中期的符合 DH 协议的密钥对,用户注册时创建,由身份密钥签名,并定期进行轮换,此密钥可能是为了保护身份密钥不被泄露;
3、一次性预共享密钥(One-Time Pre Keys)—— 一次性使用的 Curve25519 密钥对队列,安装时生成,不足时补充。
所有人都要将这 3 种密钥对的公钥上传到服务器上,以便其他人发起会话时使用。假如 Alice 要给 Bob 发送消息,首先要和 Bob 确定消息密钥,流程大致如下
1、Alice 要创建一个临时密钥对(ephemeral key),我们设成 EPK-A,此密钥对是为了后面棘轮算法准备,在此处作用不大;
2) Alice 从服务器获取 Bob 的三种密钥对的公钥:身份密钥对 IPK-B;已签名的预共享密钥 SPK-B;一次性预共享密钥 OPK-B
3) 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),以衍生出固定长度的消息密钥
SS = KDF(DH1 || DH2 || DH3 || DH4)
这一步,Alice 终于计算出了消息密钥 S。
4) Alice 使用消息密钥 S 对消息进行加密,连同自己的身份公钥 IPK-A 和临时公钥 EPK-A 一同发给 Bob
5) Bob 收到 Alice 的信息后,取出 Alice 的 2 个公钥,连同自己的密钥,使用与 Alice 相同的算法计算消息密钥 S。
6) Bob 和 Alice 使用消息密钥进行加密通讯。
由上可知,X3DH 实际是复杂版的 DH 协议,解决了在不安全的网络里如何确定消息密钥的问题。但是仍然不够安全,你可以想一下,一旦消息密钥被破解(虽然概率很小,但是也有可能发生),那么 Alice 和 Bob 所有的消息就都能解密了,泄露一个密钥,所有的聊天就会被破解,这代价有些太大了。如果可以做到每发一条消息,就换一次消息密钥,那么即使某个消息密钥被破解了,黑客也只能解密这一条消息,其它消息仍然无法解密。这样简直就完美了,于是,棘轮算法就诞生了。
棘轮算法
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 链棘轮以保障加密的前向安全。
WhatsAPP 通讯流程
客户端注册
在注册时,WhatsApp 客户端将身份公钥(public Identity Key)、已签名的预共享公钥(public Signed Pre Key)和一批一次性预共享公钥(One-Time Pre Keys)发送给服务器。WhatsApp 服务器存储用户身份相关的公钥。WhatsApp 服务器无法访问任何客户端的私钥。这里用到的就是上面说到的 X3DH。
会话初始化设置
要与另一个 WhatsApp 用户通信,WhatsApp 客户端需要先建立一个加密会话。加密会话一旦被创建,客户端就不需要再重复创建会话,除非会话失效(例如重新安装应用或更换设备)。
建立会话:
1、会话发起人为接收人申请身份公钥(public Identity Key)、已签名的预共享公钥(public Signed Pre Key)和一个一次性预共享密钥(One-Time Pre Key)。
2、服务器返回所请求的公钥。一次性预共享密钥(One-Time Pre Key)仅使用一次,因此请求完成后将从服务器删除。如果一次性预共享密钥(One-Time Pre Key)被用完且尚未补充,则返回空。
3、发起人将接收人的身份密钥(Identity Key)存为 Irecipient,将已签名的预共享密钥(Signed Pre Key)存为 Srecipient,将一次性预共享密钥(One-Time Pre Key)存为 Orecipient。
4、发起者生成一个临时的 Curve25519 密钥对 —— Einitiator
5、发起者加载自己的身份密钥(Identity Key)作为 Iinitiator
6、发起者计算主密钥 master_secret = ECDH ( Iinitiator, Srecipient ) || ECDH ( Einitiator, Irecipient ) || ECDH ( Einitiator, Srecipient ) || ECDH ( Einitiator, Orecipient )(这里不懂的可以看上面的 X3DH)。如果没有一次性预共享密钥(One-Time Pre Key),最终 ECDH 将被忽略。
7、发起者使用 HKDF 算法从 master_secret 创建一个根密钥(Root Key)和链密钥(Chain Keys)。
接收会话设置
在建立长期加密会话后,发起人可以立即向接收人发送消息,即使接收人处理离线状态。在接收方响应之前,发起方所有的消息都会包含创建会话所需的信息(在消息的 header 里)。其中包括发起人的 Einitiator 和 Iinitiator 。当接收方收到包含会话设置的消息时:
1、接收人使用自己的私钥和消息 header 里的公钥来计算相应的主密钥
2、接收人删除发起人使用的一次性预共享密钥(One-Time Pre Key)
3、发起人使用 HKDF 算法从主密钥派生出相应的根密钥(Root Key)和链密钥(Chain Keys)
交换消息
一旦建立了会话,通过 AES256 消息密钥加密(CbC 模式)和 HMAC-SHA256 验证来保护客户端交换消息。 消息密钥是短暂的且在每次发送消息后都会变化,使得用于加密消息的消息密钥不能从已发送或已接收后的会话状态中重建。 消息密钥在发送消息时对发送人的链密钥(Chain Key)进行向前的 “棘轮(ratchets)” 派生而来。此外,每次消息巡回都执行一个新的 ECDH 协议以创建一个新的链密钥(Chain Key)。通过组合即时 “哈希棘轮(hash ratchet)” 和巡回 “DH 棘轮(DH ratchet)” 提供前向安全。
通过链密钥(Chain Key)计算消息密钥(Message Key)
消息发送者每次需要新的消息密钥时,计算如下:
1、消息密钥(Message Key)= HMAC-SHA256(Chain Key, 0x01)
2、链密钥(Chain Key)随后更新为: 链密钥(Chain Key)= HMAC-SHA256(Chain Key, 0x02)
这样形成向前 “棘轮(ratchets)” 链密钥(Chain Key),这也意味不能使用存储的消息密钥推导出当前或过去的链密钥(Chain Key)值。
通过根密钥(Root Key)计算链密钥(Chain Key)
每一条发送的消息都附带一个短期的 Curve25519 公钥。一旦收到响应,新的链密钥(Chain Key)计算如下:
1、ephemeral_secret = ECDH(Ephemeralsender, Ephemeralrecipient)
2、链密钥(Chain Key),根密钥(Root Key)= HKDF(Root Key, ephemeral_secret)
一个链密钥只能给一个用户发消息,所以消息密钥不能被重用。由于消息密钥和链密钥(Chain Keys)的计算方式,消息可能会延迟、乱序或完全丢失而不会有问题。
传输媒体和附件
任何类型的大附件(视频,音频,图像或文件)也都是端对端加密的:
1、发件人(发消息的 WhatsApp 用户)生成一个 32 字节的 AES256 临时密钥和一个 32 字节 HMAC-SHA256 临时密钥。
2、发件人通过 AES256 密钥(CBC 模式)和随机 IV 给附件加密,然后附加使用 HMAC-SHA256 密文的 MAC。
3、发件人将加密的附件以上传到服务器以二进制存储。
4、发件人给收件人发送一个包含加密密钥、HMAC 密钥、加密二进制的 SHA256 哈希值和指向二进制存储的指针的加密消息
5、收件人解密消息,从服务器检索加密的二进制数据,验证 AES256 哈希,验证 MAC 并解密为明文
群组消息
传统未加密的聊天应用通常对群组消息使用 “服务器扇出(server-side fan-out)” 来发群组消息。当一个用户向群组发消息时,服务器将消息分发给每一个群组成员。
而 “客户端扇出(client-side fan-out)” 是客户端将消息发给每一个群组成员。
WhatsApp 的群组消息基于上面列出的成对加密会话构建,以便高效实现大量群组消息通过服务器扇出(server-side fan-out)。这是通过 Signal 传输协议(Signal Messaging Protocol)的 “发送者密钥(Sender Keys)” 来完成的。 WhatsApp 群组成员第一次发消息到群组:
1、发送人生成一个随机 32 字节的链密钥(Chain Key)。
2、发送人生成一个随机 Curve25519 签名密钥对。
3、发送人将 32 位链密钥(Chain Key)和签名密钥中的公钥组合成消息发送人密钥(Sender Key)。
4、发件人用成对传输协议为每个群组成员单独加密发送人密钥(Sender Keys)。
所有后续发给该群组的消息:
1、发送人从链密钥(Chain Key)中获取消息密钥(Message Key)并更新链密钥(Chain Key)
2、发送人在 CbC 模式下使用 AES256 加密消息
3、发送人使用签名密钥(Signature Key)签名密文
4、发送人将单个密文消息发给服务器,服务器将消息分发给所有群组成员
消息发送人链密钥(Chain Key)的 “哈希棘轮(hash ratchet)” 提供向前安全。当群组成员离开时时,所有剩下的群组成员都清除发送人密钥(Sender Key)并重新生成。
通话设置
WhatsApp 语音和视频通话也是端对端加密。当 WhatsApp 用户发起语音或视频通话时:
1、发起人与接收人建立加密会话(如果还没有建立过)
2、发起人生成一个随机 32 字节的安全实时传输协议(SRTP)主密钥(master secret)
3、发起人向接收人发送一个包含安全实时传输协议(SRTP)主密钥的加密消息用于发通话信号
4、如果应答了呼叫,跟着发起安全实时传输协议(SRTP)呼叫
状态
WhatsApp 状态加密方式和群组消息非常相似。给指定的一组接收人第一次发状态遵循向群组第一次发消息相同的步骤。类似地,给同一组接收人发送后续状态也遵循发群组消息相同的步骤。当状态发送人更改状态隐私设置或从地址簿种删除号码来删除接收人时,状态发送人会清除发送人密钥(Sender Key)并重新生成。
验证密钥
WhatsApp 用户还可以验证与之通信用户的密钥,以便他们能够确认未授权的第三方(或 WhatsApp)未发起中间人攻击。通过扫描二维码或通过比较 60 位数字来完成。 二维码包括:
1、版本号
2、双方的用户身份
3、双方完整的 32 字节身份公钥
当用户扫描对方的二维码时,将比较这些密钥以确保二维码中的身份密钥与服务器检索到的相匹配。 通过拼接两个用户身份密钥的 30 位数字指纹来计算 60 位数字号码。计算 30 位数字指纹步骤:
1、重复 SHA-512 哈希身份公钥和用户标识符 5200 次
2、获取最后输出哈希的前 30 个字节
3、将 30 个字节分成 6 组每组 5 字节的数据块
4、通过解析每组 5 字节数据块为 big-endian 无符号整形并且取模 10 万次转换为 5 个数字
5、把六组每组 5 个数字连接成 30 位数字
传输安全
WhatsApp 客户端和服务器之间所有通信都在单独的加密通道内分层。在 Windows Phone、iPhone 和 Android 上,这些端对端加密客户端可以使用噪音管道(Noise Pipes),使用噪声协议框架(Noise Protocol Framework)中的 Curve25519、AES-GCM 和 SHA256 实现长期运行的交互连接。
这为客户端提供了一些不错的属性:
1、极快的轻量级连接设置和恢复
2、加密隐藏元数据防止未授权的网络监听。没有透露连接用户身份相关的信息。
3、服务器上不存储客户端的安全认证信息。客户端使用 Curve25519 密钥进行身份验证,因此服务器仅保存客户端认证公钥(public authentication key)。如果服务器的用户数据库被入侵,也不会泄露个人认证凭证。
补充
WhatsApp 用户之间的消息受到端对端加密协议的保护,因此第三方和 WhatsApp 都无法获知消息内容,消息只能由接收人解密。所有 WhatsApp 消息(包括聊天、群聊、图片、视频、语音消息和文件)和 WhatsApp 通话都受到端对端加密的保护。 WhatsApp 服务器无法访问 WhatsApp 用户的私钥,并且 WhatsApp 用户可以选择验证密钥以确保其通讯完整。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/133894.html原文链接:https://javaforall.cn