Skip to main content

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