From 557ec7023751d54a99548342be4dd314a95635b3 Mon Sep 17 00:00:00 2001 From: Lonami Date: Sun, 28 Aug 2016 19:26:06 +0200 Subject: [PATCH] Added authenticator --- README.md | 1 + network/authenticator.py | 207 +++++++++++++++++++++++++++++++++++++++ utils/auth_key.py | 30 ++++++ utils/factorizator.py | 46 +++++++++ utils/helpers.py | 37 +++++++ 5 files changed, 321 insertions(+) create mode 100644 network/authenticator.py create mode 100644 utils/auth_key.py create mode 100644 utils/factorizator.py diff --git a/README.md b/README.md index e73961e1..3798792e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ to make them entirely different. This project requires the following Python modules, which can be installed by issuing `sudo -H pip install ` on a Linux terminal: - `pyaes` ([GitHub](https://github.com/ricmoo/pyaes), [package index](https://pypi.python.org/pypi/pyaes)) +- `rsa` ([GitHub](https://github.com/sybrenstuvel/python-rsa), [package index](https://pypi.python.org/pypi/rsa)) ### We need your help! As of now, the project is fully **untested** and with many pending things to do. If you know both Python and C#, please don't diff --git a/network/authenticator.py b/network/authenticator.py new file mode 100644 index 00000000..d598300c --- /dev/null +++ b/network/authenticator.py @@ -0,0 +1,207 @@ +# This file is based on TLSharp +# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Auth/Authenticator.cs +# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Auth/Step1_PQRequest.cs +# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Auth/Step2_DHExchange.cs +# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Auth/Step3_CompleteDHExchange.cs + +from network.mtproto_plain_sender import MtProtoPlainSender +from utils.binary_writer import BinaryWriter +from utils.binary_reader import BinaryReader +from utils.factorizator import Factorizator +from utils.auth_key import AuthKey +from hashlib import sha1 +import utils.helpers as utils +import time +import pyaes +import rsa + + +def do_authentication(transport): + sender = MtProtoPlainSender(transport) + + # Step 1 sending: PQ Request + nonce = utils.generate_random_bytes(16) + with BinaryWriter() as writer: + writer.write_int(0x60469778) # Constructor number + writer.write(nonce) + sender.send(writer.get_bytes()) + + # Step 1 response: PQ Request + with BinaryReader(sender.receive()) as reader: + response_code = reader.read_int() + if response_code != 0x05162463: + raise AssertionError('Invalid response code: {}'.format(hex(response_code))) + + nonce_from_server = reader.read(16) + if nonce_from_server != nonce: + raise AssertionError('Invalid nonce from server') + + server_nonce = reader.read(16) + + pq_bytes = reader.tgread_bytes() + pq = int.from_bytes(pq_bytes, byteorder='big') + + vector_id = reader.read_int() + if vector_id != 0x1cb5c415: + raise AssertionError('Invalid vector constructor ID: {}'.format(hex(response_code))) + + fingerprints = [] + fingerprint_count = reader.read_int() + for _ in range(fingerprint_count): + fingerprints.append(reader.read(8)) + + # Step 2 sending: DH Exchange + new_nonce = utils.generate_random_bytes(32) + p, q = Factorizator.factorize(pq) + with BinaryWriter() as pq_inner_data_writer: + pq_inner_data_writer.write_int(0x83c95aec) # PQ Inner Data + pq_inner_data_writer.tgwrite_bytes(pq_bytes) + # TODO, CHANGE TO_BYTE_ARRAY TO PACK(...); But idk size. And down too. + pq_inner_data_writer.tgwrite_bytes(min(p, q).to_byte_array_unsigned()) + pq_inner_data_writer.tgwrite_bytes(max(p, q).to_byte_array_unsigned()) + pq_inner_data_writer.write(nonce) + pq_inner_data_writer.write(server_nonce) + pq_inner_data_writer.write(new_nonce) + + cipher_text = None + for fingerprint in fingerprints: + cipher_text = rsa.encrypt(str(fingerprint, encoding='utf-8').replace('-', ''), + pq_inner_data_writer.get_bytes()) + + if cipher_text is not None: + target_fingerprint = fingerprint + break + + if cipher_text is None: + raise AssertionError('Could not find a valid key for fingerprints: {}' + .format(', '.join([str(f, encoding='utf-8') for f in fingerprints]))) + + with BinaryWriter() as req_dh_params_writer: + req_dh_params_writer.write_int(0xd712e4be) # Req DH Params + req_dh_params_writer.write(nonce) + req_dh_params_writer.write(server_nonce) + req_dh_params_writer.tgwrite_bytes(min(p, q).to_byte_array_unsigned()) + req_dh_params_writer.tgwrite_bytes(max(p, q).to_byte_array_unsigned()) + req_dh_params_writer.Write(target_fingerprint); + req_dh_params_writer.tgwrite_bytes(cipher_text) + + req_dh_params_bytes = req_dh_params_writer.get_bytes(); + + # Step 2 response: DH Exchange + with BinaryReader(sender.receive()) as reader: + response_code = reader.read_int() + + if response_code == 0x79cb045d: + raise AssertionError('Server DH params fail: TODO') + + if response_code != 0xd0e8075c: + raise AssertionError('Invalid response code: {}'.format(hex(response_code))) + + nonce_from_server = reader.read(16) + # TODO: + """ + if nonce_from_server != nonce: + print('Invalid nonce from server') + return None + """ + + server_nonce_from_server = reader.read(16) + # TODO: + """ + if server_nonce_from_server != server_nonce: + print('Invalid server nonce from server') + return None + """ + encrypted_answer = reader.tgread_bytes() + + # Step 3 sending: Complete DH Exchange + key, iv = utils.generate_key_data_from_nonces(server_nonce, new_nonce) + aes = pyaes.AESModeOfOperationCFB(key, iv, 16) + plain_text_answer = aes.decrypt(encrypted_answer) + with BinaryReader(plain_text_answer.encode('ascii')) as dh_inner_data_reader: + hashsum = dh_inner_data_reader.read(20) + code = dh_inner_data_reader.read_int(signed=False) + if code != 0xb5890dba: + raise AssertionError('Invalid DH Inner Data code: {}'.format(code)) + + nonce_from_server1 = dh_inner_data_reader.read(16) + if nonce_from_server1 != nonce: + raise AssertionError('Invalid nonce in encrypted answer') + + server_nonce_from_server1 = dh_inner_data_reader.read(16) + if server_nonce_from_server1 != server_nonce: + raise AssertionError('Invalid server nonce in encrypted answer') + + g = dh_inner_data_reader.read_int() + dh_prime = int.from_bytes(dh_inner_data_reader.tgread_bytes(), byteorder='big') + ga = int.from_bytes(dh_inner_data_reader.tgread_bytes(), byteorder='big') + + server_time = dh_inner_data_reader.read_int() + time_offset = server_time - int(time.time()) + + b = int.from_bytes(utils.generate_random_bytes(2048), byteorder='big') + gb = pow(g, b, dh_prime) # BigInteger.ValueOf(g).ModPow(b, dhPrime) + gab = pow(ga, b, dh_prime) # ga.ModPow(b, dhPrime) + + # Prepare client DH Inner Data + with BinaryWriter() as client_dh_inner_data_writer: + client_dh_inner_data_writer.write_int(0x6643b654) # Client DH Inner Data + client_dh_inner_data_writer.write(nonce) + client_dh_inner_data_writer.write(server_nonce) + client_dh_inner_data_writer.write_long(0) # TODO retry_id + client_dh_inner_data_writer.tgwrite_bytes(gb.to_byte_array_unsigned()) + + with BinaryWriter() as client_dh_inner_data_with_hash_writer: + client_dh_inner_data_with_hash_writer.write(sha1(client_dh_inner_data_writer.get_bytes())) + client_dh_inner_data_with_hash_writer.write(client_dh_inner_data_writer.get_bytes()) + client_dh_inner_data_bytes = client_dh_inner_data_with_hash_writer.get_bytes() + + # Encryption + client_dh_inner_data_encrypted_bytes = aes.encrypt(client_dh_inner_data_bytes) + + # Prepare Set client DH params + with BinaryWriter() as set_client_dh_params_writer: + set_client_dh_params_writer.write_int(0xf5045f1f) + set_client_dh_params_writer.write(nonce) + set_client_dh_params_writer.write(server_nonce) + set_client_dh_params_writer.tgwrite_bytes(client_dh_inner_data_encrypted_bytes) + + set_client_dh_params_bytes = set_client_dh_params_writer.get_bytes() + sender.send(set_client_dh_params_bytes) + + # Step 3 response: Complete DH Exchange + with BinaryReader(sender.receive()) as reader: + code = reader.read_int(signed=False) + if code == 0x3bcbf734: + nonce_from_server = reader.read(16) + # TODO: + """ + if nonce_from_server != nonce: + print('Invalid nonce from server') + return None + """ + + server_nonce_from_server = reader.read(16) + # TODO: + """ + if server_nonce_from_server != server_nonce: + print('Invalid server nonce from server') + return None + """ + new_nonce_hash1 = reader.read(16) + auth_key = AuthKey(gab) + + new_nonce_hash_calculated = auth_key.calc_new_nonce_hash(new_nonce, 1) + if new_nonce_hash1 != new_nonce_hash_calculated: + raise AssertionError('Invalid new nonce hash') + + return auth_key, time_offset + + elif code == 0x46dc1fb9: # DH Gen Retry + raise NotImplementedError('dh_gen_retry') + + elif code == 0xa69dae02: # DH Gen Fail + raise NotImplementedError('dh_gen_fail') + + else: + raise AssertionError('DH Gen unknown: {}'.format(hex(code))) diff --git a/utils/auth_key.py b/utils/auth_key.py new file mode 100644 index 00000000..88b1dcbb --- /dev/null +++ b/utils/auth_key.py @@ -0,0 +1,30 @@ +# This file is based on TLSharp +# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/MTProto/Crypto/AuthKey.cs +from hashlib import sha1 +from utils.binary_writer import BinaryWriter +from utils.binary_reader import BinaryReader + + +class AuthKey: + def __init__(self, gab=None, data=None): + if gab: + self.key = gab.to_byte_array_unsigned() + elif data: + self.key = data + else: + raise AssertionError('Either a gab integer or data bytes array must be provided') + + with BinaryReader(sha1(self.key)) as reader: + self.aux_hash = reader.read_long(signed=False) + reader.read(4) + self.key_id = reader.read_long(signed=False) + + + def calc_new_nonce_hash(self, new_nonce, number): + with BinaryWriter() as writer: + writer.write(new_nonce) + writer.write_byte(number) + writer.write_long(self.aux_hash, signed=False) + + new_nonce_hash = sha1(writer.get_bytes())[4:20] + return new_nonce_hash diff --git a/utils/factorizator.py b/utils/factorizator.py new file mode 100644 index 00000000..6ab78fc9 --- /dev/null +++ b/utils/factorizator.py @@ -0,0 +1,46 @@ +# This file is based on TLSharp +# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/MTProto/Crypto/Factorizator.cs +from random import randint +from math import gcd + + +class Factorizator: + @staticmethod + def find_small_multiplier_lopatin(what): + g = 0 + for i in range(3): + q = (randint(0, 127) & 15) + 17 + x = randint(1000000000) + 1 + y = x + lim = 1 << (i + 18) + for j in range(1, lim): + a, b, c = x, x, q + while b != 0: + if (b & 1) != 0: + c += a + if c >= what: + c -= what + a += a + if a >= what: + a -= what + b >>= 1 + + x = c + z = y - x if x < y else x - y + g = gcd(z, what) + if g != 1: + break + + if (j & (j - 1)) == 0: + y = x + + if g > 1: + break + + p = what // g + return min(p, g) + + @staticmethod + def factorize(pq): + divisor = Factorizator.find_small_multiplier_lopatin(pq) + return divisor, pq // divisor diff --git a/utils/helpers.py b/utils/helpers.py index 29a88876..68e6c910 100644 --- a/utils/helpers.py +++ b/utils/helpers.py @@ -59,3 +59,40 @@ def calc_msg_key_offset(data, offset, limit): # TODO untested, may not be offset like this # In the original code it was as parameters for the sha function, not slicing the array return sha1(data[offset:offset + limit])[4:20] + + +def generate_key_data_from_nonces(serverNonce, newNonce): + # TODO unsure that this works + nonces = [0] * 48 + + nonces[00:32] = newNonce + nonces[32:48] = serverNonce + hash1 = hash(bytes(nonces)) + + nonces[00:16] = serverNonce + nonces[16:32] = newNonce + hash2 = hash(bytes(nonces)) + + nonces = [0] * 64 + nonces[00:32] = newNonce + nonces[32:64] = newNonce + hash2 = hash(bytes(nonces)) + + with BinaryWriter() as keyBuffer: + with BinaryWriter() as ivBuffer: + """ + using (var keyBuffer = new MemoryStream(32)) + using (var ivBuffer = new MemoryStream(32)) + { + keyBuffer.Write(hash1, 0, hash1.Length); + keyBuffer.Write(hash2, 0, 12); + + ivBuffer.Write(hash2, 12, 8); + ivBuffer.Write(hash3, 0, hash3.Length); + ivBuffer.Write(newNonce, 0, 4); + + return new AESKeyData(keyBuffer.ToArray(), ivBuffer.ToArray()); + } + """ + # TODO implement + raise NotImplementedError()