深度解析Twitter/X加密消息协议的技术缺陷与Juicebox安全风险

本文深入分析Twitter/X新加密消息协议XChat的技术实现,重点剖析其使用的Juicebox密钥存储系统的安全漏洞,包括缺乏前向保密性、密钥集中存储风险,以及分布式OPRF协议可能存在的攻击向量。

Twitter/X新加密消息协议的更多细节

Matthew Garrett有一篇关于Twitter(呃,X)新的端到端加密消息协议(现在称为XChat)的不错文章。Matthew文章的要点是:从密码学角度来看,XChat并不出色。细节都在Matthew的文章中,但这里有一个快速总结:

  • 没有前向保密性。与使用双棘轮连续更新用户密钥的Signal协议不同,XChat密码学只是使用接收者的长期公钥加密每条消息。实际加密机制基于libsodium中的加密方案。
  • 用户私钥存储在X。XChat将用户私钥存储在自己的服务器上。要获取您的私钥,您首先需要使用密码(如PIN)登录X的密钥存储系统。这是为了支持无状态客户端(如Web浏览器),公平地说,这与Meta为Facebook Messenger和Instagram加密所做的并无太大不同。当然,这些服务使用硬件安全模块(HSM)。
  • X的密钥存储基于"Juicebox"。为了实现其秘密存储系统,XChat使用了一个名为Juicebox的协议。Juicebox将您的密钥材料"分片"到三个服务器上,原则上一个服务器的丢失或泄露不会对您造成损害。

Matthew的文章正确指出,X系统的主要漏洞是这种密钥存储方法。如果解密密钥存在于三个非HSM服务器中,且所有这些服务器都在X的控制之下,那么X很可能可以获取任何人的密钥并解密其消息。X可以出于自己的内部目的这样做:例如,因为他们著名的冷静所有者对某个用户感到愤怒。或者他们可能因为逮捕令或传票而被迫这样做。如果我们将XChat视为端到端加密方案,这似乎是一种相当致命的漏洞。

因此,在某种意义上,一切都归结为Juicebox的安全性以及X所做的具体部署选择。自Matthew撰写文章以来,我对这两方面都有了更多了解。在这篇文章中,我想更深入地探讨X系统的Juicebox部分。这有望阐明X在做什么,以及为什么您不应该使用XChat。

Juicebox到底是用来做什么的?

许多端到端加密(E2E)应用程序都遇到了一个特定问题:这些系统要求用户存储自己的秘密密钥。不幸的是,用户在这方面实在是不擅长。

有时我们因为丢失设备而忘记密钥。通常我们拥有多个设备,这意味着我们的密钥最终会出现在错误的地方。更糟糕的情况是,当应用程序需要在普通Web浏览器中工作时:这意味着秘密密钥也必须空运到该上下文中。

这个问题的明显补救措施是将秘密密钥存储在服务提供商本身。这很方便,但完全错过了端到端加密的整个要点,即服务提供商不应访问您的秘密!将解密密钥(以可访问的形式)存储在提供商的服务器上绝对是不可行的。

解决这个难题的一种方法是用户加密其秘密密钥,然后将加密后的值上传到服务提供商。理论上,他们可以随时下载其秘密密钥,并且应该知道他们的秘密是安全的。但当然有一个问题:您将使用什么秘密密钥来加密您的秘密密钥!?回答这个问题很快就会让您陷入无限的乌龟堆。

与其陷入悖论,Juicebox、Signal SVR和iCloud Key Vault等系统提供了一种替代方案。他们的观察是,虽然密码学密钥很难保存,但用户通常记得简单的密码(如PIN),特别是如果被要求定期重新输入。如果我们使用用户的PIN/密码来加密我们上传到服务器的更强密码学密钥会怎样?

