Skip to main content

ferogram_crypto/
rsa.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
15use crate::{aes, sha256};
16use num_bigint::BigUint;
17
18/// An RSA public key (n, e).
19pub struct Key {
20    n: BigUint,
21    e: BigUint,
22}
23
24impl Key {
25    /// Parse decimal `n` and `e` strings.
26    pub fn new(n: &str, e: &str) -> Option<Self> {
27        Some(Self {
28            n: BigUint::parse_bytes(n.as_bytes(), 10)?,
29            e: BigUint::parse_bytes(e.as_bytes(), 10)?,
30        })
31    }
32}
33
34fn increment(data: &mut [u8]) {
35    let mut i = data.len() - 1;
36    loop {
37        let (n, overflow) = data[i].overflowing_add(1);
38        data[i] = n;
39        if overflow {
40            i = i.checked_sub(1).unwrap_or(data.len() - 1);
41        } else {
42            break;
43        }
44    }
45}
46
47/// RSA-encrypt `data` using the MTProto RSA-PAD scheme.
48///
49/// `random_bytes` must be exactly 224 bytes of secure random data.
50/// `data` must be ≤ 144 bytes.
51pub fn encrypt_hashed(data: &[u8], key: &Key, random_bytes: &[u8; 224]) -> Vec<u8> {
52    assert!(data.len() <= 144, "data too large for RSA-PAD");
53
54    // data_with_padding: 192 bytes
55    let mut data_with_padding = Vec::with_capacity(192);
56    data_with_padding.extend_from_slice(data);
57    data_with_padding.extend_from_slice(&random_bytes[..192 - data.len()]);
58
59    // data_pad_reversed
60    let data_pad_reversed: Vec<u8> = data_with_padding.iter().copied().rev().collect();
61
62    let mut temp_key: [u8; 32] = random_bytes[192..].try_into().unwrap();
63
64    let key_aes_encrypted = loop {
65        // data_with_hash = data_pad_reversed + SHA256(temp_key + data_with_padding)
66        let mut data_with_hash = Vec::with_capacity(224);
67        data_with_hash.extend_from_slice(&data_pad_reversed);
68        data_with_hash.extend_from_slice(&sha256!(&temp_key, &data_with_padding));
69
70        aes::ige_encrypt(&mut data_with_hash, &temp_key, &[0u8; 32]);
71
72        // temp_key_xor = temp_key XOR SHA256(aes_encrypted)
73        let hash = sha256!(&data_with_hash);
74        let mut xored = temp_key;
75        for (a, b) in xored.iter_mut().zip(hash.iter()) {
76            *a ^= b;
77        }
78
79        let mut candidate = Vec::with_capacity(256);
80        candidate.extend_from_slice(&xored);
81        candidate.extend_from_slice(&data_with_hash);
82
83        if BigUint::from_bytes_be(&candidate) < key.n {
84            break candidate;
85        }
86        increment(&mut temp_key);
87    };
88
89    let payload = BigUint::from_bytes_be(&key_aes_encrypted);
90    let encrypted = payload.modpow(&key.e, &key.n);
91    let mut block = encrypted.to_bytes_be();
92    while block.len() < 256 {
93        block.insert(0, 0);
94    }
95    block
96}