Skip to main content

opentele2_core/
crypto.rs

1//! Telegram Desktop local crypto: auth keys, `prepareAES_oldmtp`, AES-256-IGE
2//! and the `tdata` local encryption envelope, plus the primitives an `MTProto`
3//! client needs: SHA-1, SHA-256, HMAC-SHA256, PBKDF2, AES-256-CTR, `pq`
4//! factorisation and the operating system's random source.
5//!
6//! AES-IGE itself comes from `grammers-crypto`, which panics on a buffer that
7//! is not a whole number of blocks; every wrapper here validates lengths first
8//! and returns [`Error::BadLength`] instead.
9
10use std::{fmt, io};
11
12use ::sha1::{Digest as _, Sha1};
13use aes::Aes256;
14use ctr::cipher::{KeyIvInit as _, StreamCipher as _};
15use hmac::{KeyInit as _, Mac as _};
16use sha2::{Sha256, Sha512};
17
18use crate::{Error, Result};
19
20/// AES block size in bytes; IGE buffers must be a multiple of it.
21pub const AES_BLOCK_SIZE: usize = 16;
22
23/// Size of an AES-256 key and of an IGE initialisation vector, in bytes.
24pub const AES_KEY_SIZE: usize = 32;
25
26/// Size of the SHA1-derived message key that prefixes local encrypted data.
27pub const MSG_KEY_SIZE: usize = 16;
28
29/// Bytes of the little-endian `u32` length header inside local plaintext.
30const LENGTH_HEADER_SIZE: usize = 4;
31
32/// A 256-byte Telegram Desktop authorization or local key (`MTP::AuthKey`).
33///
34/// `Debug` prints only the key id, never the key material.
35#[derive(Clone)]
36pub struct AuthKey([u8; AuthKey::SIZE]);
37
38impl AuthKey {
39    /// Size of every auth key, in bytes (`AuthKey::kSize`).
40    pub const SIZE: usize = 256;
41
42    /// Wraps raw key bytes.
43    #[must_use]
44    pub const fn new(bytes: [u8; Self::SIZE]) -> Self {
45        Self(bytes)
46    }
47
48    /// Copies a key from a slice.
49    ///
50    /// # Errors
51    /// [`Error::BadLength`] when `bytes` is not exactly [`AuthKey::SIZE`] long.
52    pub fn from_slice(bytes: &[u8]) -> Result<Self> {
53        let key = <[u8; Self::SIZE]>::try_from(bytes).map_err(|_| Error::BadLength {
54            what: "auth key",
55            expected: Self::SIZE,
56            actual: bytes.len(),
57        })?;
58        Ok(Self(key))
59    }
60
61    /// The raw key bytes.
62    #[must_use]
63    pub const fn as_bytes(&self) -> &[u8; Self::SIZE] {
64        &self.0
65    }
66
67    /// The key id: little-endian `u64` of `sha1(key)[12..20]`.
68    #[must_use]
69    pub fn key_id(&self) -> u64 {
70        let hash = Sha1::digest(self.0);
71        let mut id = [0u8; 8];
72        id.copy_from_slice(&hash[12..20]);
73        u64::from_le_bytes(id)
74    }
75}
76
77impl fmt::Debug for AuthKey {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.debug_struct("AuthKey")
80            .field("key_id", &format_args!("{:#018x}", self.key_id()))
81            .finish_non_exhaustive()
82    }
83}
84
85/// Derives the AES-256 key and IGE IV for `msg_key` (`prepareAES_oldmtp`).
86///
87/// `send` selects the key offset: 0 when sending, 8 when receiving. Local
88/// storage always uses `send = false`.
89#[must_use]
90pub fn prepare_aes_oldmtp(
91    auth_key: &AuthKey,
92    msg_key: &[u8; MSG_KEY_SIZE],
93    send: bool,
94) -> ([u8; AES_KEY_SIZE], [u8; AES_KEY_SIZE]) {
95    let x = if send { 0 } else { 8 };
96    let key = auth_key.as_bytes();
97
98    let sha1_a = Sha1::new()
99        .chain_update(msg_key)
100        .chain_update(&key[x..x + 32])
101        .finalize();
102    let sha1_b = Sha1::new()
103        .chain_update(&key[x + 32..x + 48])
104        .chain_update(msg_key)
105        .chain_update(&key[x + 48..x + 64])
106        .finalize();
107    let sha1_c = Sha1::new()
108        .chain_update(&key[x + 64..x + 96])
109        .chain_update(msg_key)
110        .finalize();
111    let sha1_d = Sha1::new()
112        .chain_update(msg_key)
113        .chain_update(&key[x + 96..x + 128])
114        .finalize();
115
116    let mut aes_key = [0u8; AES_KEY_SIZE];
117    aes_key[..8].copy_from_slice(&sha1_a[..8]);
118    aes_key[8..20].copy_from_slice(&sha1_b[8..20]);
119    aes_key[20..].copy_from_slice(&sha1_c[4..16]);
120
121    let mut aes_iv = [0u8; AES_KEY_SIZE];
122    aes_iv[..12].copy_from_slice(&sha1_a[8..20]);
123    aes_iv[12..20].copy_from_slice(&sha1_b[..8]);
124    aes_iv[20..24].copy_from_slice(&sha1_c[16..20]);
125    aes_iv[24..].copy_from_slice(&sha1_d[..8]);
126
127    (aes_key, aes_iv)
128}
129
130/// Validates an IGE call and returns the key and IV as arrays.
131fn ige_params<'k>(
132    buf: &[u8],
133    key: &'k [u8],
134    iv: &'k [u8],
135) -> Result<(&'k [u8; AES_KEY_SIZE], &'k [u8; AES_KEY_SIZE])> {
136    if !buf.len().is_multiple_of(AES_BLOCK_SIZE) {
137        return Err(Error::BadLength {
138            what: "AES-IGE buffer",
139            expected: buf.len().next_multiple_of(AES_BLOCK_SIZE),
140            actual: buf.len(),
141        });
142    }
143    let key = <&[u8; AES_KEY_SIZE]>::try_from(key).map_err(|_| Error::BadLength {
144        what: "AES-IGE key",
145        expected: AES_KEY_SIZE,
146        actual: key.len(),
147    })?;
148    let iv = <&[u8; AES_KEY_SIZE]>::try_from(iv).map_err(|_| Error::BadLength {
149        what: "AES-IGE iv",
150        expected: AES_KEY_SIZE,
151        actual: iv.len(),
152    })?;
153    Ok((key, iv))
154}
155
156/// Encrypts `buf` in place with AES-256-IGE.
157///
158/// # Errors
159/// [`Error::BadLength`] when `buf` is not a multiple of 16 bytes or `key` or
160/// `iv` is not 32 bytes; `buf` is left untouched.
161pub fn aes_ige_encrypt(buf: &mut [u8], key: &[u8], iv: &[u8]) -> Result<()> {
162    let (key, iv) = ige_params(buf, key, iv)?;
163    grammers_crypto::aes::ige_encrypt(buf, key, iv);
164    Ok(())
165}
166
167/// Decrypts `buf` in place with AES-256-IGE.
168///
169/// # Errors
170/// [`Error::BadLength`] when `buf` is not a multiple of 16 bytes or `key` or
171/// `iv` is not 32 bytes; `buf` is left untouched.
172pub fn aes_ige_decrypt(buf: &mut [u8], key: &[u8], iv: &[u8]) -> Result<()> {
173    let (key, iv) = ige_params(buf, key, iv)?;
174    grammers_crypto::aes::ige_decrypt(buf, key, iv);
175    Ok(())
176}
177
178/// Number of padding bytes [`encrypt_local`] requests for `data_len` bytes.
179#[must_use]
180pub const fn local_padding_len(data_len: usize) -> usize {
181    let size = data_len + LENGTH_HEADER_SIZE;
182    size.next_multiple_of(AES_BLOCK_SIZE) - size
183}
184
185/// Encrypts `data` for local storage (`Storage::PrepareEncrypted`).
186///
187/// The plaintext is the little-endian `u32` real length (`data.len() + 4`),
188/// then `data`, then padding to a 16-byte boundary taken from
189/// `padding_source(len)`. The output is `sha1(plaintext)[..16]` followed by
190/// the plaintext under AES-256-IGE keyed by
191/// `prepare_aes_oldmtp(local_key, msg_key, false)`.
192///
193/// # Errors
194/// [`Error::BadLength`] when `data` is too long for a `u32` length header or
195/// `padding_source` returns a buffer of the wrong length.
196pub fn encrypt_local(
197    data: &[u8],
198    local_key: &AuthKey,
199    padding_source: impl FnOnce(usize) -> Vec<u8>,
200) -> Result<Vec<u8>> {
201    let size = data.len() + LENGTH_HEADER_SIZE;
202    let real_len = u32::try_from(size).map_err(|_| Error::BadLength {
203        what: "local data",
204        expected: usize::try_from(u32::MAX).unwrap_or(usize::MAX),
205        actual: size,
206    })?;
207    let padding_len = local_padding_len(data.len());
208    let padding = padding_source(padding_len);
209    if padding.len() != padding_len {
210        return Err(Error::BadLength {
211            what: "local padding",
212            expected: padding_len,
213            actual: padding.len(),
214        });
215    }
216
217    let mut out = Vec::with_capacity(MSG_KEY_SIZE + size + padding_len);
218    out.extend_from_slice(&[0u8; MSG_KEY_SIZE]);
219    out.extend_from_slice(&real_len.to_le_bytes());
220    out.extend_from_slice(data);
221    out.extend_from_slice(&padding);
222
223    let (head, body) = out.split_at_mut(MSG_KEY_SIZE);
224    let mut msg_key = [0u8; MSG_KEY_SIZE];
225    msg_key.copy_from_slice(&Sha1::digest(&*body)[..MSG_KEY_SIZE]);
226    head.copy_from_slice(&msg_key);
227
228    let (aes_key, aes_iv) = prepare_aes_oldmtp(local_key, &msg_key, false);
229    aes_ige_encrypt(body, &aes_key, &aes_iv)?;
230    Ok(out)
231}
232
233/// [`encrypt_local`] with padding from the operating system's CSPRNG, as
234/// Telegram Desktop does.
235///
236/// # Errors
237/// [`Error::Io`] when the system random source fails, otherwise as
238/// [`encrypt_local`].
239pub fn encrypt_local_random(data: &[u8], local_key: &AuthKey) -> Result<Vec<u8>> {
240    let mut padding = vec![0u8; local_padding_len(data.len())];
241    random_fill(&mut padding)?;
242    encrypt_local(data, local_key, move |_| padding)
243}
244
245/// Decrypts local storage data written by [`encrypt_local`]
246/// (`Storage::DecryptLocal`) and returns the `data` part, without the
247/// length header or padding.
248///
249/// # Errors
250/// - [`Error::BadEncryptedDataSize`] when `encrypted` is at most 16 bytes or
251///   not a multiple of 16.
252/// - [`Error::BadDecryptKey`] when the decrypted SHA1 does not match the
253///   message key, which usually means a wrong passcode.
254/// - [`Error::BadDecryptedDataSize`] when the length header does not fit.
255pub fn decrypt_local(encrypted: &[u8], local_key: &AuthKey) -> Result<Vec<u8>> {
256    let encrypted_size = encrypted.len();
257    if encrypted_size <= AES_BLOCK_SIZE || !encrypted_size.is_multiple_of(AES_BLOCK_SIZE) {
258        return Err(Error::BadEncryptedDataSize {
259            size: encrypted_size,
260        });
261    }
262
263    let mut msg_key = [0u8; MSG_KEY_SIZE];
264    msg_key.copy_from_slice(&encrypted[..MSG_KEY_SIZE]);
265    let mut decrypted = encrypted[MSG_KEY_SIZE..].to_vec();
266
267    let (aes_key, aes_iv) = prepare_aes_oldmtp(local_key, &msg_key, false);
268    aes_ige_decrypt(&mut decrypted, &aes_key, &aes_iv)?;
269    if Sha1::digest(&decrypted)[..MSG_KEY_SIZE] != msg_key {
270        return Err(Error::BadDecryptKey);
271    }
272
273    let full_len = encrypted_size - MSG_KEY_SIZE;
274    let bad_size = Error::BadDecryptedDataSize {
275        encrypted_size,
276        full_len,
277        decrypted_size: decrypted.len(),
278    };
279    let mut header = [0u8; LENGTH_HEADER_SIZE];
280    header.copy_from_slice(&decrypted[..LENGTH_HEADER_SIZE]);
281    let Ok(data_len) = usize::try_from(u32::from_le_bytes(header)) else {
282        return Err(bad_size);
283    };
284    if data_len > decrypted.len()
285        || data_len <= full_len - AES_BLOCK_SIZE
286        || data_len < LENGTH_HEADER_SIZE
287    {
288        return Err(bad_size);
289    }
290
291    decrypted.truncate(data_len);
292    decrypted.drain(..LENGTH_HEADER_SIZE);
293    Ok(decrypted)
294}
295
296/// Size of an AES-CTR initial counter block, in bytes.
297pub const AES_CTR_IV_SIZE: usize = 16;
298
299/// Largest `pq` [`factorize_pq`] accepts, in bytes.
300pub const PQ_MAX_SIZE: usize = 8;
301
302/// HMAC block size of SHA-256, in bytes.
303const SHA256_BLOCK_SIZE: usize = 64;
304
305/// SHA-1 of `data` (FIPS 180-4).
306#[must_use]
307pub fn sha1(data: &[u8]) -> [u8; 20] {
308    Sha1::digest(data).into()
309}
310
311/// SHA-256 of `data` (FIPS 180-4).
312#[must_use]
313pub fn sha256(data: &[u8]) -> [u8; 32] {
314    Sha256::digest(data).into()
315}
316
317/// HMAC-SHA256 of `data` under `key` (RFC 2104); `key` may have any length.
318#[must_use]
319pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
320    // RFC 2104 hashes a key longer than the block and zero-pads the rest, so
321    // the block-sized key gives the same MAC through the infallible `new`.
322    let mut block = hmac::digest::Key::<hmac::Hmac<Sha256>>::default();
323    if key.len() > SHA256_BLOCK_SIZE {
324        block[..32].copy_from_slice(&Sha256::digest(key));
325    } else {
326        block[..key.len()].copy_from_slice(key);
327    }
328    let mut mac = hmac::Hmac::<Sha256>::new(&block);
329    mac.update(data);
330    mac.finalize().into_bytes().into()
331}
332
333/// Hash function of the HMAC that [`pbkdf2()`] runs.
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335pub enum Digest {
336    /// SHA-1.
337    Sha1,
338    /// SHA-256.
339    Sha256,
340    /// SHA-512.
341    Sha512,
342}
343
344impl Digest {
345    /// Parses `"sha1"`, `"sha256"` or `"sha512"`, ignoring ASCII case.
346    ///
347    /// # Errors
348    /// [`Error::InvalidInput`] for any other name.
349    pub fn from_name(name: &str) -> Result<Self> {
350        [
351            ("sha1", Self::Sha1),
352            ("sha256", Self::Sha256),
353            ("sha512", Self::Sha512),
354        ]
355        .into_iter()
356        .find_map(|(known, digest)| name.eq_ignore_ascii_case(known).then_some(digest))
357        .ok_or_else(|| Error::InvalidInput {
358            what: "PBKDF2 digest",
359            reason: format!("unknown digest {name:?}"),
360        })
361    }
362}
363
364/// PBKDF2 (RFC 8018) with HMAC over `digest`, producing `key_len` bytes.
365///
366/// # Errors
367/// [`Error::InvalidInput`] when `iterations` is 0, which RFC 8018 does not
368/// define.
369pub fn pbkdf2(
370    password: &[u8],
371    salt: &[u8],
372    iterations: u32,
373    key_len: usize,
374    digest: Digest,
375) -> Result<Vec<u8>> {
376    if iterations == 0 {
377        return Err(Error::InvalidInput {
378            what: "PBKDF2 iterations",
379            reason: "needs at least one iteration".to_owned(),
380        });
381    }
382    let mut key = vec![0u8; key_len];
383    match digest {
384        Digest::Sha1 => ::pbkdf2::pbkdf2_hmac::<Sha1>(password, salt, iterations, &mut key),
385        Digest::Sha256 => ::pbkdf2::pbkdf2_hmac::<Sha256>(password, salt, iterations, &mut key),
386        Digest::Sha512 => ::pbkdf2::pbkdf2_hmac::<Sha512>(password, salt, iterations, &mut key),
387    }
388    Ok(key)
389}
390
391/// AES-256 in CTR mode with the 16-byte IV as a 128-bit big-endian counter,
392/// as OpenSSL's `aes-256-ctr`.
393///
394/// The keystream continues across [`AesCtr::apply`] calls, so a buffer
395/// processed in pieces gives the same bytes as in one call. Encryption and
396/// decryption are the same operation. `Debug` never prints key material.
397pub struct AesCtr(ctr::Ctr128BE<Aes256>);
398
399impl AesCtr {
400    /// Starts a keystream at counter `iv` under `key`.
401    ///
402    /// # Errors
403    /// [`Error::BadLength`] when `key` is not 32 bytes or `iv` is not 16.
404    pub fn new(key: &[u8], iv: &[u8]) -> Result<Self> {
405        let key = <[u8; AES_KEY_SIZE]>::try_from(key).map_err(|_| Error::BadLength {
406            what: "AES-CTR key",
407            expected: AES_KEY_SIZE,
408            actual: key.len(),
409        })?;
410        let iv = <[u8; AES_CTR_IV_SIZE]>::try_from(iv).map_err(|_| Error::BadLength {
411            what: "AES-CTR iv",
412            expected: AES_CTR_IV_SIZE,
413            actual: iv.len(),
414        })?;
415        Ok(Self(ctr::Ctr128BE::<Aes256>::new(&key.into(), &iv.into())))
416    }
417
418    /// XORs the next `buf.len()` keystream bytes into `buf`.
419    pub fn apply(&mut self, buf: &mut [u8]) {
420        self.0.apply_keystream(buf);
421    }
422}
423
424impl fmt::Debug for AesCtr {
425    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426        f.debug_struct("AesCtr").finish_non_exhaustive()
427    }
428}
429
430/// Splits the `MTProto` `pq` into its factors `p <= q`.
431///
432/// `pq` is big-endian, 1 to 8 bytes, leading zeros allowed. `p` and `q` are
433/// returned as minimal big-endian encodings; they are equal only when `pq`
434/// is a square. The factorisation is deterministic Miller-Rabin followed by
435/// Pollard-Brent rho, and never panics.
436///
437/// # Errors
438/// - [`Error::BadLength`] when `pq` is empty or longer than 8 bytes.
439/// - [`Error::InvalidInput`] when `pq` is 0, 1, or a prime, so it is not a
440///   product of two factors greater than 1.
441pub fn factorize_pq(pq: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
442    if pq.is_empty() || pq.len() > PQ_MAX_SIZE {
443        return Err(Error::BadLength {
444            what: "pq",
445            expected: PQ_MAX_SIZE,
446            actual: pq.len(),
447        });
448    }
449    let mut be = [0u8; PQ_MAX_SIZE];
450    be[PQ_MAX_SIZE - pq.len()..].copy_from_slice(pq);
451    let n = u64::from_be_bytes(be);
452    let factor = find_factor(n).ok_or_else(|| Error::InvalidInput {
453        what: "pq",
454        reason: format!("{n} is not a product of two factors greater than 1"),
455    })?;
456    let (p, q) = (factor.min(n / factor), factor.max(n / factor));
457    Ok((minimal_be(p), minimal_be(q)))
458}
459
460/// `value` big-endian without leading zero bytes.
461fn minimal_be(value: u64) -> Vec<u8> {
462    let bytes = value.to_be_bytes();
463    let skip = bytes.iter().take_while(|&&b| b == 0).count();
464    bytes[skip..].to_vec()
465}
466
467/// A factor `d` of `n` with `1 < d < n`, or `None` when `n` is below 4 or
468/// prime.
469fn find_factor(n: u64) -> Option<u64> {
470    if n < 4 {
471        return None;
472    }
473    if n.is_multiple_of(2) {
474        return Some(2);
475    }
476    if is_prime(n) {
477        return None;
478    }
479    // A composite `n` always yields to some `c`; the range only bounds the
480    // search so the function stays total.
481    (1..u64::MAX).find_map(|c| pollard_brent(n, c))
482}
483
484/// `a * b mod m` without overflow.
485fn mul_mod(a: u64, b: u64, m: u64) -> u64 {
486    let product = u128::from(a) * u128::from(b) % u128::from(m);
487    // The remainder is below `m`, so it always fits.
488    u64::try_from(product).unwrap_or_default()
489}
490
491/// `base^exp mod m`.
492fn pow_mod(mut base: u64, mut exp: u64, m: u64) -> u64 {
493    let mut result = 1 % m;
494    base %= m;
495    while exp > 0 {
496        if exp & 1 == 1 {
497            result = mul_mod(result, base, m);
498        }
499        base = mul_mod(base, base, m);
500        exp >>= 1;
501    }
502    result
503}
504
505/// Deterministic Miller-Rabin for odd `n >= 5`: the first twelve primes as
506/// bases decide every `u64`.
507fn is_prime(n: u64) -> bool {
508    const BASES: [u64; 12] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
509    let s = (n - 1).trailing_zeros();
510    let d = (n - 1) >> s;
511    BASES.iter().all(|&a| {
512        if a % n == 0 {
513            return true;
514        }
515        let mut x = pow_mod(a, d, n);
516        if x == 1 || x == n - 1 {
517            return true;
518        }
519        for _ in 1..s {
520            x = mul_mod(x, x, n);
521            if x == n - 1 {
522                return true;
523            }
524        }
525        false
526    })
527}
528
529/// Greatest common divisor.
530fn gcd(mut a: u64, mut b: u64) -> u64 {
531    while b != 0 {
532        (a, b) = (b, a % b);
533    }
534    a
535}
536
537/// One Pollard-Brent rho run over `x -> x^2 + c mod n`: a factor strictly
538/// between 1 and `n`, or `None` when this `c` only finds `n` itself.
539fn pollard_brent(n: u64, increment: u64) -> Option<u64> {
540    const BATCH: u64 = 128;
541    let step = |value: u64| {
542        let sum = u128::from(mul_mod(value, value, n)) + u128::from(increment);
543        u64::try_from(sum % u128::from(n)).unwrap_or_default()
544    };
545    let (mut anchor, mut walker, mut batch_start) = (2u64, 2u64, 2u64);
546    let (mut divisor, mut product, mut span) = (1u64, 1u64, 1u64);
547    while divisor == 1 {
548        anchor = walker;
549        for _ in 0..span {
550            walker = step(walker);
551        }
552        let mut done = 0;
553        while done < span && divisor == 1 {
554            batch_start = walker;
555            for _ in 0..BATCH.min(span - done) {
556                walker = step(walker);
557                product = mul_mod(product, anchor.abs_diff(walker), n);
558            }
559            divisor = gcd(product, n);
560            done += BATCH;
561        }
562        span = span.saturating_mul(2);
563    }
564    if divisor == n {
565        // The batch overshot: replay it one step at a time from its start.
566        loop {
567            batch_start = step(batch_start);
568            divisor = gcd(anchor.abs_diff(batch_start), n);
569            if divisor > 1 {
570                break;
571            }
572        }
573    }
574    (divisor != n).then_some(divisor)
575}
576
577/// Fills `buf` from the operating system's CSPRNG.
578///
579/// # Errors
580/// [`Error::Io`] when the system random source fails.
581pub fn random_fill(buf: &mut [u8]) -> Result<()> {
582    getrandom::fill(buf).map_err(|e| Error::Io(io::Error::other(e)))
583}