虽然这比没有好,但还不够好。大多数人选择的密码和PIN作为密码学密钥非常糟糕。特别是,短PIN(如许多人用于手机密码的6位十进制PIN)容易受到高效的暴力猜测攻击。一个六位PIN最多提供2^20的安全性,密码学家称之为"一个相当小的数字"。即使您使用像scrypt或Argon2这样的"硬"密钥派生函数并设置 insane 难度,您仍然可能会丢失数据。

幸运的是,还有另一种方法。

多年来,密码学家一直在考虑将"弱秘密"转化为强秘密的问题。这个问题有时被称为密码强化,做好它通常需要额外的组件。首先,您需要有一些强的密码学秘密,可以"混合"到用户的密码中,以产生真正强的加密密钥。其次,您需要某种机制来限制用户猜测的次数,这样攻击者就不能简单地运行在线攻击来遍历PIN空间。这不能仅使用密码学来强制执行:您必须添加一个(或多个)服务器来强制执行这些检查。关键的是,服务器将限制用户可以输入的错误密码次数:例如,在十次错误尝试后,用户的账户将被锁定或删除。

在某种意义上,我们又回到了起点:需要有人操作服务器。如果该服务器在服务提供商的控制之下,那么他们可以禁用猜测限制和/或提取服务器的秘密密钥材料,此时您又回到了起点。

然而,许多服务已经为此设计了一些合理的解决方案。它们归结为以下替代方案,可以单独或一起实施:

  • 服务器可以在专门的硬件安全模块内实现,该模块的设置使得提供商无法重新编程或访问其密钥材料(至少在配置后)。这种方法由苹果首创,现在被iCloud Key Vault、Signal SVR、WhatsApp和Facebook Messenger使用。
  • 或者,服务器可以"拆分"成多个部分,每个部分由不同的方运行。这里的想法是,用户必须联系N个不同服务器中的T个才能获得正确的密钥(一个常见的例子是2-of-3)。只要攻击者无法破坏T个不同的服务器,组合系统仍然能够强制执行猜测限制并防止攻击者获取密钥。自然,这个想法根本上依赖于服务器不由同一方运行的假设!

最后,我们来到了Juicebox。

Juicebox是一个基于软件的分布式密钥强化服务,可以在多个服务器上实现。用户可以将其账户"注册"到系统中,此时Juicebox服务器将把他们的PIN/密码转换为强的密码学密钥(通过与存储在Juicebox服务器上的秘密混合)。之后,他们可以联系Juicebox服务器,并且——假设他们输入了正确的密码,并且没有尝试太多错误猜测——他们可以从系统获得相同的密码学密钥。用户可以指定服务器数量(N)和阈值(T),目标是系统能够在N-T个服务器丢失(或不可用)时存活,并且即使攻击者破坏了A<T个服务器,它也应保持其安全性。最关键的是,Juicebox强制执行限制:如果输入了太多错误密码,Juicebox将锁定或销毁用户的账户。

原则上,Juicebox服务器(在项目术语中称为"领域")可以是"基于软件的",也可以部署在HSM内部。然而,据我所知,HSM功能在Juicebox的一个测试部署之外并未得到完全支持,并且尚未在部署中使用(在X或任何其他地方)。更新6/10:但请参阅下文!这意味着XChat版本的Juicebox的安全性可能归结为谁运行服务器的问题。

谁运行X的Juicebox服务器,他们使用HSM吗?

据我所知,所有XChat服务器都是由Twitter/X本身在软件中运行的。如果这被证明是不正确的,我将非常高兴更新这篇文章。

更新:一位工程负责人的推文声称它们实际上是HSM,而Twitter只是没有公开宣传这一点或发布用于设置它们的密钥仪式。我对此感到非常困惑,因为这似乎极其倒退!这个迟来的声明的问题是,除了来自X某人的一两条推文之外,真的没有办法验证这一事实。

更明确地说,如果没有像可验证地使用HSM和/或将Juicebox服务器分布在互不信任的操作员之间这样的保护措施,拥有三个服务器在保护用户秘密免受服务操作员侵害方面作用相对较小。即使X秘密实施了这些保护措施,秘密实施也是愚蠢的。正如一位智者曾经说过的:

