文章目录
  1. 1. 引言
  2. 2. Signal 简介
    1. 2.1. 历史
    2. 2.2. 优势
    3. 2.3. Signal 协议
  3. 3. 基础知识
    1. 3.1. 前向保密(Forward Secrecy)
    2. 3.2. DH(Diffie-Hellman)
    3. 3.3. ECDH
    4. 3.4. HMAC
    5. 3.5. KDF
    6. 3.6. HKDF
  4. 4. Signal 协议剖析
    1. 4.1. X3DH
      1. 4.1.1. 参与方
      2. 4.1.2. 前提条件
      3. 4.1.3. 加密符号
      4. 4.1.4. 各种秘钥
      5. 4.1.5. 执行流程
        1. 4.1.5.1. Bob 发布密钥
        2. 4.1.5.2. Alice 发送会话建立起始信息
        3. 4.1.5.3. Bob 接收 Alice 的初始消息
    2. 4.2. 双棘轮(Double Ratchet)算法
      1. 4.2.1. 初始化
      2. 4.2.2. KDF 链(KDF Chain)
      3. 4.2.3. 对称密钥棘轮(Symmetric-Key Ratchet)
      4. 4.2.4. 迪菲-赫尔曼棘轮(Diffie-Hellman Ratchet)
        1. 4.2.4.1. 与 X3DH 集成
      5. 4.2.5. 乱序消息处理
  5. 5. 参考资料

引言

才送走魔幻的2020年,2021年又继续迎来魔幻开局。

灯塔国选扛把子的闹剧,由于各大互联网社交网络不约而同对建国同志作出封号处理,让这个全球最大的网红瞬间“社交性死亡”。

更魔幻的是,建国被封号,却意外带火了Telegram、Signal等一帮以“安全”作为卖点的小众IM软件。钢铁侠马斯克在Twitter上煽风点火的一句”Use Signal“,更是让Signal的App安装量飙升,服务器甚至一度被挤爆。

那么Signal凭什么敢自称是最安全的即时通讯工具?它有没有被监听的可能?它又是怎么做到的呢?

这篇文章我会对Signal的安全性实现原理做一个简单的剖析。其中很多内容是来自Signal官方文档,有兴趣的同学建议也可以去阅读英文原文。

Signal 简介

首先对Signal做一个简单介绍。

Signal App

Signal是由 Signal Foundation 和 Signal Messenger LLC 开发运营的一款免费的加密即时通讯应用,具有iOS、安卓、Mac、Windows等多种平台客户端版本。它的功能与Facebook Messenger、WhatsApp和苹果的iMessage类似,可通过互联网发送一对一及群组消息,消息内容可包含图像及视频,它还可以用来进行语音通话。

Signal被称为是最安全的即时通讯软件。

历史

Signal的起源和发展,大致可以按以下时间线展开:

  • 2010年:著名的白帽黑客 Moxie Marlinspike 和机器人专家 Stuart Anderson 共同创立了一家名为Whisper Systems 的公司,并推出了两款安卓应用:一个是 TextSecure,该应用程序对文本消息进行加密,另一个是 RedPhone,该应用程序对语音通话进行加密。

  • 2011年:Whisper Systems 公司被 Twitter 收购,这些应用的代码正式开源对外公布。

  • 2013年:Moxie Marlinspike 离开了 Twitter。随后成立了另一家初创公司 Open Whisper Systems,并继续开发 TextSecure 和 RedPhone 的功能。

  • 2014年:TextSecure 和 RedPhone 两个应用合二为一,并改名为 Signal。

  • 2018年:Moxie Marlinspike 和 WhatsApp 的联合创始人 Brian Acton 成立了非营利组织 Signal Technology Foundation,由该组织继续开发 Signal 并保持其免费和开源软件的地位。

优势

在 Signal 的网站上有这样一段介绍:

State-of-the-art end-to-end encryption (powered by the open source Signal Protocol) keeps your conversations secure. We can’t read your messages or listen to your calls, and no one else can either. Privacy isn’t an optional mode — it’s just the way that Signal works. Every message, every call, every time.

Moxie Marlinspike 在推出 Signal 之初就曾表示,自己的使命就是要推动端到端加密通讯成为常态,在 Signal服务中,这一特性都是通过 Signal 协议来实现,而这也是 Signal 服务的基石。

