Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file mtproto_crypto.c
6 : * @brief MTProto 2.0 encryption layer implementation.
7 : *
8 : * Implements key derivation, message encryption/decryption per
9 : * https://core.telegram.org/mtproto/description#defining-aes-key-and-iv
10 : */
11 :
12 : #include "mtproto_crypto.h"
13 : #include "crypto.h"
14 : #include "ige_aes.h"
15 :
16 : #include <stdio.h>
17 : #include <stdlib.h>
18 : #include <string.h>
19 :
20 : #define AUTH_KEY_SIZE 256
21 : #define MSG_KEY_SIZE 16
22 : #define AES_KEY_SIZE 32
23 :
24 : /**
25 : * @brief Derive AES key and IV from auth_key and msg_key.
26 : *
27 : * MTProto 2.0 spec:
28 : * sha256_a = SHA256(msg_key + auth_key[x:x+36])
29 : * sha256_b = SHA256(auth_key[x+40:x+76] + msg_key)
30 : * aes_key = sha256_a[0:8] + sha256_b[8:16] + sha256_a[24:32]
31 : * aes_iv = sha256_b[0:8] + sha256_a[8:16] + sha256_b[24:32]
32 : *
33 : * where x = 0 for client→server, x = 8 for server→client.
34 : *
35 : * @param auth_key 256-byte authorization key.
36 : * @param msg_key 16-byte message key.
37 : * @param x Direction offset: 0 (client→server) or 8 (server→client).
38 : * @param aes_key Output: 32-byte AES key.
39 : * @param aes_iv Output: 32-byte AES IV.
40 : */
41 1284 : void mtproto_derive_keys(const uint8_t *auth_key, const uint8_t *msg_key,
42 : int x,
43 : uint8_t *aes_key, uint8_t *aes_iv) {
44 1284 : if (!auth_key || !msg_key || !aes_key || !aes_iv) return;
45 :
46 : uint8_t sha256_a[32], sha256_b[32];
47 :
48 : /* sha256_a = SHA256(msg_key + auth_key[x:x+36]) */
49 : {
50 : uint8_t buf[16 + 36];
51 1284 : memcpy(buf, msg_key, 16);
52 1284 : memcpy(buf + 16, auth_key + x, 36);
53 1284 : crypto_sha256(buf, sizeof(buf), sha256_a);
54 : }
55 :
56 : /* sha256_b = SHA256(auth_key[x+40:x+76] + msg_key) */
57 : {
58 : uint8_t buf[36 + 16];
59 1284 : memcpy(buf, auth_key + x + 40, 36);
60 1284 : memcpy(buf + 36, msg_key, 16);
61 1284 : crypto_sha256(buf, sizeof(buf), sha256_b);
62 : }
63 :
64 : /* aes_key = sha256_a[0:8] + sha256_b[8:24] + sha256_a[24:32] = 32 bytes
65 : * Telegram substr(hash, start, length): substr(sha256_b, 8, 16) = 16 bytes */
66 1284 : memcpy(aes_key, sha256_a, 8); /* sha256_a[0..7] */
67 1284 : memcpy(aes_key + 8, sha256_b + 8, 16); /* sha256_b[8..23] */
68 1284 : memcpy(aes_key + 24, sha256_a + 24, 8); /* sha256_a[24..31] */
69 :
70 : /* aes_iv = sha256_b[0:8] + sha256_a[8:24] + sha256_b[24:32] = 32 bytes */
71 1284 : memcpy(aes_iv, sha256_b, 8); /* sha256_b[0..7] */
72 1284 : memcpy(aes_iv + 8, sha256_a + 8, 16); /* sha256_a[8..23] */
73 1284 : memcpy(aes_iv + 24, sha256_b + 24, 8); /* sha256_b[24..31] */
74 : }
75 :
76 : /**
77 : * @brief Compute msg_key from auth_key and plaintext.
78 : *
79 : * MTProto 2.0 spec:
80 : * msg_key_large = SHA256(auth_key[88+x:88+x+32] + plaintext)
81 : * msg_key = msg_key_large[8:24]
82 : *
83 : * @param auth_key 256-byte authorization key.
84 : * @param plain Plaintext payload.
85 : * @param plain_len Plaintext length.
86 : * @param x Direction offset: 0 or 8.
87 : * @param msg_key Output: 16-byte message key.
88 : */
89 1284 : void mtproto_compute_msg_key(const uint8_t *auth_key,
90 : const uint8_t *plain, size_t plain_len,
91 : int x,
92 : uint8_t *msg_key) {
93 1284 : if (!auth_key || !plain || !msg_key) return;
94 :
95 : /* auth_key slice: 32 bytes starting at offset 88+x */
96 1284 : size_t offset = (size_t)(88 + x);
97 1284 : size_t buf_len = 32 + plain_len;
98 1284 : uint8_t *buf = (uint8_t *)malloc(buf_len);
99 1284 : if (!buf) {
100 0 : fprintf(stderr, "OOM: mtproto_compute_msg_key\n");
101 0 : abort();
102 : }
103 :
104 1284 : memcpy(buf, auth_key + offset, 32);
105 1284 : memcpy(buf + 32, plain, plain_len);
106 :
107 : uint8_t hash[32];
108 1284 : crypto_sha256(buf, buf_len, hash);
109 :
110 : /* msg_key = middle 16 bytes of hash = hash[8:24] */
111 1284 : memcpy(msg_key, hash + 8, MSG_KEY_SIZE);
112 :
113 1284 : free(buf);
114 : }
115 :
116 : /**
117 : * @brief Generate random padding for MTProto 2.0 message.
118 : *
119 : * Padding is 12-1024 bytes, aligned so that (plain_len + padding) % 16 == 0.
120 : *
121 : * @param plain_len Plaintext length (before padding).
122 : * @param padding_out Output buffer for padding bytes (must be >= 1024 bytes).
123 : * @return Number of padding bytes generated.
124 : */
125 657 : size_t mtproto_gen_padding(size_t plain_len, uint8_t *padding_out) {
126 657 : size_t min_pad = 12;
127 657 : size_t total = plain_len + min_pad;
128 : /* Align to 16-byte boundary */
129 657 : total = (total + 15) & ~(size_t)15;
130 :
131 657 : size_t pad_len = total - plain_len;
132 657 : if (pad_len > 0 && padding_out) {
133 657 : crypto_rand_bytes(padding_out, pad_len);
134 : }
135 657 : return pad_len;
136 : }
137 :
138 : /**
139 : * @brief Encrypt plaintext with MTProto 2.0.
140 : *
141 : * Computes msg_key, generates padding, derives AES key/IV,
142 : * then encrypts (plaintext + padding) with AES-256-IGE.
143 : *
144 : * @param plain Plaintext payload.
145 : * @param plain_len Plaintext length.
146 : * @param auth_key 256-byte authorization key.
147 : * @param x Direction offset: 0 or 8.
148 : * @param out Output: encrypted data.
149 : * @param out_len Output: encrypted length.
150 : */
151 640 : void mtproto_encrypt(const uint8_t *plain, size_t plain_len,
152 : const uint8_t *auth_key, int x,
153 : uint8_t *out, size_t *out_len,
154 : uint8_t msg_key_out[16]) {
155 640 : if (!plain || !auth_key || !out || !out_len || !msg_key_out) return;
156 :
157 : /* Generate padding */
158 : uint8_t padding[1024];
159 640 : size_t pad_len = mtproto_gen_padding(plain_len, padding);
160 :
161 : /* Build padded plaintext: plain + padding */
162 640 : size_t padded_len = plain_len + pad_len;
163 640 : uint8_t *padded = (uint8_t *)malloc(padded_len);
164 640 : if (!padded) {
165 0 : fprintf(stderr, "OOM: mtproto_encrypt\n");
166 0 : abort();
167 : }
168 640 : memcpy(padded, plain, plain_len);
169 640 : if (pad_len > 0) memcpy(padded + plain_len, padding, pad_len);
170 :
171 : /* Compute msg_key from padded plaintext (spec: includes padding) and
172 : * return it to the caller so the wire frame carries the exact value
173 : * that was used to derive the AES keys. */
174 640 : mtproto_compute_msg_key(auth_key, padded, padded_len, x, msg_key_out);
175 :
176 : /* Derive AES key + IV */
177 : uint8_t aes_key[AES_KEY_SIZE], aes_iv[AES_KEY_SIZE];
178 640 : mtproto_derive_keys(auth_key, msg_key_out, x, aes_key, aes_iv);
179 :
180 : /* AES-IGE encrypt */
181 640 : aes_ige_encrypt(padded, padded_len, aes_key, aes_iv, out);
182 :
183 640 : *out_len = padded_len;
184 :
185 640 : memset(padded, 0, padded_len);
186 640 : free(padded);
187 : }
188 :
189 : /**
190 : * @brief Decrypt ciphertext with MTProto 2.0.
191 : *
192 : * Derives AES key/IV, decrypts with AES-256-IGE, verifies msg_key.
193 : *
194 : * @param cipher Ciphertext.
195 : * @param cipher_len Ciphertext length (must be multiple of 16).
196 : * @param auth_key 256-byte authorization key.
197 : * @param msg_key 16-byte message key (from the wire).
198 : * @param x Direction offset: 0 or 8.
199 : * @param plain Output: decrypted plaintext.
200 : * @param plain_len Output: decrypted length.
201 : * @return 0 on success (msg_key verified), -1 on error.
202 : */
203 637 : int mtproto_decrypt(const uint8_t *cipher, size_t cipher_len,
204 : const uint8_t *auth_key, const uint8_t *msg_key,
205 : int x,
206 : uint8_t *plain, size_t *plain_len) {
207 637 : if (!cipher || !auth_key || !msg_key || !plain || !plain_len) return -1;
208 637 : if (cipher_len < 16 || cipher_len % 16 != 0) return -1;
209 :
210 : /* Derive AES key + IV */
211 : uint8_t aes_key[AES_KEY_SIZE], aes_iv[AES_KEY_SIZE];
212 637 : mtproto_derive_keys(auth_key, msg_key, x, aes_key, aes_iv);
213 :
214 : /* AES-IGE decrypt */
215 637 : aes_ige_decrypt(cipher, cipher_len, aes_key, aes_iv, plain);
216 :
217 : /* Verify msg_key: recompute from decrypted plaintext */
218 : uint8_t verify_key[MSG_KEY_SIZE];
219 637 : mtproto_compute_msg_key(auth_key, plain, cipher_len, x, verify_key);
220 :
221 637 : if (memcmp(msg_key, verify_key, MSG_KEY_SIZE) != 0) {
222 1 : return -1; /* msg_key mismatch — wrong auth_key or corrupted data */
223 : }
224 :
225 636 : *plain_len = cipher_len;
226 636 : return 0;
227 : }
|