验证XChat Juicebox服务器是纯软件的更为复杂。在Juicebox Github中挖掘,您会发现他们"领域"的纯软件实现以及HSM特定实现。具体来说,有一个完整的存储库专门支持Juicebox在Entrust nShield Solo XC HSM上运行(请参阅此处的说明),尽管相同的代码也可以在HSM外部部署。甚至有一个很酷的"仪式"文档,一组管理员可以执行以证明他们正确设置了HSM,并且他们销毁了所有允许重新编程的卡片!

然而,在与Juicebox的协议设计者Nora Trapp交谈后,我怀疑X是否使用了任何这些。Nora告诉我,Juicebox项目在一年多前关闭,工程师们继续前进,现有的代码现在是开源的且未积极维护(这与我能看到的项目提交匹配)。Nora还查看了XChat的Juicebox部署,并向我发送了以下评论:

据我所见,Twitter目前使用了四个领域:realm-a.x.com、realm-b.x.com、realm-east1.x.com和realm-west1.x.com。

realm-a和realm-b绝对是软件领域。它们不使用Noise(仅对软件领域成立)并且仅依赖TLS。相比之下,HSM支持的领域在TLS内部使用Noise,TLS在负载均衡器处终止,Noise在HSM内部。

realm-east1和realm-west1似乎运行来自juicebox-hsm-realm的代码。例如,访问https://realm-east1.x.com/livez显示他们可能未修改地使用了主分支的代码。然而,这并不意味着它们是HSM支持的。该存储库包括一个用于测试的"软件HSM",这是不安全的,不提供实际的HSM属性。

时序分析(例如通过X留下的方便的x-exec-time响应头)表明这些确实使用了软件HSM。真正的HSM通常明显较慢。即使它们碰巧运行真正的HSM,也没有发布任何仪式,因此没有理由相信它们是安全的或者密钥材料没有被外泄。

Nora最近还写了一篇文章,警告部署者不要将所有服务器置于单个服务提供商的控制之下,这似乎非常适用于Twitter/X正在做的事情。

显然,这并不能证明X没有使用HSM。不过,显然,当部署者特意不告诉您某物是安全的时候,您没有理由希望它是安全的。对于XChat,我的建议是您应该假设此部署(1)完全在软件中运行,并且(2)所有Juicebox"领域"都由同一组织运行。这意味着您应该假设您的解密密钥可以被X的服务器管理员在稍加麻烦后恢复,除非您使用非常强的密码。

这很糟糕,但让我们还是多谈谈Juicebox协议吧!

如果您来这里只是为了了解一些关于XChat安全态势的讨论,那么Matthew的文章和上面的附加说明就是您所需要的。除非X证明他们正在使用HSM(并且已经销毁了所有编程卡),否则您应该假设他们的Juicebox实例是基于X控制下的软件领域,并且这意味着它可能容易受到暴力密码猜测攻击。

本文的其余部分将超越X。

让我们假设部署者智能地配置了Juicebox:意味着一些/所有领域将部署在HSM内部,和/或领域分布在多个组织信任边界上——这样没有任何单个组织可以轻易要求恢复任何人的密码。我们现在要问的问题是:在这种设置下,Juicebox提供了什么保证,以及协议是如何工作的?

阈值OPRF

Juicebox内部的核心密码学原语称为阈值不经意伪随机函数,或t-OPRF。我以前写过关于OPRF的文章,特别是在密码认证密钥协商(PAKE方案)的背景下。尽管如此,我认为从头开始是有帮助的。

让我们暂时搁置"不经意"部分。PRF是接收密钥K和字符串P(例如,密码)并输出位串的函数。我们可以写成: O = PRF(K, P) 只要攻击者不知道密钥,结果字符串O应该看起来是随机的,这意味着PRF的输出可以成为优秀的密码学密钥。在许多实际实现中,PRF使用像HMAC或CBC-MAC这样的函数实现;然而,有许多不同的构建方法。