并且Signal的代码(包括Signal的服务端、各种客户端及Signal协议的代码)全部开源,可以通过Github查看和下载。

Signal 协议

Signal 协议 是一种真正的端到端加密(End-to-End Encryption)的通讯协议,号称是世界上最安全的通讯协议。只有参与通讯的用户可以读取并解密信息,而任何第三方(包括服务器)都无法查看到通讯的内容。

总的来说,它可以防止潜在的窃听者(包括:通信服务商、互联网服务提供商甚至是该通讯系统的提供者),获取能够用以解密通讯内容的密钥。

这个特性的重要性对于日常使用 IM 的用户来说,不言而喻。

由于 Signal 协议卓越的安全性,目前有大量即时通讯软件也使用或借鉴参考了 Signal 协议,例如:

  • Wire
  • Skype
  • What’sApp
  • Facebook Messenger
  • Mixin Messenger
  • 。。。

我们公司内部一直在使用私有化部署的 Wire 作为内部 IM 工具。最近一年多我有部分工作内容也是与Wire的维护、优化以及各种定制化开发相关,因此也花了些时间研究了一下Wire的架构和实现,对 Signal的安全性实现也就有了一点粗浅的认识。

下面就来说说 Signal 协议的具体实现原理:

基础知识

Signal 协议本质上就是一种“密码协议”,因此要理解其设计思想,必须具备一定的密码学基础知识。

这里先简单介绍几个概念:

前向保密(Forward Secrecy)

在密码学中,前向保密(英语:Forward Secrecy,FS),有时也被称为完全前向保密(英语:Perfect Forward Secrecy,PFS),是密码学中通讯协议的安全属性,指的是长期使用的主密钥泄漏不会导致过去的会话密钥泄漏。

前向保密能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。如果系统具有前向保密性,就可以保证在私钥泄露时历史通讯的安全,即使系统遭到主动攻击也是如此。

无论长期密钥,或者中期密钥,或者某轮密钥泄露,都不会导致之前的消息被破解。

DH(Diffie-Hellman)

Diffie-Hellman 算法是 Whitefield Diffie 和 Martin Hellman 在1976年公布的一种秘钥交换算法,它是一种建立秘钥的方法,而不是加密方法,所以秘钥必须和其他一种加密算法结合使用。

这种秘钥交换技术的目的在于使两个用户安全的交换一个秘钥,以便后面的报文加密。

计算的基本思想就是:

A的私钥 + B的公钥 = 密钥 = A的公钥 + B的私钥

ECDH

ECC 算法和 DH 结合使用,用于密钥磋商,这个密钥交换算法称为 ECDH,全称是椭圆曲线迪菲-赫尔曼秘钥交换(Elliptic Curve Diffie–Hellman key Exchange),主要用来在一个不安全的通道中建立起安全的共有加密资料,一般来说交换的都是私钥,这个密钥一般作为“对称加密”的密钥而被双方在后续数据传输中使用。

ECC 是建立在基于椭圆曲线的离散对数问题上的密码体制,给定椭圆曲线上的一个点 $P$,一个整数 $k$,求解 $Q = kP$ 很容易;给定一个点$P$、$Q$,知道 $Q = kP$,求整数 $k$ 确是一个难题。

ECDH即建立在此数学难题之上。

HMAC

全称 Hash-based Message Authentication Code,用于生成摘要,验证信息的完整性以及源身份。

KDF

KDF 全称(Key Derivation Function)密钥导出函数。密码学中,密钥导出函数是指使用伪随机函数从秘密值(或者称为主密钥,就是一个原始的密钥)导出一个或多个密钥。

KDF 最常见的用途是将密码散列的方法来密码验证,也用作多方密钥协商协议的组成部分。

KDF一般可表示为:

$$ DK = KDF(Key, Salt, Iterations)$$

其中:
$DK$:派生密钥,
$KDF$:密钥导出函数;
$Key$:原始密钥或密码;
$Salt$:作为密码盐的随机数
$Iterations$:子功能的迭代次数。

HKDF

HKDF 全称(HMAC-based KDF),基于 HMAC 的密钥推导函数,它可以应用于各种协议和应用程序的构建。

HKDF 的主要目的是使用原始的密钥材料,派生出一个或更多个能达到密码学强度的密钥(主要是保证随机性)——就是将较短的密钥材料扩展成较长的密钥材料,过程中需要保证随机性。

简单地说,就是

