电报客户端开发人员安全指南

客户端开发人员安全指南

电报MTProto协议旨在提供相对快速且安全的运行环境,但如果部署不当,其优势很容易被抵消。我们在此页面汇总了一些客户端软件开发者的安全指南,所有Telegram客户端都必须遵守。

请注意,从 4.6 版本开始,主流 Telegram 客户端均已使用MTProto 2.0。MTProtov.1.0 已被弃用,目前正在逐步淘汰。

迪菲-赫尔曼密钥交换

我们在两种情况下使用DH密钥交换:

无论哪种情况,在使用DH时都需要进行一些验证:

DH参数的验证

客户端需要检查p = dh_prime是否为安全的 2048 位素数(即p和(p-1)/2均为素数,且 2^2047 < p < 2^2048),以及g是否生成一个素数阶为(p-1)/2的循环子群,即 g 是否模 p 为二次剩余。由于g始终等于 2、3、4、5、6 或 7,因此可以使用二次互反律轻松完成此操作,从而得到p mod 4g的简单条件——即:当g = 2时,p mod 8 = 7;当g = 3时,p mod 3 = 2;当 g = 4 时,无需额外条件;当g = 5 时,p mod 5 = 1 或 4;当g = 6 时,p mod 24 = 19 或 23;当g = 7时,p mod 7 = 3、5 或 6。客户端检查完g和p之后,缓存结果就很有意义了,这样以后就不用重复冗长的计算了。

如果验证耗时过长(对于较旧的移动设备来说就是这种情况),最初可能只运行 15 次 Miller-Rabin 迭代(在 Java 中使用参数 30)来验证p和(p - 1)/2的素性,错误概率不超过十亿分之一,之后在后台进行更多迭代。

另一种优化方法是在客户端应用程序代码中嵌入一个包含一些已知“良好”素数对(g, p)(或者仅包含已知安全素数p ,因为g的条件在执行期间很容易验证)的小表,该表在代码生成阶段进行检查,从而完全避免在运行时进行此类验证。服务器很少更改这些值,因此通常需要将服务器的dh_prime的当前值放入该表中。例如,dh_prime的当前值(以大端字节序表示)等于:

C7 1C AE B9 C6 B1 C9 04 8E 6C 52 2F 70 F1 3F 73 98 0D 40 23 8E 3E 21 C1 49 34 D0 37 56 3D 93 0F 48 19 8A 0A A7 C1 40 58 22 94 93 D2 25 30 F4 DB FA 33 6F 6E 0A C9 25 13 95 43 AE D4 4C CE 7C 37 20 FD 51 F6 94 58 70 5A C6 8C D4 FE 6B 6B 13 AB DC 97 46 51 29 69 32 84 54 F1 8F AF 8C 59 5F 64 24 77 FE 96 BB 2A 94 1D 5B CD 1D 4A C8 CC 49 88 07 08 FA 9B 37 8E 3C 4F 3A 90 60 BE E6 7C F9 A4 A4 A6 95 81 10 51 90 7E 16 27 53 B5 6B 0F 6B 41 0D BA 74 D8 A8 4B 2A 14 B3 14 4E 0E F1 28 47 54 FD 17 ED 95 0D 59 65 B4 B9 DD 46 58 2D B1 17 8D 16 9C 6B C4 65 B0 D6 FF 9C A3 92 8F EF 5B 9A E4 E4 18 FC 15 E8 3E BE A0 F8 7F A9 FF 5E ED 70 05 0D ED 28 49 F4 7B F9 59 D9 56 85 0C E9 29 85 1F 0D 81 15 F6 35 B1 05 EE 2E 4E 15 D0 4B 24 54 BF 6F 4F AD F0 34 B1 04 03 11 9C D8 E3 B9 2F CC 5B

g_a 和 g_b 验证

除了对 Diffie-Hellman 素数dh_prime和生成元g的条件外,等式两边还需要检查g、g_a和g_b是否大于1且小于dh_prime - 1。我们建议同时检查g_a和g_b是否在2^{2048-64}和dh_prime - 2^{2048-64}之间。

在密钥生成过程中检查 SHA1 哈希值