一个不经意PRF(OPRF)是一个两方密码学协议,帮助客户端和服务器联合计算PRF的输出。它工作如下:

  • 想象服务器拥有密码学密钥K。
  • 客户端有他们的字符串P(如密码)。
  • 在成功的协议运行结束时,客户端应获得O = PRF(K, P)。服务器根本没有任何输出。
  • 关键:任何一方都不应学习另一方的输入,服务器也不应学习客户端的结果。

有了这个工具,构建一个非常简单的密码强化协议很容易。只需将服务器配置一个密钥K(最好每个用户账户使用不同的密钥),然后让客户端与服务器运行OPRF协议以获得O = PRF(K, P)。结果值O将成为一个优秀的加密密钥,客户端可以使用它来加密任何秘密值。这种安排的最好部分是OPRF协议确保服务器永远不会学习用户的密码P,因此即使服务器完全恶意,也不会泄露任何信息!

当然,这种基本设计有一些限制。它不允许服务器限制密码猜测次数,也不允许我们将过程分散到许多不同的服务器上。

解决第一个问题很容易。当客户端首次向服务器注册其账户时,他们可以运行OPRF协议以获得O = PRF(K, P)。接下来,他们计算一些"认证标签" T,该标签以某种方式从秘密O派生。然后他们可以将该标签T存储在服务器上。当用户返回登录系统时,客户端和服务器可以运行OPRF协议,然后进行某种过程来验证客户端接收到的O与服务器存储的标签T一致。(执行此操作的确切过程很重要,我将在下面进一步讨论。)如果不成功,则服务器可以增加一个计数器以指示该用户账户上的错误密码猜测。如果协议成功完成,则服务器应将该计数器重置为零。¹

关键的是,当计数器达到某个最大值(通常为十次错误尝试)时,服务器必须锁定用户的账户——或者更好的是,删除账户特定的密钥K。这可以防止攻击者系统地猜测所有可能的PIN。

将PRF拆分成多个服务器仅稍微复杂一些。基本思想依赖于Juicebox使用的特定OPRF基于椭圆曲线,这使得它非常适合于阈值实现。我将把细节放在一个单独的页面上,因为它有点无聊。但您应该只是了解到(1)服务操作员可以将密钥K拆分到多个服务器上,并且(2)客户端可以与其中任意T个服务器交谈并最终获得PRF(K, P)。

客户端如何证明它获得了正确的密钥,以及存在哪些攻击?

您会注意到这些"错误尝试"计数器是像Juicebox这样的系统内部保护的重要组成部分。只要攻击者只能进行,比如说,最多十次错误猜测尝试,那么即使是像234984这样相对较弱的密码可能也不算太糟。如果攻击者可以进行许多可能的猜测,那么整个系统就相当弱。

请注意,在大多数这些攻击中,我们将做出非常强的假设,即服务器操作员是攻击者,并且他们以无法从自己的实例中读取秘密的方式部署了Juicebox(例如,在这种假设情景中,他们使用HSM或分布式信任部署了Juicebox)。由于端到端加密的整个点是用户的秘密密钥不应被服务器操作员知晓,这并不是一个太强的假设。此外,我们最近看到政府向苹果等公司发出秘密请求,迫使它们绕过其端到端加密服务。这意味着黑客攻击和法律强制都是真正的问题。