$$HKDF(key, salt, info) => T(0), T(1), T(2), T(..)$$

Signal 协议剖析

Signal 协议的安全性主要通过以下两个大招来实现:

  1. X3DH 协议
  2. 双棘轮算法

下面就对这两个大招,分别进行说明:

X3DH

要进行安全的通信,首先需要参与通信的各方交换和协商通讯中使用的秘钥,即进行DH 或 ECDH 秘钥协商。但是即时通讯系统的一个典型使用场景是:

一个用户向另一个已经离线(或掉线)的用户发送信息,信息先被服务器暂存,待离线用户重新上线后,再从服务器接收该消息。

由于通信各方并非同时在线,也就无法进行DH 或 ECDH。

Signal 使用了三方密钥协商协议(X3DH,Extended Triple Diffie-Hellman)来解决这个问题,该协议由 Moxie Marlinspike 和 Trevor Perrin 开发。

X3DH 是 ECDH 的扩展,相比 ECDH,最大的不同就是引入了一个中间服务器的角色,在中间服务器的协助下实现了通讯双方异步地并且只与服务器进行交互的情况下进行密钥协商,这样在某一方在离线的时候也可以进行密钥交换来建立共享密钥,同时根据用户唯一的身份密钥提供身份认证,以及前向安全性。。

下面详细说一下X3DH的具体实现:

参与方

在X3DH协议中,有3个角色参与:

序号 角色 代号 说明
1 会话发起者 Alice Alice 希望通过加密信道向 Bob 发送一些初始数据,并建立一个可用于双向通信的共享密钥。
2 会话接收者 Bob Bob 允许 Alice 与他建立共享密钥并发送加密数据。然而 Bob 可能会暂时处于离线状态。
3 中间服务器 Server 通信各方要各种数据

前提条件

使用 X3DH 首先必须指定几个参数:

序号 参数名 说明
1 椭圆曲线 X25519 或 X448
2 哈希函数 256 或 512 bit 的哈希函数(比如:SHA-256、SHA-512)
3 应用信息 一个简单的字符串,比如:应用的名称等

加密符号

在X3DH 中使用了以下加密符号:

  • 字节序列连接符:字节序列 $X$ 和 $Y$ 的连接符表示为 $X || Y$
  • $DH(PK1, PK2)$:基于公钥 $PK1$ 和 $PK2$ 进行 ECDH 建立的共享密钥。
  • $Sig(PK, M)$:使用 XEdDSA 对字节序列 $M$ 进行签名,并使用公钥 $PK$ 进行验证。
  • $KDF(KM)$:$HKDF$ 密钥导出函数的结果。

各种秘钥

在 X3DH中使用了以下一些秘钥:

  • $IK_A$:Alice 的身份认证公钥 
  • $EK_A$:Alice 的临时公钥 
  • $IK_B$:Bob 的身份认证公钥
  • $SPK_B$:Bob 的已签名预设公钥(prekey) 
  • $OPK_B$:Bob的单次预设公钥 

$IK_A$ 和 $IK_B$ 都用长期使用用于身份验证的公钥, $SPK_B$
 是Bob签名过的用于建立会话认证使用,一般为发安全这个密钥会以一定的周期更换,一般一周,一天,甚至几个小时。 $OPK_B$
 是Bob为建立会话生成的仅可使用一次的公钥,一般Bob会向服务器上传一组几十上百个这样的公钥,这样的公钥每次成功建立一个会话后一定要废弃,绝对不可以重复使用,否则将面临巨大的安全风险。$EK_A$ 
 是Alice为了建立会话临时生成的密钥,会话成功建立后要马上废弃。

执行流程

X3DH 协议的执行可分为3个阶段:

  1. Bob 将他的 Identity Key 和 Prekey 发布到 Server,供任何想与其建立通讯会话的人使用;
  2. Alice 从 Server 获取 Bob的 Prekeys等进行秘钥协商计算, 并用它向 Bob 发送一条初始消息;
  3. Bob 接收并处理 Alice 的初始消息;

下面详细介绍各阶段的细节:

Bob 发布密钥

Bob 把自己相关的密钥信息发布到 Server,其中包含了:

  1. Bob 的身份认证公钥 $IK_B$  
  2. Bob 的签名预设钥 $SPK_B$ 
  3. 签名后的预设公钥 $Sig(IK_B, Encode(SPK_B))$ 
  4. 一组单次预设密钥 $(OPK_B^1, OPK_B^2, OPK_B^3, …)$

