Skip to main content

ferogram_crypto/
aes.rs

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