一旦客户端server_DH_params_ok在授权密钥生成协议的步骤 5) 中收到响应并对其进行解密answer_with_hash,它必须检查

answer_with_hash := SHA1(answer) + answer + (0-15 random bytes)

换句话说,前 20 个字节answer_with_hash必须等于解密消息的其余部分(不包括填充的随机字节)的 SHA1 值。

检查 nonce、server_nonce 和 new_nonce 字段

当客户端在创建授权密钥期间接收和/或解密服务器消息时,如果这些消息包含客户端已从协议同一运行期间先前获得的消息中知道的一些 nonce 字段,则客户端应检查这些字段是否确实包含先前已知的值。

使用安全的伪随机数生成器来创建DH秘密参数a和b

客户端必须使用加密安全的伪随机数生成器 (PRNG) 来生成秘密指数a或进行 DH 密钥交换。对于秘密聊天,客户端可以在调用`messages.getDhConfig`b时向服务器请求一些熵(随机字节),并将这些随机字节输入到其 PRNG 中(例如,如果使用了 OpenSSL 库),但绝不能单独使用这些“随机”字节,也不能用它们替换本地 PRNG 种子。应该将从服务器接收的字节与本地 PRNG 种子混合使用。PRNG_seed

MTProto加密消息

发送和接收加密的 MTProto 消息时,需要进行一些重要的检查。

检查 msg_key 的 SHA256 哈希值

msg_key不仅用于计算 AES 密钥和 IV 以解密接收到的消息。解密后,客户端必须检查它msg_key是否确实等于解密结果获得的明文的 SHA256 值(包括最后的 12...1024 个填充字节),并加上从 中提取的 32 个字节作为前缀auth_key,如MTProto 2.0 描述中所述。

如果在执行此检查之前遇到错误,客户端必须先执行检查,然后再返回任何结果。请注意,对检查msg_key之前遇到的任何错误的响应必须与对检查失败的响应相同。msg_keymsg_key

检查消息长度

客户端必须检查从解密消息(根据其字段计算)获得的消息或容器的长度length是否超过明文的总大小,并且差值(即随机填充的长度)是否在 12 到 1024 字节的范围内。

长度必须始终能被 4 整除且为非负数。客户端绝不能访问包含明文消息的解密缓冲区末尾之后的数据。

检查 session_id

客户端需要检查session_id解密消息中的字段是否确实与客户端创建的活动会话中的字段相等。

检查 msg_id

客户端必须检查msg_id客户端到服务器的消息是否具有偶校验,以及服务器到客户端的消息是否具有奇校验。

此外,必须存储从对端收到的最后 N 条消息的标识符 (msg_id)。如果收到一条消息,其 msg_id 小于或等于任何已存储的值,则忽略该消息。否则,将新消息的 msg_id 添加到集合中。如果已存储的 msg_id 值的数量大于 N,则丢弃最早的(即最小的)msg_id。

此外,超过 30 秒或 300 秒的 msg_id 值将被忽略(回想一下,这msg_id大约等于 Unix 时间 * 2^32)。这一点对服务器尤为重要。客户端也会发现这很有用(可以防止重放攻击),但前提是客户端的时间是确定的(例如,如果客户端时间已与服务器时间同步)。

某些包含客户端发送给服务器的数据(例如,最近的客户端查询)的客户端到服务器服务消息,msg_id即使时间看起来“不正确”,也可能在客户端上被处理。更改服务器盐值的消息和关于客户端时间无效的通知尤其如此。参见移动协议:服务消息。

不匹配时的行为

如果上述任何一项检查失败,客户端应完全丢弃从服务器收到的消息。我们还建议关闭并重新建立与服务器的 TCP 连接,然后重试该操作或整个密钥生成协议。

错误消息中的任何信息都不能使用。即使应用程序抛出异常并崩溃,也比继续使用无效数据要好得多。

请注意,即使没有恶意篡改,正常工作期间也偶尔会出现无效消息。这是由于网络传输错误造成的。我们建议您忽略无效消息并关闭 TCP 连接,然后重新与服务器建立 TCP 连接并重试原始查询。

之前适用于 MTProto 1.0 客户端的安全建议版本可在此处获取。