ferogram-crypto 0.6.5

Cryptographic primitives for Telegram MTProto: AES-IGE, RSA, SHA-1/256, auth key derivation
Documentation
/*
 * Copyright (c) 2026 Ankit Chaubey <ankitchaubey.dev@gmail.com>
 * https://github.com/ankit-chaubey
 *
 * Project: ferogram
 * Website: https://ferogram.dev
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
 * This file may not be copied, modified, or distributed except according
 * to those terms.
 */

#![allow(deprecated)]

use aes::Aes256;
use aes::cipher::{Array, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};

/// Encrypt `buffer` in-place with AES-256-IGE.
/// `buffer.len()` must be a multiple of 16.
pub fn ige_encrypt(buffer: &mut [u8], key: &[u8; 32], iv: &[u8; 32]) {
    assert_eq!(buffer.len() % 16, 0);
    let cipher = Aes256::new(&Array::from(*key));

    let mut iv1: [u8; 16] = iv[..16].try_into().unwrap();
    let mut iv2: [u8; 16] = iv[16..].try_into().unwrap();
    let mut next_iv2 = [0u8; 16];

    for block in buffer.chunks_mut(16) {
        next_iv2.copy_from_slice(block);
        for i in 0..16 {
            block[i] ^= iv1[i];
        }
        let mut b = Array::from(<[u8; 16]>::try_from(&*block).unwrap());
        cipher.encrypt_block(&mut b);
        for i in 0..16 {
            block[i] = b[i];
        }
        for i in 0..16 {
            block[i] ^= iv2[i];
        }
        iv1.copy_from_slice(block);
        std::mem::swap(&mut iv2, &mut next_iv2);
    }
}

/// Encrypt/decrypt `buffer` in-place with AES-256-CTR (symmetric).
/// `key` = 32 bytes, `iv` = 16 bytes (full block = counter starting value).
pub fn ctr_crypt(buffer: &mut [u8], key: &[u8; 32], iv: &[u8; 16]) {
    use ctr::Ctr128BE;
    use ctr::cipher::{Array, KeyIvInit, StreamCipher};
    let mut cipher = Ctr128BE::<Aes256>::new(&Array::from(*key), &Array::from(*iv));
    cipher.apply_keystream(buffer);
}

/// Return the effective AES-CTR IV for a CDN chunk starting at `byte_offset`.
/// Telegram CDN increments the counter (big-endian uint128) by `byte_offset / 16`.
pub fn ctr_iv_at_offset(base_iv: &[u8; 16], byte_offset: u64) -> [u8; 16] {
    let block_offset = byte_offset / 16;
    let iv_int = u128::from_be_bytes(*base_iv);
    iv_int.wrapping_add(block_offset as u128).to_be_bytes()
}

/// Decrypt `buffer` in-place with AES-256-IGE.
/// `buffer.len()` must be a multiple of 16.
pub fn ige_decrypt(buffer: &mut [u8], key: &[u8; 32], iv: &[u8; 32]) {
    assert_eq!(buffer.len() % 16, 0);
    let cipher = Aes256::new(&Array::from(*key));

    let mut iv1: [u8; 16] = iv[..16].try_into().unwrap();
    let mut iv2: [u8; 16] = iv[16..].try_into().unwrap();
    let mut next_iv1 = [0u8; 16];

    for block in buffer.chunks_mut(16) {
        next_iv1.copy_from_slice(block);
        for i in 0..16 {
            block[i] ^= iv2[i];
        }
        let mut b = Array::from(<[u8; 16]>::try_from(&*block).unwrap());
        cipher.decrypt_block(&mut b);
        for i in 0..16 {
            block[i] = b[i];
        }
        for i in 0..16 {
            block[i] ^= iv1[i];
        }
        std::mem::swap(&mut iv1, &mut next_iv1);
        iv2.copy_from_slice(block);
    }
}