LCOV - code coverage report
Current view: top level - src/core - mtproto_crypto.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 93.4 % 61 57
Test Date: 2026-04-20 19:54:24 Functions: 100.0 % 5 5

            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              : }
        

Generated by: LCOV version 2.0-1