diff --git a/crypt.py b/crypt.py index 26200ccc..870cff47 100644 --- a/crypt.py +++ b/crypt.py @@ -10,62 +10,58 @@ from __future__ import print_function from Crypto.Util.strxor import strxor from Crypto.Cipher import AES +# AES 256 IGE part -class IGE: - def __init__(self, key, iv): - if len(key) != 32: - raise ValueError("key must be 32 bytes long (was " + - str(len(key)) + " bytes)") - if len(iv) != 32: - raise ValueError("iv must be 32 bytes long (was " + - str(len(iv)) + " bytes)") +def ige_encrypt(message, key, iv): + return _ige(message, key, iv, operation="encrypt") - self.key = key - self.iv = iv +def ige_decrypt(message, key, iv): + return _ige(message, key, iv, operation="decrypt") - self.cipher = AES.new(key, AES.MODE_ECB, iv) +def _ige(message, key, iv, operation="decrypt"): + """Given a key, given an iv, and message + do whatever operation asked in the operation field. + Operation will be checked for: "decrypt" and "encrypt" strings. + Returns the message encrypted/decrypted. + message must be a multiple by 16 bytes (for division in 16 byte blocks) + key must be 32 byte + iv must be 32 byte (it's not internally used in AES 256 ECB, but it's + needed for IGE)""" - def encrypt(self, message): - return self._ige(message, operation="encrypt") + if len(key) != 32: + raise ValueError("key must be 32 bytes long (was " + + str(len(key)) + " bytes)") + if len(iv) != 32: + raise ValueError("iv must be 32 bytes long (was " + + str(len(iv)) + " bytes)") - def decrypt(self, message): - return self._ige(message, operation="decrypt") + cipher = AES.new(key, AES.MODE_ECB, iv) + blocksize = cipher.block_size - def _ige(self, message, operation="decrypt"): - """Given a key, given an iv, and message - do whatever operation asked in the operation field. - Operation will be checked for: "decrypt" and "encrypt" strings. - Returns the message encrypted/decrypted. - message must be a multiple by 16 bytes (for division in 16 byte blocks) - key must be 32 byte - iv must be 32 byte (it's not internally used in AES 256 ECB, but it's - needed for IGE)""" - blocksize = self.cipher.block_size + if len(message) % blocksize != 0: + raise ValueError("message must be a multiple of 16 bytes (try adding " + + str(16 - len(message) % 16) + " bytes of padding)") - if len(message) % blocksize != 0: - raise ValueError("message must be a multiple of 16 bytes (try adding " + - str(16 - len(message) % 16) + " bytes of padding)") + ivp = iv[0:blocksize] + ivp2 = iv[blocksize:] - ivp = self.iv[0:blocksize] - ivp2 = self.iv[blocksize:] + ciphered = bytearray() - ciphered = bytearray() - - for i in range(0, len(message), blocksize): - indata = message[i:i+blocksize] - if operation == "decrypt": - xored = strxor(indata, ivp2) - decrypt_xored = self.cipher.decrypt(xored) - outdata = strxor(decrypt_xored, ivp) - ivp = indata - ivp2 = outdata - elif operation == "encrypt": - xored = strxor(indata, ivp) - encrypt_xored = self.cipher.encrypt(xored) - outdata = strxor(encrypt_xored, ivp2) - ivp = outdata - ivp2 = indata - else: - raise ValueError("operation must be either 'decrypt' or 'encrypt'") - ciphered.extend(outdata) - return ciphered \ No newline at end of file + for i in range(0, len(message), blocksize): + indata = message[i:i+blocksize] + if operation == "decrypt": + xored = strxor(indata, ivp2) + decrypt_xored = cipher.decrypt(xored) + outdata = strxor(decrypt_xored, ivp) + ivp = indata + ivp2 = outdata + elif operation == "encrypt": + xored = strxor(indata, ivp) + encrypt_xored = cipher.encrypt(xored) + outdata = strxor(encrypt_xored, ivp2) + ivp = outdata + ivp2 = indata + else: + raise ValueError("operation must be either 'decrypt' or 'encrypt'") + ciphered.extend(outdata) + return ciphered \ No newline at end of file diff --git a/mtproto.py b/mtproto.py index 56b0c9ad..45af6e41 100644 --- a/mtproto.py +++ b/mtproto.py @@ -19,6 +19,7 @@ import struct from Crypto.Hash import SHA from Crypto.PublicKey import RSA from Crypto.Util.strxor import strxor +from Crypto.Util.number import long_to_bytes, bytes_to_long # local modules import crypt @@ -36,15 +37,13 @@ def vis(bs): bs = bytearray(bs) symbols_in_one_line = 8 n = len(bs) // symbols_in_one_line + i = 0 for i in range(n): print(str(i*symbols_in_one_line)+" | "+" ".join(["%02X" % b for b in bs[i*symbols_in_one_line:(i+1)*symbols_in_one_line]])) # for every 8 symbols line if not len(bs) % symbols_in_one_line == 0: print(str((i+1)*symbols_in_one_line)+" | "+" ".join(["%02X" % b for b in bs[(i+1)*symbols_in_one_line:]])+"\n") # for last line - - - class Session: """ Manages TCP Transport. encryption and message frames """ def __init__(self, ip, port, auth_key=None): @@ -73,8 +72,10 @@ class Session: # Message id: https://core.telegram.org/mtproto/description#message-identifier-msg-id # http://stackoverflow.com/questions/8777753/converting-datetime-date-to-utc-timestamp-in-python # to make it work in py2 and py3 (py3 has the timestamp() method but py2 doesnt) - curr_timestamp = (datetime.utcfromtimestamp(time()) - datetime(1970, 1, 1)).total_seconds() - msg_id = int(curr_timestamp*2**30)*4 + #curr_timestamp = (datetime.utcfromtimestamp(time()) - datetime(1970, 1, 1)).total_seconds() + + msg_id = int(time()*2**30)*4 + #msg_id = int(datetime.utcnow().timestamp()*2**30)*4 return (b'\x00\x00\x00\x00\x00\x00\x00\x00' + @@ -101,8 +102,8 @@ class Session: self.sock.send(step2) self.number += 1 # Sending message visualisation to console -# print('>>') - # vis(step2) + # print('>>') + # vis(step2) def recv_message(self): """ @@ -141,7 +142,7 @@ class Session: def create_auth_key(self): nonce = os.urandom(16) - print("Requesting PQ") + print("Requesting pq") ResPQ = self.method_call('req_pq', nonce=nonce) server_nonce = ResPQ['server_nonce'] @@ -149,22 +150,18 @@ class Session: # TODO: selecting RSA public key based on this fingerprint public_key_fingerprint = ResPQ['server_public_key_fingerprints'][0] - PQ_bytes = ResPQ['pq'] + pq_bytes = ResPQ['pq'] # TODO: from_bytes here - PQ = struct.unpack('>q', PQ_bytes)[0] - [p, q] = prime.primefactors(PQ) + pq = struct.unpack('>q', pq_bytes)[0] + [p, q] = prime.primefactors(pq) if p > q: (p, q) = (q, p) - assert p*q == PQ and p < q - - print("Factorization %d = %d * %d" % (PQ, p, q)) - - # TODO: to_bytes here - P_bytes = struct.pack('>i', p) - Q_bytes = struct.pack('>i', q) + assert p*q == pq and p < q + print("Factorization %d = %d * %d" % (pq, p, q)) + p_bytes = long_to_bytes(p) + q_bytes = long_to_bytes(q) f = open(os.path.join(os.path.dirname(__file__), "rsa.pub")) - key = RSA.importKey(f.read()) z = io.BytesIO() @@ -172,12 +169,12 @@ class Session: new_nonce = os.urandom(32) TL.serialize_obj(z, 'p_q_inner_data', - pq=PQ_bytes, - p=P_bytes, - q=Q_bytes, - nonce=nonce, - server_nonce=server_nonce, - new_nonce=new_nonce) + pq=pq_bytes, + p=p_bytes, + q=q_bytes, + nonce=nonce, + server_nonce=server_nonce, + new_nonce=new_nonce) data = z.getvalue() sha_digest = SHA.new(data).digest() @@ -187,12 +184,12 @@ class Session: print("Starting Diffie Hellman key exchange") server_DH_params = self.method_call('req_DH_params', - nonce=nonce, # 16 bytes - server_nonce=server_nonce, - p=P_bytes, - q=Q_bytes, - public_key_fingerprint=public_key_fingerprint, - encrypted_data=encrypted_data) + nonce=nonce, # 16 bytes + server_nonce=server_nonce, + p=p_bytes, + q=q_bytes, + public_key_fingerprint=public_key_fingerprint, + encrypted_data=encrypted_data) assert nonce == server_DH_params['nonce'] assert server_nonce == server_DH_params['server_nonce'] @@ -201,9 +198,7 @@ class Session: tmp_aes_key = SHA.new(new_nonce + server_nonce).digest() + SHA.new(server_nonce + new_nonce).digest()[0:12] tmp_aes_iv = SHA.new(server_nonce + new_nonce).digest()[12:20] + SHA.new(new_nonce + new_nonce).digest() + new_nonce[0:4] - crypter = crypt.IGE(tmp_aes_key, tmp_aes_iv) - - answer_with_hash = crypter.decrypt(encrypted_answer) + answer_with_hash = crypt.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv) answer_hash = answer_with_hash[:20] answer = answer_with_hash[20:] @@ -219,54 +214,57 @@ class Session: self.timedelta = server_time - time() print("Server-client time delta = %.1f s" % self.timedelta) - dh_prime = int.from_bytes(dh_prime_str,'big') - g_a = int.from_bytes(g_a_str,'big') + dh_prime = bytes_to_long(dh_prime_str) + g_a = bytes_to_long(g_a_str) assert prime.isprime(dh_prime) retry_id = 0 - while retry_id<25: - try: - b_str = os.urandom(256) - b = int.from_bytes(b_str,'big') - g_b = pow(g,b,dh_prime) + b_str = os.urandom(256) + b = bytes_to_long(b_str) + g_b = pow(g, b, dh_prime) - g_b_str = int.to_bytes(g_b, g_b.bit_length() // 8 + 1, 'big') + g_b_str = long_to_bytes(g_b) - z = io.BytesIO() - TL.serialize_obj(z, 'client_DH_inner_data', - nonce=nonce, - server_nonce=server_nonce, - retry_id=retry_id, - g_b=g_b_str) - data = z.getvalue() - data_with_sha = SHA.new(data).digest()+data - data_with_sha_padded = data_with_sha + os.urandom(-len(data_with_sha) % 16) - encrypted_data = crypter.encrypt(data_with_sha_padded) + z = io.BytesIO() + TL.serialize_obj(z, 'client_DH_inner_data', + nonce=nonce, + server_nonce=server_nonce, + retry_id=retry_id, + g_b=g_b_str) + data = z.getvalue() + data_with_sha = SHA.new(data).digest()+data + data_with_sha_padded = data_with_sha + os.urandom(-len(data_with_sha) % 16) + encrypted_data = crypt.ige_encrypt(data_with_sha_padded, tmp_aes_key, tmp_aes_iv) - Set_client_DH_params_answer = self.method_call('set_client_DH_params', - nonce=nonce, - server_nonce=server_nonce, - encrypted_data=encrypted_data) + Set_client_DH_params_answer = self.method_call('set_client_DH_params', + nonce=nonce, + server_nonce=server_nonce, + encrypted_data=encrypted_data) - auth_key = pow(g_a, b, dh_prime) - auth_key_str = int.to_bytes(auth_key, auth_key.bit_length() // 8 + 1, 'big') - auth_key_sha = SHA.new(auth_key_str).digest() - auth_key_hash = auth_key_sha[-8:] - auth_key_aux_hash = auth_key_sha[:8] + auth_key = pow(g_a, b, dh_prime) + auth_key_str = long_to_bytes(auth_key) + auth_key_sha = SHA.new(auth_key_str).digest() + auth_key_hash = auth_key_sha[-8:] + auth_key_aux_hash = auth_key_sha[:8] - new_nonce_hash1 = SHA.new(new_nonce+b'\x01'+auth_key_aux_hash).digest()[-16:] - new_nonce_hash2 = SHA.new(new_nonce+b'\x02'+auth_key_aux_hash).digest()[-16:] - new_nonce_hash3 = SHA.new(new_nonce+b'\x03'+auth_key_aux_hash).digest()[-16:] + new_nonce_hash1 = SHA.new(new_nonce+b'\x01'+auth_key_aux_hash).digest()[-16:] + new_nonce_hash2 = SHA.new(new_nonce+b'\x02'+auth_key_aux_hash).digest()[-16:] + new_nonce_hash3 = SHA.new(new_nonce+b'\x03'+auth_key_aux_hash).digest()[-16:] - assert Set_client_DH_params_answer['nonce'] == nonce - assert Set_client_DH_params_answer['server_nonce'] == server_nonce - assert Set_client_DH_params_answer['new_nonce_hash1'] == new_nonce_hash1 - print("Diffie Hellman key exchange processed successfully") + assert Set_client_DH_params_answer['nonce'] == nonce + assert Set_client_DH_params_answer['server_nonce'] == server_nonce + assert Set_client_DH_params_answer['new_nonce_hash1'] == new_nonce_hash1 + print("Diffie Hellman key exchange processed successfully") - self.server_salt = strxor(new_nonce[0:8], server_nonce[0:8]) - self.auth_key = auth_key_str - print("Auth key generated") - break - except: - print("Retry DH key exchange") - retry_id += 1 + self.server_salt = strxor(new_nonce[0:8], server_nonce[0:8]) + self.auth_key = auth_key_str + print("Auth key generated") + def aes_calculate(self, msg_key, direction="to server"): + x = 0 if direction == "to server" else 8 + sha1_a = SHA.new(msg_key + self.auth_key[x:x+32]).digest() + sha1_b = SHA.new(self.auth_key[x+32:x+48] + msg_key + self.auth_key[48+x:64+x]).digest() + sha1_c = SHA.new(self.auth_key[x+64:x+96] + msg_key).digest() + sha1_d = SHA.new(msg_key + self.auth_key[x+96:x+128]).digest() + aes_key = sha1_a[0:8] + sha1_b[8:20] + sha1_c[4:16] + aes_iv = sha1_a[8:20] + sha1_b[0:8] + sha1_c[16:20] + sha1_d[0:8] + return aes_key, aes_iv