Skip to main content

ferogram_crypto/
aes.rs

1/*
2 * Copyright (c) 2026 Ankit Chaubey <ankitchaubey.dev@gmail.com>
3 * https://github.com/ankit-chaubey
4 *
5 * Project: ferogram
6 * Website: https://ferogram.dev
7 *
8 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
9 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
10 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
11 * This file may not be copied, modified, or distributed except according
12 * to those terms.
13 */
14
15#![allow(deprecated)]
16
17use aes::Aes256;
18use aes::cipher::{Array, BlockCipherDecrypt, BlockCipherEncrypt, 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(&Array::from(*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        let mut b = Array::from(<[u8; 16]>::try_from(&*block).unwrap());
36        cipher.encrypt_block(&mut b);
37        for i in 0..16 {
38            block[i] = b[i];
39        }
40        for i in 0..16 {
41            block[i] ^= iv2[i];
42        }
43        iv1.copy_from_slice(block);
44        std::mem::swap(&mut iv2, &mut next_iv2);
45    }
46}
47
48/// Encrypt/decrypt `buffer` in-place with AES-256-CTR (symmetric).
49/// `key` = 32 bytes, `iv` = 16 bytes (full block = counter starting value).
50pub fn ctr_crypt(buffer: &mut [u8], key: &[u8; 32], iv: &[u8; 16]) {
51    use ctr::Ctr128BE;
52    use ctr::cipher::{Array, KeyIvInit, StreamCipher};
53    let mut cipher = Ctr128BE::<Aes256>::new(&Array::from(*key), &Array::from(*iv));
54    cipher.apply_keystream(buffer);
55}
56
57/// Return the effective AES-CTR IV for a CDN chunk starting at `byte_offset`.
58/// Telegram CDN increments the counter (big-endian uint128) by `byte_offset / 16`.
59pub fn ctr_iv_at_offset(base_iv: &[u8; 16], byte_offset: u64) -> [u8; 16] {
60    let block_offset = byte_offset / 16;
61    let iv_int = u128::from_be_bytes(*base_iv);
62    iv_int.wrapping_add(block_offset as u128).to_be_bytes()
63}
64
65/// Decrypt `buffer` in-place with AES-256-IGE.
66/// `buffer.len()` must be a multiple of 16.
67pub fn ige_decrypt(buffer: &mut [u8], key: &[u8; 32], iv: &[u8; 32]) {
68    assert_eq!(buffer.len() % 16, 0);
69    let cipher = Aes256::new(&Array::from(*key));
70
71    let mut iv1: [u8; 16] = iv[..16].try_into().unwrap();
72    let mut iv2: [u8; 16] = iv[16..].try_into().unwrap();
73    let mut next_iv1 = [0u8; 16];
74
75    for block in buffer.chunks_mut(16) {
76        next_iv1.copy_from_slice(block);
77        for i in 0..16 {
78            block[i] ^= iv2[i];
79        }
80        let mut b = Array::from(<[u8; 16]>::try_from(&*block).unwrap());
81        cipher.decrypt_block(&mut b);
82        for i in 0..16 {
83            block[i] = b[i];
84        }
85        for i in 0..16 {
86            block[i] ^= iv1[i];
87        }
88        std::mem::swap(&mut iv1, &mut next_iv1);
89        iv2.copy_from_slice(block);
90    }
91}