Telegram 安全方案解析 - 文件及音视频加密
客户端到服务端的文件发送
客户端到服务端的文件发送流程遵循通用的 客户端到服务端的加密,文件数据按照 Telegram 规定的 二进制数据序列化 规则即可。
客户端到客户端的文件发送
所有发送到 Secret Chat 的文件均使用一次性密钥加密,该密钥与 secret_key
没有任何关系。
- 发送端获取文件明文数据
file_data
; - 发送端生成两个 256 bits 的数字,分别作为
key
和iv
; - 发送端计算
payload = encrypted_data = AES256_ige_encrypt(file_data, key, iv)
; - 发送端计算
digest = md5(key + iv)
; - 发送端计算
fingerprint = substr(digest, 0, 4) XOR substr(digest, 4, 4)
; - 发送端将
payload
,fingerprint
作为一个文件整体上传至服务端,并获取文件对应的地址path
; - 发送端将
path
,key
,iv
作为消息明文发送至接收端; - 接收端收到消息后,通过
path
下载文件; - 接收端重新计算
fingerprint
,校验与下载的值是否相等; - 接收端使用
aes
,iv
解密文件得到原始文件。
客户端到客户端的音视频通话
在呼叫准备好之前,必须进行一些初始化的操作。
- 主叫方需要联系被叫方,并检查它是否准备好接受呼叫。
- 双方还要协商好要使用的协议
- 双方需要了解对方或要使用的 Telegram 中继服务器的IP地址
- 双方借助 Diffie–Hellman 密钥交换为这次呼叫生成一次性加密密钥
密钥交换
相比于客户端到服务端的 auth_key
交换,及客户端到客户端的 secret_key
交换,在音视频通话中,Telegram 优化了密钥交换流程,以实现更强的安全性。
- A->B : 生成
a
, 计算g_a_hash := hash(g^a)
, 发送g_a_hash
; - B->A : 存储
g_a_hash
, 生成b
, 计算g_b := g^b
, 发送g_b
; - A->B : 计算
key := (g_b)^a
, 计算g_a = g^a
, 计算key_fingerprint = substr(SHA1(key), 0, 8)
, 发送g_a
,key_fingerprint
; - B : 校验
hash(g_a) == g_a_hash
, 校验substr(SHA1(key), 0, 8) == key_fingerprint
, 计算key := (g_a)^b
;
A 确定一个特定的 a
值(和 g_a
),但直到最后一步才向 B 透露 g_a
。B 必须在不知道 g_a
的真实值的情况下选择他的 b
和 g_b
的值。如果进行中间人攻击,则中间人不能根据 B 的 g_b
值来改变 a
,也不能根据 g_a
来调整 b
的值。因此,中间人只有一次注入参数的机会,并且此时中间人没有任何中间交换的特征值。
借助上述优化,仅用超过 33 bits 的熵,就可以防止超过 0.9999999999 的中间人攻击。这些 bits 以四个表情符号的形式呈现给用户。Telegram 选择了一个由 333 个表情符号组成的表情池,这些表情符号看起来都有很大的不同,并且可以很容易地用任何语言的简单词汇来描述。
加密方案
音视频加密方案是一个基于 MTProto 2.0 的优化版本,数据包由一个或多个各种类型的端到端加密消息组成。
加密开始工作时,以下内容已就绪
- 双方共享的加密密钥
key
,如上文中生成的 - 呼叫是呼出还是呼入的信息
- 两个数据传输通道:由Telegram API 提供的信令通道 signaling,和基于 WebRTC 的传输通道 transport
这两种数据传输通道都是不可靠的(消息可能会丢失),但信令传输速度较慢,可靠性较高。
通话数据加密
一个数据包的主体由多个消息和它们各自的 seq
序号连在一起组成。
1 | decrypted_body = message_seq1 + message_body1 + message_seq2 + message_body2 |
每个 decrypted_body
都是唯一的,因为第一个消息的 seq
序号不能相同。如果只有旧的消息需要重新发送,则先在数据包中添加一个具有新的唯一 seq
的空消息。
加密密钥 key
用于计算 128 bits 的 msg_key
,然后计算 256 bits 的 aes_key
和 128 bits 的 aes_iv
。
msg_key_large = SHA256(substr(key, 88+x, 32) + decrypted_body)
;msg_key = substr(msg_key_large, 8, 16)
;sha256_a = SHA256(msg_key + substr(key, x, 36))
;sha256_b = SHA256(substr(key, 40+x, 36) + msg_key)
;aes_key = substr(sha256_a, 0, 8) + substr(sha256_b, 8, 16) + substr(sha256_a, 24, 8)
;aes_iv = substr(sha256_b, 0, 4) + substr(sha256_a, 8, 8) + substr(sha256_b, 24, 4)
;
x
取决于呼叫是呼出还是呼入以及连接类型。
传输通道 | 信令通道 | |
---|---|---|
呼出 | 0 | 128 |
呼入 | 8 | 136 |
这允许应用程序决定哪些数据包类型将被发送到哪些连接,并在这些连接中独立工作(每个连接有各自的 seq
计数器)。
aes_key
和 aes_iv
将被用于加密 decrypted_body
。
1 | encrypted_body = AES_CTR(decrypted_body, aes_key, aes_iv) |
被发送的数据包由 msg_key
和 encrypted_body
组成。
1 | packet_bytes = msg_key + encrypted_body |
当接收到数据包时,使用 key
和 msg_key
对数据包进行解密,然后重新计算 msg_key
,校验与收到的是否相等。如果不相等,则必须丢弃该数据包。
防止重放攻击
每个端都有自己的 32 bits 出站报文计数器 seq
,从 1 开始单调递增。这个 seq
计数器被预置到每个发送的报文中,并为每个新报文增加 1。一个报文包中第一个报文的 seq
号不能相同。如果只有旧的消息需要重新发送,则先在数据包中添加一个具有新的唯一 seq
的空消息。当seq计数器达到 2^30
时,必须中止呼叫。每个端都会存储它收到(和处理)的所有大于 max_received_seq - 64
的消息的 seq
值,其中 max_received_seq
是目前收到的最大 seq
数。
如果收到一个报文,其中第一个报文的 seq <= max_received_seq - 64
,或者它的 seq
值已经被收到,那么这个报文将被丢弃。否则,所有接收到的报文的 seq
值都会被记住,并调整 max_received_seq
。这样可以保证没有两个数据包会被处理两次。
密钥校验
为了验证加密秘钥,并确保没有发生中间人攻击,双方做如下计算
1 | hash = SHA256(key + g_a) |
将 hash
按位等分为 4 个 64 bits 的整数,每个整数怼表情符号总数(目前为 333)取模,用于选择特定的表情符号,从而得到四个表情符号。双方对比 4 个表情符号是否相同,从而确保没有中间人攻击。
协议的具体内容保证了在 333 个表情符号中比较 4 个表情符号足以防止 0.9999999999 的中间人攻击。
群音视频通话
根据官方说明,群音视频通话仍在研发中,预计在 2020 年末推出,但至今(2021年2月15日)仍未推出。
群语音聊天
与音视频通话不同,群语音聊天只是在群内创建一个独立的空间,允许群成员一起发送语音聊天,但每一条语音仍是作为普通的语音消息,使用客户端到服务端的加密。
参考
- Binary Data Serialization
- Telegram 安全方案解析 - 客户端到服务端的加密
- End-to-End Encryption, Secret Chats
- Video Calls and Seven Years of Telegram
- Voice Calls: Secure, Crystal-Clear, AI-Powered
- Voice Chats Done Right
- 400 Million Users, 20,000 Stickers, Quizzes 2.0 and €400K for Creators of Educational Tests
- Q: How are Voice Calls authenticated?
- End-to-End Encrypted Voice and Video Calls