use std::{fmt, io};
use ::sha1::{Digest as _, Sha1};
use aes::Aes256;
use ctr::cipher::{KeyIvInit as _, StreamCipher as _};
use hmac::{KeyInit as _, Mac as _};
use sha2::{Sha256, Sha512};
use crate::{Error, Result};
pub const AES_BLOCK_SIZE: usize = 16;
pub const AES_KEY_SIZE: usize = 32;
pub const MSG_KEY_SIZE: usize = 16;
const LENGTH_HEADER_SIZE: usize = 4;
#[derive(Clone)]
pub struct AuthKey([u8; AuthKey::SIZE]);
impl AuthKey {
pub const SIZE: usize = 256;
#[must_use]
pub const fn new(bytes: [u8; Self::SIZE]) -> Self {
Self(bytes)
}
pub fn from_slice(bytes: &[u8]) -> Result<Self> {
let key = <[u8; Self::SIZE]>::try_from(bytes).map_err(|_| Error::BadLength {
what: "auth key",
expected: Self::SIZE,
actual: bytes.len(),
})?;
Ok(Self(key))
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; Self::SIZE] {
&self.0
}
#[must_use]
pub fn key_id(&self) -> u64 {
let hash = Sha1::digest(self.0);
let mut id = [0u8; 8];
id.copy_from_slice(&hash[12..20]);
u64::from_le_bytes(id)
}
}
impl fmt::Debug for AuthKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AuthKey")
.field("key_id", &format_args!("{:#018x}", self.key_id()))
.finish_non_exhaustive()
}
}
#[must_use]
pub fn prepare_aes_oldmtp(
auth_key: &AuthKey,
msg_key: &[u8; MSG_KEY_SIZE],
send: bool,
) -> ([u8; AES_KEY_SIZE], [u8; AES_KEY_SIZE]) {
let x = if send { 0 } else { 8 };
let key = auth_key.as_bytes();
let sha1_a = Sha1::new()
.chain_update(msg_key)
.chain_update(&key[x..x + 32])
.finalize();
let sha1_b = Sha1::new()
.chain_update(&key[x + 32..x + 48])
.chain_update(msg_key)
.chain_update(&key[x + 48..x + 64])
.finalize();
let sha1_c = Sha1::new()
.chain_update(&key[x + 64..x + 96])
.chain_update(msg_key)
.finalize();
let sha1_d = Sha1::new()
.chain_update(msg_key)
.chain_update(&key[x + 96..x + 128])
.finalize();
let mut aes_key = [0u8; AES_KEY_SIZE];
aes_key[..8].copy_from_slice(&sha1_a[..8]);
aes_key[8..20].copy_from_slice(&sha1_b[8..20]);
aes_key[20..].copy_from_slice(&sha1_c[4..16]);
let mut aes_iv = [0u8; AES_KEY_SIZE];
aes_iv[..12].copy_from_slice(&sha1_a[8..20]);
aes_iv[12..20].copy_from_slice(&sha1_b[..8]);
aes_iv[20..24].copy_from_slice(&sha1_c[16..20]);
aes_iv[24..].copy_from_slice(&sha1_d[..8]);
(aes_key, aes_iv)
}
fn ige_params<'k>(
buf: &[u8],
key: &'k [u8],
iv: &'k [u8],
) -> Result<(&'k [u8; AES_KEY_SIZE], &'k [u8; AES_KEY_SIZE])> {
if !buf.len().is_multiple_of(AES_BLOCK_SIZE) {
return Err(Error::BadLength {
what: "AES-IGE buffer",
expected: buf.len().next_multiple_of(AES_BLOCK_SIZE),
actual: buf.len(),
});
}
let key = <&[u8; AES_KEY_SIZE]>::try_from(key).map_err(|_| Error::BadLength {
what: "AES-IGE key",
expected: AES_KEY_SIZE,
actual: key.len(),
})?;
let iv = <&[u8; AES_KEY_SIZE]>::try_from(iv).map_err(|_| Error::BadLength {
what: "AES-IGE iv",
expected: AES_KEY_SIZE,
actual: iv.len(),
})?;
Ok((key, iv))
}
pub fn aes_ige_encrypt(buf: &mut [u8], key: &[u8], iv: &[u8]) -> Result<()> {
let (key, iv) = ige_params(buf, key, iv)?;
grammers_crypto::aes::ige_encrypt(buf, key, iv);
Ok(())
}
pub fn aes_ige_decrypt(buf: &mut [u8], key: &[u8], iv: &[u8]) -> Result<()> {
let (key, iv) = ige_params(buf, key, iv)?;
grammers_crypto::aes::ige_decrypt(buf, key, iv);
Ok(())
}
#[must_use]
pub const fn local_padding_len(data_len: usize) -> usize {
let size = data_len + LENGTH_HEADER_SIZE;
size.next_multiple_of(AES_BLOCK_SIZE) - size
}
pub fn encrypt_local(
data: &[u8],
local_key: &AuthKey,
padding_source: impl FnOnce(usize) -> Vec<u8>,
) -> Result<Vec<u8>> {
let size = data.len() + LENGTH_HEADER_SIZE;
let real_len = u32::try_from(size).map_err(|_| Error::BadLength {
what: "local data",
expected: usize::try_from(u32::MAX).unwrap_or(usize::MAX),
actual: size,
})?;
let padding_len = local_padding_len(data.len());
let padding = padding_source(padding_len);
if padding.len() != padding_len {
return Err(Error::BadLength {
what: "local padding",
expected: padding_len,
actual: padding.len(),
});
}
let mut out = Vec::with_capacity(MSG_KEY_SIZE + size + padding_len);
out.extend_from_slice(&[0u8; MSG_KEY_SIZE]);
out.extend_from_slice(&real_len.to_le_bytes());
out.extend_from_slice(data);
out.extend_from_slice(&padding);
let (head, body) = out.split_at_mut(MSG_KEY_SIZE);
let mut msg_key = [0u8; MSG_KEY_SIZE];
msg_key.copy_from_slice(&Sha1::digest(&*body)[..MSG_KEY_SIZE]);
head.copy_from_slice(&msg_key);
let (aes_key, aes_iv) = prepare_aes_oldmtp(local_key, &msg_key, false);
aes_ige_encrypt(body, &aes_key, &aes_iv)?;
Ok(out)
}
pub fn encrypt_local_random(data: &[u8], local_key: &AuthKey) -> Result<Vec<u8>> {
let mut padding = vec![0u8; local_padding_len(data.len())];
random_fill(&mut padding)?;
encrypt_local(data, local_key, move |_| padding)
}
pub fn decrypt_local(encrypted: &[u8], local_key: &AuthKey) -> Result<Vec<u8>> {
let encrypted_size = encrypted.len();
if encrypted_size <= AES_BLOCK_SIZE || !encrypted_size.is_multiple_of(AES_BLOCK_SIZE) {
return Err(Error::BadEncryptedDataSize {
size: encrypted_size,
});
}
let mut msg_key = [0u8; MSG_KEY_SIZE];
msg_key.copy_from_slice(&encrypted[..MSG_KEY_SIZE]);
let mut decrypted = encrypted[MSG_KEY_SIZE..].to_vec();
let (aes_key, aes_iv) = prepare_aes_oldmtp(local_key, &msg_key, false);
aes_ige_decrypt(&mut decrypted, &aes_key, &aes_iv)?;
if Sha1::digest(&decrypted)[..MSG_KEY_SIZE] != msg_key {
return Err(Error::BadDecryptKey);
}
let full_len = encrypted_size - MSG_KEY_SIZE;
let bad_size = Error::BadDecryptedDataSize {
encrypted_size,
full_len,
decrypted_size: decrypted.len(),
};
let mut header = [0u8; LENGTH_HEADER_SIZE];
header.copy_from_slice(&decrypted[..LENGTH_HEADER_SIZE]);
let Ok(data_len) = usize::try_from(u32::from_le_bytes(header)) else {
return Err(bad_size);
};
if data_len > decrypted.len()
|| data_len <= full_len - AES_BLOCK_SIZE
|| data_len < LENGTH_HEADER_SIZE
{
return Err(bad_size);
}
decrypted.truncate(data_len);
decrypted.drain(..LENGTH_HEADER_SIZE);
Ok(decrypted)
}
pub const AES_CTR_IV_SIZE: usize = 16;
pub const PQ_MAX_SIZE: usize = 8;
const SHA256_BLOCK_SIZE: usize = 64;
#[must_use]
pub fn sha1(data: &[u8]) -> [u8; 20] {
Sha1::digest(data).into()
}
#[must_use]
pub fn sha256(data: &[u8]) -> [u8; 32] {
Sha256::digest(data).into()
}
#[must_use]
pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
let mut block = hmac::digest::Key::<hmac::Hmac<Sha256>>::default();
if key.len() > SHA256_BLOCK_SIZE {
block[..32].copy_from_slice(&Sha256::digest(key));
} else {
block[..key.len()].copy_from_slice(key);
}
let mut mac = hmac::Hmac::<Sha256>::new(&block);
mac.update(data);
mac.finalize().into_bytes().into()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Digest {
Sha1,
Sha256,
Sha512,
}
impl Digest {
pub fn from_name(name: &str) -> Result<Self> {
[
("sha1", Self::Sha1),
("sha256", Self::Sha256),
("sha512", Self::Sha512),
]
.into_iter()
.find_map(|(known, digest)| name.eq_ignore_ascii_case(known).then_some(digest))
.ok_or_else(|| Error::InvalidInput {
what: "PBKDF2 digest",
reason: format!("unknown digest {name:?}"),
})
}
}
pub fn pbkdf2(
password: &[u8],
salt: &[u8],
iterations: u32,
key_len: usize,
digest: Digest,
) -> Result<Vec<u8>> {
if iterations == 0 {
return Err(Error::InvalidInput {
what: "PBKDF2 iterations",
reason: "needs at least one iteration".to_owned(),
});
}
let mut key = vec![0u8; key_len];
match digest {
Digest::Sha1 => ::pbkdf2::pbkdf2_hmac::<Sha1>(password, salt, iterations, &mut key),
Digest::Sha256 => ::pbkdf2::pbkdf2_hmac::<Sha256>(password, salt, iterations, &mut key),
Digest::Sha512 => ::pbkdf2::pbkdf2_hmac::<Sha512>(password, salt, iterations, &mut key),
}
Ok(key)
}
pub struct AesCtr(ctr::Ctr128BE<Aes256>);
impl AesCtr {
pub fn new(key: &[u8], iv: &[u8]) -> Result<Self> {
let key = <[u8; AES_KEY_SIZE]>::try_from(key).map_err(|_| Error::BadLength {
what: "AES-CTR key",
expected: AES_KEY_SIZE,
actual: key.len(),
})?;
let iv = <[u8; AES_CTR_IV_SIZE]>::try_from(iv).map_err(|_| Error::BadLength {
what: "AES-CTR iv",
expected: AES_CTR_IV_SIZE,
actual: iv.len(),
})?;
Ok(Self(ctr::Ctr128BE::<Aes256>::new(&key.into(), &iv.into())))
}
pub fn apply(&mut self, buf: &mut [u8]) {
self.0.apply_keystream(buf);
}
}
impl fmt::Debug for AesCtr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AesCtr").finish_non_exhaustive()
}
}
pub fn factorize_pq(pq: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
if pq.is_empty() || pq.len() > PQ_MAX_SIZE {
return Err(Error::BadLength {
what: "pq",
expected: PQ_MAX_SIZE,
actual: pq.len(),
});
}
let mut be = [0u8; PQ_MAX_SIZE];
be[PQ_MAX_SIZE - pq.len()..].copy_from_slice(pq);
let n = u64::from_be_bytes(be);
let factor = find_factor(n).ok_or_else(|| Error::InvalidInput {
what: "pq",
reason: format!("{n} is not a product of two factors greater than 1"),
})?;
let (p, q) = (factor.min(n / factor), factor.max(n / factor));
Ok((minimal_be(p), minimal_be(q)))
}
fn minimal_be(value: u64) -> Vec<u8> {
let bytes = value.to_be_bytes();
let skip = bytes.iter().take_while(|&&b| b == 0).count();
bytes[skip..].to_vec()
}
fn find_factor(n: u64) -> Option<u64> {
if n < 4 {
return None;
}
if n.is_multiple_of(2) {
return Some(2);
}
if is_prime(n) {
return None;
}
(1..u64::MAX).find_map(|c| pollard_brent(n, c))
}
fn mul_mod(a: u64, b: u64, m: u64) -> u64 {
let product = u128::from(a) * u128::from(b) % u128::from(m);
u64::try_from(product).unwrap_or_default()
}
fn pow_mod(mut base: u64, mut exp: u64, m: u64) -> u64 {
let mut result = 1 % m;
base %= m;
while exp > 0 {
if exp & 1 == 1 {
result = mul_mod(result, base, m);
}
base = mul_mod(base, base, m);
exp >>= 1;
}
result
}
fn is_prime(n: u64) -> bool {
const BASES: [u64; 12] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
let s = (n - 1).trailing_zeros();
let d = (n - 1) >> s;
BASES.iter().all(|&a| {
if a % n == 0 {
return true;
}
let mut x = pow_mod(a, d, n);
if x == 1 || x == n - 1 {
return true;
}
for _ in 1..s {
x = mul_mod(x, x, n);
if x == n - 1 {
return true;
}
}
false
})
}
fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
(a, b) = (b, a % b);
}
a
}
fn pollard_brent(n: u64, increment: u64) -> Option<u64> {
const BATCH: u64 = 128;
let step = |value: u64| {
let sum = u128::from(mul_mod(value, value, n)) + u128::from(increment);
u64::try_from(sum % u128::from(n)).unwrap_or_default()
};
let (mut anchor, mut walker, mut batch_start) = (2u64, 2u64, 2u64);
let (mut divisor, mut product, mut span) = (1u64, 1u64, 1u64);
while divisor == 1 {
anchor = walker;
for _ in 0..span {
walker = step(walker);
}
let mut done = 0;
while done < span && divisor == 1 {
batch_start = walker;
for _ in 0..BATCH.min(span - done) {
walker = step(walker);
product = mul_mod(product, anchor.abs_diff(walker), n);
}
divisor = gcd(product, n);
done += BATCH;
}
span = span.saturating_mul(2);
}
if divisor == n {
loop {
batch_start = step(batch_start);
divisor = gcd(anchor.abs_diff(batch_start), n);
if divisor > 1 {
break;
}
}
}
(divisor != n).then_some(divisor)
}
pub fn random_fill(buf: &mut [u8]) -> Result<()> {
getrandom::fill(buf).map_err(|e| Error::Io(io::Error::other(e)))
}