Bob发布相关密钥后会将相关私钥保存备用,当有人尝试与其验证以建立会话时会用到相关私钥,对于单次预设密钥,如果建立会话过程中使用到了那么一定要在建立会话后删除相关密钥。

Alice 发送会话建立起始信息

Alice 首先向 Server 请求 Bob 的相关密钥:

  1. Bob 的身份认证公钥 $IK_B$  
  2. Bob 的签名预设钥 $SPK_B$ 
  3. 签名后的预设公钥 $Sig(IK_B, Encode(SPK_B))$ 
  4. 一个单次预设密钥 $OPK_B$

其中单次预设密钥只会下发一个而非全部。

Alice 首先使用 Bob 的身份认证公钥验证预设公钥,如果验证成功,Alice生成一个临时密钥对,公钥为 $EK_A$ 

然后 Alice 按照下图中的规则来执行4次 ECDH 生成共享密钥$SK$

297a3bba56e210ff62b58a8fbdc28700.png

如果Alice从服务获得的消息不包含单次预设密钥:

$$DH1 = DH(IK_A, SPK_B)$$    
$$DH2 = DH(EK_A, IK_B)$$    
$$DH3 = DH(EK_A, SPK_B)$$    
$$SK = KDF(DH1 || DH2 || DH3)$$

如果消息中包含单次预设密钥,那么就多做一次DH运算:

$$DH4 = DH(EK_A, OPK_B)$$    

最后一步也变为:

$$SK = KDF(DH1 || DH2 || DH3 || DH4)$$

生成共享密钥后 Alice 删除自己的 $EK_A$ 对应的私钥和相关DH计算的输出结果,然后 Alice 会计算一组附带数据 $AD$ 如下:

$$ AD = Encode(IK_A) || Encode(IK_B)$$

之后 Alice 向 Bob 发送发起会话的起始消息,消息包含如下内容:

  • Alice 的身份认证密钥 $IK_A$ 
  • Alice 的临时公钥 $EK_A$
  • Alice 所使用的预设密钥和单次预设密钥的标识符
  • 使用AEAD加密机制加密的初始密文,其中AD为附带消息,其中密钥可以直接使用 $SK$ 也可以再使用密码学伪随机数发生成器以$SK$为私密生成

Bob 接收 Alice 的初始消息

在接收到 Alice 的初始消息后,Bob从消息中检索出:

  • Alice 的身份密钥 $IK_A$
  • Alice 的临时密钥 $EK_A$

Bob 重复上一步中的 DH 和 KDF 计算来推导 $SK$

Bob然后使用$IK_A$和$IK_B$来构造$AD$字节序列。最后 Bob 用 $SK$ 和 $AD$ 来解密初始密文。

  • 如果初始密文解密失败,Bob将终止协议
  • 如果初始密文解密成功,Bob 的协议就完成了。Bob可以继续使用后 $SK$ 或源自$SK$的密钥与Alice通信。

双棘轮(Double Ratchet)算法

双棘轮算法用于通信的双方交换基于共享密钥的加密消息。

首先,通信双方使用密钥协商协议(如:X3DH)来就共享密钥达成一致。然后,通信双方即可使用双棘轮算法发送和接收加密消息了。

通信各方为每个双棘轮消息派生新密钥,这样就不能从新消息的密钥计算出旧消息的密钥。通信双方还将 DH 的公钥值附在他们的消息中。Diffie-Hellman计算的结果混合到派生的秘钥中,这样就使得攻击者无法从旧的密钥计算得到新的密钥。

这些特性将保证在某一方的密钥泄漏后,保护加密消息的前向和后向安全。

下面介绍了双棘轮算法的具体实现。

初始化

在初始化之前,双方必须使用某种密钥协商协议以协商出一个 256比特的共享密钥 $SK$ 及 Bob 的棘轮公钥

这些值将用于生成 Alice 的发送链密钥及 Bob 的根密钥。而 Bob 的(发送及接收)链密钥及 Alice 的接收链密钥暂时留空,将由各自的首次 DH 棘轮步进操作初始化。

KDF 链(KDF Chain)

KDF 链是双棘轮算法中的核心概念。