在这种设置下,存在一些可能在像Juicebox这样的系统中出现的攻击。有些比其他更容易预防:

  • 攻击者可以尝试输入几次密码猜测,然后等待真实用户登录。只要真实用户输入了正确的密码,服务器上的尝试计数器将降回零。这种攻击可能允许您为每个真实用户登录进行最多,比如,九次无效猜测。² 这种攻击的存在在像Juicebox这样的系统中是不可避免的。幸运的是,这可能不是一种非常有效的攻击。除非用户不断登录(例如,因为Web浏览器缓存了用户的密码并自动运行协议),否则攻击者猜测的速率将非常低。此外:这种攻击可以通过让服务器通知客户端自上次用户登录以来看到的错误猜测次数来缓解,这应有助于用户检测到他们正在受到攻击。
  • 如果服务器彼此不协调,聪明的攻击者可能尝试针对不同子集的Juicebox服务器猜测密码:例如,如果需要10个服务器中的2个来恢复密钥,那么攻击者实际上可以获得远多于十次的猜测。这是因为攻击者至少可以对每两个服务器的子集进行十次尝试,直到所有必要的服务器将它们锁定。Juicebox的HSM实现实际上花了很大力气来防止这种情况(正如SVR之前所做的那样),通过让服务器使用共识协议共享当前的密码计数值。

另一种可能性是恶意(软件)服务器操作员可能尝试直接攻击协议。只是为了好玩,我认为以一个在浏览协议描述时注意到的可能攻击作为结束可能很有趣。我几乎确定这在实践中不会起作用——而且Juicebox开发人员也同意,但我认为这是一个有趣的例子,说明了这些系统中出现的漏洞类型。

回想一下,每当Juicebox客户端成功完成与一组T个服务器的协议时,它必须以某种方式说服那些服务器它获得了正确的密钥。这是必要的,因为正确的密码输入应该将这些服务器的尝试计数器重置为零,而不正确的猜测将增加尝试计数器。(没有重置计数器的机制,计数器将不断上升,直到用户的账户被永久锁定。)

客户端通过首先从t-OPRF输出计算一个称为unlockKey的值,然后计算一组称为unlockKeyTags的"标签"来处理这个问题,每个标签对应系统中的一个服务器(领域):

每个unlockKeyTags[i]值的计算都定制为包含服务器的"领域ID",这意味着服务器i的标签仅特定于该服务器。它不能从恶意服务器i提取并重放给服务器j,这将是非常糟糕的。客户端然后将每个值unlockKeyTags[i]发送到相应的服务器。服务器可以根据在账户注册过程中存储的副本验证接收到的标签。如果匹配,它们将计数器重置为零,如下所示:

然而,区分这些标签的唯一概念是不同服务器的领域ID总是不同的。如果这个假设不正确怎么办?

我担心的攻击相当奇怪,但它看起来像这样。想象一个用户之前已经将其密钥注册到几个真实的基于HSM的服务器中。现在有人入侵了服务提供商。这个黑客"欺骗"客户端将后续登录请求发送到攻击者使用软件(即,没有HSM保护)启动的一组新的恶意服务器。

通常,这种攻击不会是一场灾难,因为OPRF应该防止这些新服务器学习用户的密码输入。但让我们想象一下,黑客将这些新服务器的"领域ID"设置为与真实(HSM)服务器的领域ID相同。在这种情况下,发送到恶意服务器的unlockKeyTag[i]值将与存储在基于HSM的服务器内的值相同。一旦攻击者得知这个值,他们就可以对具有相同领域ID的HSM服务器进行无限次猜测:这是因为被盗的unlockKeyTags[i]值将重置HSM服务器的计数器。

我向Nora提出了这个问题,她指出了一些实际问题,这些问题可能会使这种攻击不太可能成功,但发现这种事情仍然很有趣。更重要的是,我认为这显示了像这样的分布式协议可能有多么微妙,以及有时一个人的假设可能并不总是有效的。


注释:

  1. 显然这可能是危险的。一个只想拒绝向用户服务的攻击者可以故意输入错误的猜测,直到用户的账户被永久锁定。为了防止这种情况,大多数服务要求您首先使用传统密码登录,然后您可以第二次访问密码强化服务器。一些系统还增加时间延迟,以确保攻击者不能快速耗尽计数器。
  2. “尝试N次然后让客户端正常登录"的攻击是由Ian Miers向我概述的。
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计