电报端到端加密语音和视频通话

本文介绍电报 Telegram语音和视频通话中如何使用的端到端加密。

建立通话

在通话准备就绪之前,需要执行一些准备工作。呼叫方需要联系被叫方,并确认其是否已准备好接听电话。此外,双方还需要协商所使用的协议,获取彼此或所用 Telegram 中继服务器(即所谓的反射器)的 IP 地址,并借助Diffie-Hellman 密钥交换生成用于此次语音通话的一次性加密密钥。所有这些操作都通过多个 Telegram API 方法和相关通知并行完成。本文档详细介绍了密钥生成、加密和安全相关内容。

密钥生成

Diffie-Hellman密钥交换以及用于创建新语音通话的整个协议,与秘密聊天所使用的协议非常相似。我们建议您在继续操作之前先阅读链接文章。

但是,我们做出了一些重要更改,以简化密钥验证过程。以下是通信双方(呼叫方 (A) 和被呼叫方 (B))通过 Telegram 服务器 (S) 进行的完整通信记录。

至此,Diffie-Hellman 密钥交换完成,双方拥有一个 256 字节的共享密钥,用于加密A和B之间所有后续的交换。

对于密钥生成协议的每个实例,每次更新只能接受一次,这一点至关重要,应丢弃任何重复的或已接收和处理的消息(更新)的替代版本。

加密

本文档介绍了Telegram 应用7.0及以上版本中语音和视频通话的加密方式。有关2020 年 8 月 14 日之前发布的应用版本中语音通话所使用的加密方式的详细信息,请参阅此文档。

Telegram语音和视频通话库使用MTProto 2.0的优化版本来发送和接收数据包,这些数据包由一个或多个端到端加密的各种类型的消息组成(ICE 候选列表、视频格式、远程视频状态、音频流数据、视频流数据、消息确认或空消息)。

本文档仅描述加密过程,不涉及编码和网络相关部分。

该库开始使用:

两种数据传输通道都不可靠(消息可能会丢失),但信令传输速度较慢,可靠性更高。

通话数据加密

数据包的主体(decrypted_body)由若干条消息及其各自的seq编号连接而成。

每个消息decrypted_body都是唯一的,因为seq第一条消息中的任意两个数字都不能相同。如果只需要重新发送旧消息,则首先在数据包中添加一条包含新唯一编号的空消息。seq

加密密钥 key用于计算一个 128 位密钥msg_key,然后计算一个 256 位密钥aes_key,最后再计算一个 128 位密钥aes_iv:

x取决于呼叫是拨出还是拨入,以及连接类型:

这样一来,应用程序就可以决定将哪些数据包类型发送到哪些连接,并在这些连接中独立工作(每个连接都有自己的seq计数器)。

所得结果aes_key用于aes_iv加密decrypted_body:

发送的数据包包含msg_key以下内容encrypted_body:

接收到数据包后,首先使用 `undefined`key和 `msg_keyundefined` 对其进行解密,然后msg_key将其与相关的SHA256子字符串进行比对。如果比对失败,则必须丢弃该数据包。

防范重放攻击

每个对等节点都维护一个32位单调递增的传出消息计数器,seq初始值为10。该seq计数器会添加到每条已发送消息的前面,并且1每收到一条新消息,计数器值都会递增seq0。数据包中第一条消息的计数器值不能相同。如果只需要重新发送旧消息,则首先在数据包中添加一条包含新唯一标识符的空消息。当计数器达到0时,调用必须中止。每个对等节点都会存储所有已接收(并已处理)且大于0的消息的值,其中0是目前为止接收到的最大值。seqseq2^30seqmax_received_seq - 64max_received_seqseq

如果收到一个数据包,其中第一个消息的值seq小于或等于某个阈值max_received_seq - 64,或者该阈值seq已被接收,则丢弃该消息。否则,seq所有传入消息的值都会被记忆并max_received_seq进行调整。这确保了不会对任何两个数据包进行重复处理。

密钥验证

为了验证密钥并确保没有发生中间人攻击,双方将私钥key与调用方 (A) 的值g_a连接起来,计算 SHA256 哈希值,并用它来生成一个表情符号序列。更准确地说,SHA256 哈希值被分成四个 64 位整数;每个整数除以使用的表情符号总数(目前为 333),余数用于选择特定的表情符号。该协议的具体细节保证了从 333 个表情符号中比较四个表情符号足以以0.9999999999的概率防止窃听(针对 DH 的中间人攻击) 。

这是因为,标准的 Diffie-Hellman 密钥交换只需要双方之间发送两条消息:

我们采用了一种三条消息的修改版本,当双方都在线时效果很好(这也恰好是语音通话的必要条件):

这里的想法是,A承诺一个特定的a值(以及g_a的值),但不向B透露。B必须在不知道g_a真实值的情况下选择b和g_b的值,这样它就无法尝试不同的b值来强制最终密钥(g_a)^b具有任何特定属性(例如 SHA256(key) 的固定低 32 位)。此时,B承诺一个特定的g_b值,但不知道g_a的值。然后A必须发送其g_a值;即使它现在知道了g_b 的值,也不能更改它,因为对方B只会接受在交换的第一条消息中指定的哈希值的g_a值。

如果某个冒名顶替者伪装成A或B,并试图对Diffie-Hellman密钥交换进行中间人攻击,上述结论仍然成立。A方将与B方(或任何伪装成B方的人)生成共享密钥,但无法根据对方收到的g_b值更改密钥的指数a;冒名顶替者也无法根据g_a调整b值,因为它必须在得知g_a之前确定g_b的值。冒名顶替者与B方之间的密钥生成过程也存在同样的问题。

DH 交易所中使用哈希承诺限制了攻击者只能进行一次猜测来生成攻击中的正确可视化效果,这意味着在可视化中使用超过 33 比特的熵(由四个表情符号表示)就足以使攻击成功的概率极低。