可以表示为如下流程:把一个 KDF 的输出分为两部分,一部分作为输出密钥(Output Key),而另一部分取代 KDF 密钥(KDF Key)作为另一个 KDF 的输入密钥

如下图所示,是一个处理了三个输入密钥并生成三个输出密钥的 KDF 链

95cc4e704727fe81e9af95a52df17781.png

并且 KDF 链具有如下几个特性:

  • 弹性(Resilience):对于不知道 KDF 密钥的攻击者来说,输出密钥看起来是随机的。即使攻击者能控制 KDF 的输入,此条特性仍然成立。
  • 前向安全性(Forward Security):对于知道某一时刻的 KDF 密钥的攻击者来说,旧的输出密钥看起来是随机的。
  • 被攻破后的可恢复性(Break-in Recovery):对于知道某一时刻的 KDF 密钥的攻击者来说,新的输出密钥看起来是随机的,只要新的输入中增加了足够的熵(Entropy)。

在 Alice 和 Bob 之间的双棘轮会话中,双方保存的 KDF 密钥会用于以下三条链:

  • 根链(Root Chain)
  • 发送链(Sending Chain)
  • 接收链(Receiving Chain)

Alice 的发送链对应 Bob 的接收链,Bob 的发送链则对应 Alice 的接收链

Alice 和 Bob 交换消息的同时,也交换新的迪菲-赫尔曼公钥,而迪菲-赫尔曼输出的密钥将作为根链的输入。根链输出的密钥将作为发送链和接收链的 KDF 密钥。这称为迪菲-赫尔曼棘轮(Diffie-Hellman Ratchet)。

每发送和接收一条消息,发送链和接收链都将向前推进。相应的输出密钥将用于加密和解密消息。这称为对称密钥棘轮(Symmetric-Key Ratchet)。

后继几节将更详细地解释对称密钥棘轮和迪菲-赫尔曼棘轮,之后将描述二者如何组合成为双棘轮。

对称密钥棘轮(Symmetric-Key Ratchet)

每条发送或接收的消息都使用一个唯一的消息密钥Message Key)进行过加密。消息密钥是发送 KDF 链接收 KDF 链的输出密钥。这些链的 KDF 密钥称为链密钥Chain Key)。

由于发送链和接收链的 KDF 输入是常数,所以这两条链不具备被攻破后的可恢复性。发送链和接收链只能确保每条消息使用唯一的密钥加密,而此密钥在加密或解密后可以删除。

由一个给定的链密钥计算下一个链密钥和消息密钥的过程,称为对称密钥棘轮(Symmetric-Key Ratchet)的一次棘轮步进(Ratchet Step)。

下图展示了对称秘钥棘轮的两次步进:

52e9fe45cabbd256bfcc31b2aa9d64c2.png

由于消息密钥不用于派生其它密钥,因此可以保存起来而不影响其它消息密钥的安全性,这将有助于处理消息的丢失或乱序。

迪菲-赫尔曼棘轮(Diffie-Hellman Ratchet)

在基于对称秘钥棘轮的会话中,如果中间攻击者窃取了其中一方的发送链密钥和接收链密钥,就可以计算出此后所有的消息密钥,并解密对应的消息。

为了避免这种情况发生,双棘轮算法将对称密钥棘轮迪菲-赫尔曼棘轮组合在一起,使用后者基于迪菲-赫尔曼的输出来更新链密钥。

为了实现 DH 棘轮,通信双方各自生成一个 DH 密钥对(迪菲-赫尔曼公钥和私钥)作为当前的棘轮密钥对(Ratchet Key Pair)。从任意一方发出的每一条消息都将携带一个消息头,其中包含发送者当前的棘轮公钥。当接收到远端发送过来的新的棘轮公钥时,本端将实施一次 DH 棘轮步进(DH Ratchet Step),生成一个新的棘轮密钥对以取代本端当前的密钥对。

通信双方交替地更新棘轮密钥对,使之形成一个「乒乓」行为模式。仅截获了其中一方的窃听者可能得到当前棘轮私钥的值,但此棘轮私钥将最终被未泄露的棘轮私钥取代。那时,棘轮密钥对之间的迪菲-赫尔曼计算将定义一个对攻击者未知的新的 DH 输出。

以下几张图展示了 DH 棘轮如何派生出一系列共享的 DH 输出(DH Output)。

