def prepare_key_to_read(auth_key: bytes, msg_key: bytes): sha1_a = sha1(msg_key + auth_key[8:40]) sha1_b = sha1(auth_key[40:56] + msg_key + auth_key[56:72]) sha1_c = sha1(auth_key[72:104] + msg_key) sha1_d = sha1(msg_key + auth_key[104:136]) aes_key = sha1_a[:8] + sha1_b[8:20] + sha1_c[4:16] aes_iv = sha1_a[8:20] + sha1_b[:8] + sha1_c[16:20] + sha1_d[:8] aes = AesIge(aes_key, aes_iv) return aes
def prepare_key_to_write(auth_key: bytes, msg_key: bytes): sha1_a = sha1(msg_key + auth_key[:32]) sha1_b = sha1(auth_key[32:48] + msg_key + auth_key[48:64]) sha1_c = sha1(auth_key[64:96] + msg_key) sha1_d = sha1(msg_key + auth_key[96:128]) aes_key = sha1_a[:8] + sha1_b[8:20] + sha1_c[4:16] aes_iv = sha1_a[8:20] + sha1_b[:8] + sha1_c[16:20] + sha1_d[:8] aes = AesIge(aes_key, aes_iv) return aes
async def _create_auth_key(self): generate_b = self._loop.create_task( self._in_thread(secrets.randbits, 2048)) nonce = await self._in_thread(secrets.token_bytes, 16) await self._write_unencrypted_message(_cons='req_pq', nonce=nonce) respq = (await self._read_unencrypted_message()).body # check if we have got the right public key if self._public_rsa_key.fingerprint not in respq.server_public_key_fingerprints: raise ValueError("Our certificate is not supported by the server") server_nonce = respq.server_nonce pq = int.from_bytes(respq.pq, 'big', signed=False) new_nonce, (p, q) = await asyncio.gather( self._in_thread(secrets.token_bytes, 32), self._in_thread(primes.factorize, pq)) p_string = to_bytes(p) q_string = to_bytes(q) # request Diffie–Hellman parameters p_q_inner_data = self._scheme.boxed( _cons='p_q_inner_data', pq=respq.pq, p=p_string, q=q_string, nonce=nonce, server_nonce=server_nonce, new_nonce=new_nonce).get_flat_bytes() await self._write_unencrypted_message( _cons='req_DH_params', nonce=nonce, server_nonce=server_nonce, p=p_string, q=q_string, public_key_fingerprint=self._public_rsa_key.fingerprint, encrypted_data=self._public_rsa_key.encrypt_with_hash( p_q_inner_data)) params = (await self._read_unencrypted_message()).body if params != 'server_DH_params_ok' or params.nonce != nonce or params.server_nonce != server_nonce: raise RuntimeError("Diffie–Hellman exchange failed: `%r`", params) # https://core.telegram.org/mtproto/auth_key#presenting-proof-of-work-server-authentication tmp_aes_key = sha1(new_nonce + server_nonce) + sha1(server_nonce + new_nonce)[:12] tmp_aes_iv = sha1(server_nonce + new_nonce)[12:] + sha1(new_nonce + new_nonce) + new_nonce[:4] tmp_aes = encryption.AesIge(tmp_aes_key, tmp_aes_iv) answer, b = await asyncio.gather( self._in_thread(tmp_aes.decrypt_with_hash, params.encrypted_answer), generate_b) params2 = await self._scheme.read_from_string(answer) # FIXME! save server_time # print(params2.server_time) # print(time.time()) if params2 != 'server_DH_inner_data': raise RuntimeError("Diffie–Hellman exchange failed: `%r`", params2) dh_prime = int.from_bytes(params2.dh_prime, 'big') g = params2.g g_a = int.from_bytes(params2.g_a, 'big') if (params2.nonce != nonce or params2.server_nonce != server_nonce or not primes.is_safe_dh_prime(g, dh_prime)): raise RuntimeError("Diffie–Hellman exchange failed: `%r`", params2) g_b, self._auth_key = map( to_bytes, await asyncio.gather(self._in_thread(pow, g, b, dh_prime), self._in_thread(pow, g_a, b, dh_prime))) self._set_auth_key_id() self._server_salt = int.from_bytes(xor(new_nonce[:8], server_nonce[:8]), 'little', signed=True) client_DH_inner_data = self._scheme.boxed(_cons='client_DH_inner_data', nonce=nonce, server_nonce=server_nonce, retry_id=0, g_b=g_b).get_flat_bytes() tmp_aes = encryption.AesIge(tmp_aes_key, tmp_aes_iv) await self._write_unencrypted_message(_cons='set_client_DH_params', nonce=nonce, server_nonce=server_nonce, encrypted_data=await self._in_thread( tmp_aes.encrypt_with_hash, client_DH_inner_data)) params3 = (await self._read_unencrypted_message()).body if params3 != 'dh_gen_ok': raise RuntimeError("Diffie–Hellman exchange failed: `%r`", params3)
def _set_auth_key_id(self): self._auth_key_id = sha1(self._auth_key)[-8:]
def encrypt_with_hash(self, plain: bytes) -> bytes: return self.encrypt(sha1(plain) + plain)