Skip to main content

ferogram_crypto/
aes.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3//
4// ferogram: async Telegram MTProto client in Rust
5// https://github.com/ankit-chaubey/ferogram
6//
7//
8// If you use or modify this code, keep this notice at the top of your file
9// and include the LICENSE-MIT or LICENSE-APACHE file from this repository:
10// https://github.com/ankit-chaubey/ferogram
11
12//! AES-IGE (Infinite Garble Extension): used by Telegram's MTProto.
13
14#![allow(deprecated)]
15
16use aes::Aes256;
17use aes::cipher::generic_array::GenericArray;
18use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
19
20/// Encrypt `buffer` in-place with AES-256-IGE.
21/// `buffer.len()` must be a multiple of 16.
22pub fn ige_encrypt(buffer: &mut [u8], key: &[u8; 32], iv: &[u8; 32]) {
23    assert_eq!(buffer.len() % 16, 0);
24    let cipher = Aes256::new(GenericArray::from_slice(key));
25
26    let mut iv1: [u8; 16] = iv[..16].try_into().unwrap();
27    let mut iv2: [u8; 16] = iv[16..].try_into().unwrap();
28    let mut next_iv2 = [0u8; 16];
29
30    for block in buffer.chunks_mut(16) {
31        next_iv2.copy_from_slice(block);
32        for i in 0..16 {
33            block[i] ^= iv1[i];
34        }
35        cipher.encrypt_block(GenericArray::from_mut_slice(block));
36        for i in 0..16 {
37            block[i] ^= iv2[i];
38        }
39        iv1.copy_from_slice(block);
40        std::mem::swap(&mut iv2, &mut next_iv2);
41    }
42}
43
44/// Encrypt/decrypt `buffer` in-place with AES-256-CTR (symmetric).
45/// `key` = 32 bytes, `iv` = 16 bytes (full block = counter starting value).
46pub fn ctr_crypt(buffer: &mut [u8], key: &[u8; 32], iv: &[u8; 16]) {
47    use ctr::Ctr128BE;
48    use ctr::cipher::{KeyIvInit, StreamCipher};
49    let mut cipher =
50        Ctr128BE::<Aes256>::new(GenericArray::from_slice(key), GenericArray::from_slice(iv));
51    cipher.apply_keystream(buffer);
52}
53
54/// Return the effective AES-CTR IV for a CDN chunk starting at `byte_offset`.
55/// Telegram CDN increments the counter (big-endian uint128) by `byte_offset / 16`.
56pub fn ctr_iv_at_offset(base_iv: &[u8; 16], byte_offset: u64) -> [u8; 16] {
57    let block_offset = byte_offset / 16;
58    let iv_int = u128::from_be_bytes(*base_iv);
59    iv_int.wrapping_add(block_offset as u128).to_be_bytes()
60}
61
62/// Decrypt `buffer` in-place with AES-256-IGE.
63/// `buffer.len()` must be a multiple of 16.
64pub fn ige_decrypt(buffer: &mut [u8], key: &[u8; 32], iv: &[u8; 32]) {
65    assert_eq!(buffer.len() % 16, 0);
66    let cipher = Aes256::new(GenericArray::from_slice(key));
67
68    let mut iv1: [u8; 16] = iv[..16].try_into().unwrap();
69    let mut iv2: [u8; 16] = iv[16..].try_into().unwrap();
70    let mut next_iv1 = [0u8; 16];
71
72    for block in buffer.chunks_mut(16) {
73        next_iv1.copy_from_slice(block);
74        for i in 0..16 {
75            block[i] ^= iv2[i];
76        }
77        cipher.decrypt_block(GenericArray::from_mut_slice(block));
78        for i in 0..16 {
79            block[i] ^= iv1[i];
80        }
81        std::mem::swap(&mut iv1, &mut next_iv1);
82        iv2.copy_from_slice(block);
83    }
84}