Alice 使用 Bob 的棘轮公钥(Ratchet Public Key)初始化,而 Bob 尚未得知 Alice 的棘轮公钥。作为初始化的一部分,Alice 使用她自己的棘轮私钥(Ratchet Private Key)和 Bob 的棘轮公钥作 DH 运算:

72497da312ee3d10f79bf4d903f25ffe.png

Alice 的初始消息宣告了其棘轮公钥。一旦 Bob 收到其中一条初始消息,Bob 就执行一次 DH 棘轮步进:他使用 Alice 的棘轮公钥和自己的棘轮私钥作 DH 运算,得到的结果应与 Alice 的初始 DH 输出相等。之后 Bob 替换掉自己的棘轮密钥对并重新计算一个新的 DH 输出:

9b24d5f6ee53e87c690354ab829d0972.png

Bob 发送的消息宣告了其新的公钥。最终,Alice 将收到其中一条消息并执行一次 DH 棘轮步进,替换自己的棘轮密钥对并派生出两个 DH 输出,一个与 Bob 的最新 DH 输出相等,另一个为新的 DH 输出:

9d0b75c97c6b712441b5e9534cdc8d17.png

Alice 发送的消息宣告了其新的公钥。最终,Bob 将收到其中一条消息并执行第二次 DH 棘轮步进,如此反复:

92a3641ab4e839db3b8b7cd6fd8b6b73.png

每一次 DH 棘轮步进生成的 DH 输出,用于派生新的发送链密钥和接收链密钥。

下图重新展示了 Bob 的第一次棘轮步进。Bob 使用其第一个 DH 输出派生出接收链,与 Alice 的发送链对应。Bob 使用第二个 DH 输出派生新的发送链:

2d89dc4424e46ddef5d00f3d8d9c45ba.png

当双方交替执行 DH 棘轮步进的同时,也交替地引入新的发送链:

ef42272c9fd325b8acbdf04f551663c2.png

上述的过程其实是为便于讲解而经过简化的版本。

在实际实现的算法中并不是直接将 DH 输出作为链密钥这么简单,而是加入了 KDF 链来改进算法的弹性和被攻破后的可恢复性,将 DH 输出作为根链KDF 输入,而根链KDF 输出作为发送链密钥接收链密钥。一次完整的 DH 棘轮步进包括根 KDF 链的两次更新,并将其 KDF 输出分别作为新的接收链密钥和发送链密钥:

56401f3ace568f83bba23e44c530cf92.png

 ### 双棘轮
对称密钥棘轮DH 棘轮组合在一起,就形成了双棘轮

  • 当发送或接收消息时,执行一次发送链或接收链的对称密钥棘轮步进,以派生新的消息密钥。
  • 当接收到新的棘轮公钥时,在对称密钥棘轮步进之前,执行一次 DH 棘轮步进,以更新链密钥。

在下图中,Alice 已使用 Bob 的棘轮公钥和一个共享密钥(初始根密钥)进行了初始化。作为初始化的一部分,Alice 生成一个新的棘轮密钥对,并将 DH 输出作为根 KDF 的输入,计算出新的根密钥( $RK$ )和发送链密钥( $CK$ ):

fa6f9e18ae711ee993d6e7220c557ae1.png

当 Alice 发送第一条消息 $A1$ 时,她对发送链密钥执行一次对称密钥棘轮步进,以生成新的消息密钥(消息密钥以其加密或解密的消息编号标注)。新的链密钥将保存起来,但消息密钥和旧的链密钥可以删除:

52625690a9fb3d1e33e13b44e6f75e26.png

假如接下来 Alice 收到 Bob 发送的响应消息 $B1$ ,其中包含 Bob 的新的棘轮公钥(Bob 的公钥以其所在的首条消息编号标注)。Alice 执行一次 DH 棘轮步进,以派生新的接收链密钥和发送链密钥。之后她对接收链执行一次对称密钥棘轮步进,以获取接收到的消息对应的消息密钥:

99f3e592885fc7a0b4df9c296ec5e68f.png

假设接下来 Alice 发送了消息 $A2$ ,接收到包含 Bob 旧的棘轮公钥的消息 $B2$ ,接着又发送了消息 $A3$ 和 $A4$ 。Alice 的发送链将步进三次,而其接收链仅步进一次:

07ede153c89d5d830aa0482fd81d287f.png

假设接下来 Alice 接收到包含 Bob 新的棘轮公钥的消息 $B3$ 和 $B4$ ,并发送了消息 $A5$ 。Alice 的最终状态如下:

335abd0d843aa1d19569efdd62d28eb5.png

X3DH 集成

双棘轮算法将 X3DH 协商好的会话密钥 $SK$ 作为初始根密钥,扮演「后 X3DH」协议的角色。

以下 X3DH 的输出将用于双棘轮算法:

  • X3DH 输出的 $SK$ 作为双棘轮算法初始化所需的 $SK$ 输入。
  • X3DH 输出的 $AD$ 作为双棘轮算法加解密所需的 $AD$ 输入。
  • Bob 从 X3DH 输出的已签名的预共享密钥 $SPKB$ 作为双棘轮算法初始化所需的 Bob 的初始棘轮公钥(及对应的密钥对)。

使用 Alice 的初始发送链加密的所有双棘轮消息都可认为是 X3DH 的「初始密文」。为了处理可能发生的消息丢失或乱序,推荐的模型是 Alice 不停地发送前附相同 X3DH 初始消息的双棘轮消息,直到她接收到 Bob 的首条双棘轮响应消息为止。

乱序消息处理

双棘轮算法处理消息丢失或乱序的方法是:在每个消息头部包含此消息在发送链中的编号($N=0,1,2,…$)以及之前的发送链的长度($PN$,即消息密钥的个数)。这使接收方可以保存被跳过的消息密钥而跳转到对应的消息密钥,而当被跳过的消息到达时可使用保存的消息密钥解密。

当接收到一条消息时,如果触发了 DH 棘轮步进,那么接收到的 $PN$ 减掉当前接收链的长度就是此接收链中被跳过的消息数目。接收到的 $N$ 是新的接收链(即 DH 棘轮步进之后的接收链)中被跳过的消息数目。

如果没有触发 DH 棘轮步进,那么接收到的 $N$ 减掉当前接收链的长度就是此接收链中被跳过的消息数目。

例如,假设上一节的消息序列中,消息 $B2$ 和 $B3$ 被跳过。消息 $B4$ 将触发 Alice 的 DH 棘轮步进(不乱序时本应由 $B3$ 触发)。消息 $B4$ 的 $PN=2,N=1$。当接收到 $B4$ 时,Alice 的接收链长度为 1( $B1$ ),所以 Alice 将 $B2$ 和 $B3$ 的消息密钥保存,以便之后接收到这两个消息时可以对其解密:

a02319c078ba13db5b8712c48b78364a.png

参考资料

  1. The Double Ratchet Algorith, Revision 1, 2016-11-20

  2. The X3DH Key Agreement Protocol, Revision 1, 2016-11-04

  3. Signal安全IM核心技术详解:X3DH,双棘轮算法,以及源码详解,含FS/PCS等概念

  4. 双棘轮算法:端对端加密安全协议,原理以及流程详解

  5. 《双棘轮算法》,烂磁头,https://blog.lancitou.net/double-ratchet-algorithm/

文章目录
  1. 1. 引言
  2. 2. Signal 简介
    1. 2.1. 历史
    2. 2.2. 优势
    3. 2.3. Signal 协议
  3. 3. 基础知识
    1. 3.1. 前向保密(Forward Secrecy)
    2. 3.2. DH(Diffie-Hellman)
    3. 3.3. ECDH
    4. 3.4. HMAC
    5. 3.5. KDF
    6. 3.6. HKDF
  4. 4. Signal 协议剖析
    1. 4.1. X3DH
      1. 4.1.1. 参与方
      2. 4.1.2. 前提条件
      3. 4.1.3. 加密符号
      4. 4.1.4. 各种秘钥
      5. 4.1.5. 执行流程
        1. 4.1.5.1. Bob 发布密钥
        2. 4.1.5.2. Alice 发送会话建立起始信息
        3. 4.1.5.3. Bob 接收 Alice 的初始消息
    2. 4.2. 双棘轮(Double Ratchet)算法
      1. 4.2.1. 初始化
      2. 4.2.2. KDF 链(KDF Chain)
      3. 4.2.3. 对称密钥棘轮(Symmetric-Key Ratchet)
      4. 4.2.4. 迪菲-赫尔曼棘轮(Diffie-Hellman Ratchet)
        1. 4.2.4.1. 与 X3DH 集成
      5. 4.2.5. 乱序消息处理
  5. 5. 参考资料