Skip to main content

layer_crypto/
rsa.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// NOTE:
5// The "Layer" project is no longer maintained or supported.
6// Its original purpose for personal SDK/APK experimentation and learning
7// has been fulfilled.
8//
9// Please use Ferogram instead:
10// https://github.com/ankit-chaubey/ferogram
11// Ferogram will receive future updates and development, although progress
12// may be slower.
13//
14// Ferogram is an async Telegram MTProto client library written in Rust.
15// Its implementation follows the behaviour of the official Telegram clients,
16// particularly Telegram Desktop and TDLib, and aims to provide a clean and
17// modern async interface for building Telegram clients and tools.
18
19//! RSA padding used by Telegram's auth key exchange.
20
21use crate::{aes, sha256};
22use num_bigint::BigUint;
23
24/// An RSA public key (n, e).
25pub struct Key {
26    n: BigUint,
27    e: BigUint,
28}
29
30impl Key {
31    /// Parse decimal `n` and `e` strings.
32    pub fn new(n: &str, e: &str) -> Option<Self> {
33        Some(Self {
34            n: BigUint::parse_bytes(n.as_bytes(), 10)?,
35            e: BigUint::parse_bytes(e.as_bytes(), 10)?,
36        })
37    }
38}
39
40fn increment(data: &mut [u8]) {
41    let mut i = data.len() - 1;
42    loop {
43        let (n, overflow) = data[i].overflowing_add(1);
44        data[i] = n;
45        if overflow {
46            i = i.checked_sub(1).unwrap_or(data.len() - 1);
47        } else {
48            break;
49        }
50    }
51}
52
53/// RSA-encrypt `data` using the MTProto RSA-PAD scheme.
54///
55/// `random_bytes` must be exactly 224 bytes of secure random data.
56/// `data` must be ≤ 144 bytes.
57pub fn encrypt_hashed(data: &[u8], key: &Key, random_bytes: &[u8; 224]) -> Vec<u8> {
58    assert!(data.len() <= 144, "data too large for RSA-PAD");
59
60    // data_with_padding: 192 bytes
61    let mut data_with_padding = Vec::with_capacity(192);
62    data_with_padding.extend_from_slice(data);
63    data_with_padding.extend_from_slice(&random_bytes[..192 - data.len()]);
64
65    // data_pad_reversed
66    let data_pad_reversed: Vec<u8> = data_with_padding.iter().copied().rev().collect();
67
68    let mut temp_key: [u8; 32] = random_bytes[192..].try_into().unwrap();
69
70    let key_aes_encrypted = loop {
71        // data_with_hash = data_pad_reversed + SHA256(temp_key + data_with_padding)
72        let mut data_with_hash = Vec::with_capacity(224);
73        data_with_hash.extend_from_slice(&data_pad_reversed);
74        data_with_hash.extend_from_slice(&sha256!(&temp_key, &data_with_padding));
75
76        aes::ige_encrypt(&mut data_with_hash, &temp_key, &[0u8; 32]);
77
78        // temp_key_xor = temp_key XOR SHA256(aes_encrypted)
79        let hash = sha256!(&data_with_hash);
80        let mut xored = temp_key;
81        for (a, b) in xored.iter_mut().zip(hash.iter()) {
82            *a ^= b;
83        }
84
85        let mut candidate = Vec::with_capacity(256);
86        candidate.extend_from_slice(&xored);
87        candidate.extend_from_slice(&data_with_hash);
88
89        if BigUint::from_bytes_be(&candidate) < key.n {
90            break candidate;
91        }
92        increment(&mut temp_key);
93    };
94
95    let payload = BigUint::from_bytes_be(&key_aes_encrypted);
96    let encrypted = payload.modpow(&key.e, &key.n);
97    let mut block = encrypted.to_bytes_be();
98    while block.len() < 256 {
99        block.insert(0, 0);
100    }
101    block
102}