1use sha1::{Digest as Sha1Digest, Sha1};
9use sha2::Sha512;
10
11use crate::{AUTH_KEY_SIZE, Error, Result};
12
13pub const LOCAL_ENCRYPT_SALT_SIZE: usize = 32;
15
16pub const AES_KEY_SIZE: usize = 32;
18
19pub const AES_BLOCK_SIZE: usize = 16;
21
22const PBKDF2_ITERATIONS_WITH_PASSCODE: u32 = 100_000;
24
25const PBKDF2_ITERATIONS_NO_PASSCODE: u32 = 1;
27
28#[derive(Clone, Copy)]
30pub struct AuthKey {
31 data: [u8; AUTH_KEY_SIZE],
32}
33
34impl AuthKey {
35 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
37 if bytes.len() != AUTH_KEY_SIZE {
38 return Err(Error::invalid_format(format!(
39 "auth key must be {} bytes, got {}",
40 AUTH_KEY_SIZE,
41 bytes.len()
42 )));
43 }
44
45 let mut data = [0u8; AUTH_KEY_SIZE];
46 data.copy_from_slice(bytes);
47 Ok(Self { data })
48 }
49
50 #[must_use]
52 pub const fn as_bytes(&self) -> &[u8; AUTH_KEY_SIZE] {
53 &self.data
54 }
55}
56
57impl std::fmt::Debug for AuthKey {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 f.debug_struct("AuthKey").field("len", &self.data.len()).finish()
61 }
62}
63
64#[must_use]
71pub fn create_local_key(salt: &[u8], passcode: &[u8]) -> AuthKey {
72 let mut key_data = [0u8; AUTH_KEY_SIZE];
73
74 let mut hasher = Sha512::new();
76 hasher.update(salt);
77 hasher.update(passcode);
78 hasher.update(salt);
79 let hash_key = hasher.finalize();
80
81 let iterations = if passcode.is_empty() {
83 PBKDF2_ITERATIONS_NO_PASSCODE
84 } else {
85 PBKDF2_ITERATIONS_WITH_PASSCODE
86 };
87
88 pbkdf2::pbkdf2_hmac::<Sha512>(&hash_key, salt, iterations, &mut key_data);
90
91 AuthKey { data: key_data }
92}
93
94pub fn decrypt_local(encrypted: &[u8], key: &AuthKey) -> Result<Vec<u8>> {
105 if encrypted.len() <= AES_BLOCK_SIZE {
106 return Err(Error::invalid_format("encrypted data too short"));
107 }
108
109 if !encrypted.len().is_multiple_of(AES_BLOCK_SIZE) {
110 return Err(Error::invalid_format("encrypted data length must be multiple of 16"));
111 }
112
113 let encrypted_key = &encrypted[0..16];
115 let encrypted_data = &encrypted[16..];
116
117 tracing::debug!(
118 "decrypt_local: encrypted len={}, msg_key={:02x?}",
119 encrypted.len(),
120 encrypted_key
121 );
122
123 let (aes_key, aes_iv) = prepare_aes_oldmtp(key.as_bytes(), encrypted_key);
125
126 let decrypted = ige_decrypt(&aes_key, &aes_iv, encrypted_data);
128
129 let check_hash = &sha1_hash(&decrypted)[0..16];
131
132 tracing::debug!("SHA1 check: expected={:02x?}, computed={:02x?}", encrypted_key, check_hash);
133
134 if check_hash != encrypted_key {
135 return Err(Error::ChecksumMismatch);
136 }
137
138 if decrypted.len() < 4 {
140 return Err(Error::DecryptionFailed);
141 }
142
143 let original_len =
144 u32::from_le_bytes([decrypted[0], decrypted[1], decrypted[2], decrypted[3]]) as usize;
145
146 let full_len = encrypted_data.len();
147
148 if original_len > decrypted.len()
150 || original_len <= full_len.saturating_sub(16)
151 || original_len < 4
152 {
153 return Err(Error::invalid_format(format!(
154 "invalid decrypted length: {}, full_len: {}, decrypted size: {}",
155 original_len,
156 full_len,
157 decrypted.len()
158 )));
159 }
160
161 Ok(decrypted[4..original_len].to_vec())
163}
164
165fn prepare_aes_oldmtp(auth_key: &[u8], msg_key: &[u8]) -> ([u8; AES_KEY_SIZE], [u8; AES_KEY_SIZE]) {
170 let x: usize = 8;
172
173 let sha1_a = sha1_hash_2(msg_key, &auth_key[x..x + 32]);
175
176 let sha1_b = sha1_hash_3(&auth_key[32 + x..48 + x], msg_key, &auth_key[48 + x..64 + x]);
178
179 let sha1_c = sha1_hash_2(&auth_key[64 + x..96 + x], msg_key);
181
182 let sha1_d = sha1_hash_2(msg_key, &auth_key[96 + x..128 + x]);
184
185 let mut key = [0u8; AES_KEY_SIZE];
186 let mut iv = [0u8; AES_KEY_SIZE];
187
188 key[0..8].copy_from_slice(&sha1_a[0..8]);
190 key[8..20].copy_from_slice(&sha1_b[8..20]);
191 key[20..32].copy_from_slice(&sha1_c[4..16]);
192
193 iv[0..12].copy_from_slice(&sha1_a[8..20]);
195 iv[12..20].copy_from_slice(&sha1_b[0..8]);
196 iv[20..24].copy_from_slice(&sha1_c[16..20]);
197 iv[24..32].copy_from_slice(&sha1_d[0..8]);
198
199 (key, iv)
200}
201
202fn ige_decrypt(key: &[u8; 32], iv: &[u8; 32], data: &[u8]) -> Vec<u8> {
204 use grammers_crypto::aes::ige_decrypt as grammers_ige_decrypt;
205
206 grammers_ige_decrypt(data, key, iv)
207}
208
209fn sha1_hash(data: &[u8]) -> [u8; 20] {
211 let mut hasher = Sha1::new();
212 hasher.update(data);
213 hasher.finalize().into()
214}
215
216fn sha1_hash_2(a: &[u8], b: &[u8]) -> [u8; 20] {
218 let mut hasher = Sha1::new();
219 hasher.update(a);
220 hasher.update(b);
221 hasher.finalize().into()
222}
223
224fn sha1_hash_3(a: &[u8], b: &[u8], c: &[u8]) -> [u8; 20] {
226 let mut hasher = Sha1::new();
227 hasher.update(a);
228 hasher.update(b);
229 hasher.update(c);
230 hasher.finalize().into()
231}
232
233#[cfg(test)]
234#[allow(
235 clippy::unwrap_used,
236 clippy::expect_used,
237 clippy::assertions_on_result_states,
238 reason = "test assertions with controlled inputs"
239)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_create_local_key_no_passcode() {
245 let salt = [0u8; LOCAL_ENCRYPT_SALT_SIZE];
246 let passcode = b"";
247
248 let key = create_local_key(&salt, passcode);
249 assert_eq!(key.as_bytes().len(), AUTH_KEY_SIZE);
250 }
251
252 #[test]
253 fn test_create_local_key_with_passcode() {
254 let salt = [0u8; LOCAL_ENCRYPT_SALT_SIZE];
255 let passcode = b"test";
256
257 let key = create_local_key(&salt, passcode);
258 assert_eq!(key.as_bytes().len(), AUTH_KEY_SIZE);
259
260 let key2 = create_local_key(&salt, passcode);
262 assert_eq!(key.as_bytes(), key2.as_bytes());
263 }
264
265 #[test]
266 fn test_auth_key_from_bytes() {
267 let bytes = [0xAB; AUTH_KEY_SIZE];
268 let key = AuthKey::from_bytes(&bytes).unwrap();
269 assert_eq!(key.as_bytes(), &bytes);
270 }
271
272 #[test]
273 fn test_auth_key_wrong_size() {
274 let bytes = [0u8; 100];
275 assert!(AuthKey::from_bytes(&bytes).is_err());
276 }
277
278 #[test]
279 fn test_sha1_hash() {
280 let data = b"hello";
281 let hash = sha1_hash(data);
282 assert_eq!(hex::encode(hash), "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
284 }
285}