Skip to main content

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