Finished authenticator (now it works!)

This commit is contained in:
Lonami 2016-09-03 20:34:24 +02:00
parent 75892afb2e
commit c863537b7b
4 changed files with 97 additions and 12 deletions

View File

@ -11,5 +11,7 @@ if __name__ == '__main__':
transport = TcpTransport('149.154.167.91', 443) transport = TcpTransport('149.154.167.91', 443)
auth_key, time_offset = do_authentication(transport) auth_key, time_offset = do_authentication(transport)
print(auth_key) print(auth_key.aux_hash)
print(auth_key.key)
print(auth_key.key_id)
print(time_offset) print(time_offset)

View File

@ -11,8 +11,8 @@ from utils.factorizator import Factorizator
from utils.auth_key import AuthKey from utils.auth_key import AuthKey
import utils.helpers as utils import utils.helpers as utils
import time import time
import pyaes
from utils.rsa import RSA from utils.rsa import RSA
from utils.aes import AES
def do_authentication(transport): def do_authentication(transport):
@ -111,12 +111,10 @@ def do_authentication(transport):
# Step 3 sending: Complete DH Exchange # Step 3 sending: Complete DH Exchange
key, iv = utils.generate_key_data_from_nonces(server_nonce, new_nonce) key, iv = utils.generate_key_data_from_nonces(server_nonce, new_nonce)
# TODO ValueError: initialization vector must be 16 bytes plain_text_answer = AES.decrypt_ige(encrypted_answer, key, iv)
aes = pyaes.AESModeOfOperationCFB(key, iv, len(key))
plain_text_answer = aes.decrypt(encrypted_answer)
g, dh_prime, ga, time_offset = None, None, None, None g, dh_prime, ga, time_offset = None, None, None, None
with BinaryReader(plain_text_answer.encode('ascii')) as dh_inner_data_reader: with BinaryReader(plain_text_answer) as dh_inner_data_reader:
hashsum = dh_inner_data_reader.read(20) hashsum = dh_inner_data_reader.read(20)
code = dh_inner_data_reader.read_int(signed=False) code = dh_inner_data_reader.read_int(signed=False)
if code != 0xb5890dba: if code != 0xb5890dba:
@ -131,15 +129,15 @@ def do_authentication(transport):
raise AssertionError('Invalid server nonce in encrypted answer') raise AssertionError('Invalid server nonce in encrypted answer')
g = dh_inner_data_reader.read_int() g = dh_inner_data_reader.read_int()
# "current value of dh_prime equals (in big-endian byte order) # "current value of dh_prime equals (in big-endian byte order)"
# See https://core.telegram.org/mtproto/auth_key#presenting-proof-of-work-server-authentication # See https://core.telegram.org/mtproto/auth_key#presenting-proof-of-work-server-authentication
dh_prime = int.from_bytes(dh_inner_data_reader.tgread_bytes(), byteorder='big', signed=True) dh_prime = int.from_bytes(dh_inner_data_reader.tgread_bytes(), byteorder='big', signed=False)
ga = int.from_bytes(dh_inner_data_reader.tgread_bytes(), byteorder='big', signed=True) ga = int.from_bytes(dh_inner_data_reader.tgread_bytes(), byteorder='big', signed=False)
server_time = dh_inner_data_reader.read_int() server_time = dh_inner_data_reader.read_int()
time_offset = server_time - int(time.time() * 1000) # Multiply by 1000 to get milliseconds time_offset = server_time - int(time.time())
b = int.from_bytes(utils.generate_random_bytes(2048), byteorder='big', signed=True) b = int.from_bytes(utils.generate_random_bytes(2048), byteorder='big', signed=False)
gb = pow(g, b, dh_prime) gb = pow(g, b, dh_prime)
gab = pow(ga, b, dh_prime) gab = pow(ga, b, dh_prime)
@ -157,7 +155,7 @@ def do_authentication(transport):
client_dh_inner_data_bytes = client_dh_inner_data_with_hash_writer.get_bytes() client_dh_inner_data_bytes = client_dh_inner_data_with_hash_writer.get_bytes()
# Encryption # Encryption
client_dh_inner_data_encrypted_bytes = aes.encrypt(client_dh_inner_data_bytes) client_dh_inner_data_encrypted_bytes = AES.encrypt_ige(client_dh_inner_data_bytes, key, iv)
# Prepare Set client DH params # Prepare Set client DH params
with BinaryWriter() as set_client_dh_params_writer: with BinaryWriter() as set_client_dh_params_writer:
@ -170,6 +168,7 @@ def do_authentication(transport):
sender.send(set_client_dh_params_bytes) sender.send(set_client_dh_params_bytes)
# Step 3 response: Complete DH Exchange # Step 3 response: Complete DH Exchange
# TODO, no more data, why did it stop again?!
with BinaryReader(sender.receive()) as reader: with BinaryReader(sender.receive()) as reader:
code = reader.read_int(signed=False) code = reader.read_int(signed=False)
if code == 0x3bcbf734: # DH Gen OK if code == 0x3bcbf734: # DH Gen OK

View File

@ -8,6 +8,7 @@ from network.tcp_client import TcpClient
from utils.binary_reader import BinaryReader from utils.binary_reader import BinaryReader
from utils.binary_writer import BinaryWriter from utils.binary_writer import BinaryWriter
from utils.factorizator import Factorizator from utils.factorizator import Factorizator
from utils.aes import AES
host = 'localhost' host = 'localhost'
@ -191,5 +192,27 @@ class UnitTest(unittest.TestCase):
value = int.from_bytes(bytez, byteorder='big', signed=True) value = int.from_bytes(bytez, byteorder='big', signed=True)
assert value == real, 'Invalid bytes to int conversion (should be {} but is {})'.format(real, value) assert value == real, 'Invalid bytes to int conversion (should be {} but is {})'.format(real, value)
@staticmethod
def test_aes_decrypt():
cipher_text = get_bytes('EF-5F-5D-57-7B-A5-95-B0-1D-B7-BF-E5-09-AC-64-5E-F9-ED-E3-28-FB-D7-59-78-16-F8-74-4A-51-A6-10-16-F5-EF-99-0B-E6-CA-A7-9D-FA-DD-7B-CD-39-BF-FB-F0-D4-B6-09-50-76-78-9D-6F-87-DD-57-33-B7-F4-48-4F-C1-78-73-66-22-E1-65-74-E6-7B-4F-EB-99-7B-2D-58-94-62-10-2A-A2-C8-95-B4-D4-EE-2E-C9-44-DA-54-EE-7A-86-72-34-14-54-4F-E3-F9-C5-3A-3F-7E-C3-28-29-2F-19-D4-40-DA-D4-E6-11-A4-6D-A7-8A-20-0A-C4-2A-55-DD-4A-1A-D1-57-18-86-4C-0F-BC-9C-F6-2C-D5-E1-FA-D4-08-48-F9-B2-49-61-BA-30-6A-9F-E0-3E-55-66-E0-57-55-8A-02-57-28-E1-C8-BD-4A-F7-26-6A-6E-2F-93-76-57-C4-E5-F3-96-F3-79-17-B8-16-15-C1-A4-21-11-0F-9A-4D-92-BB-DF-2F-8E-4C-3E-88-D6-41-CC-91-D4-BA-FF-5A-F1-D9-2C-9C-FA-F3-DD-DB-03-B0-1B-1C-32-8C-9C-DE-96-FA-AE-40-9C-F5-AC-15-BF-11-17-D3-0F-E4-F4-C5-46-E7-27-3C-47-91-FD-02-AC-FC-7F-84-0D-4A-BC-43-2A-7E-0E-B9-BD-93-8E-0F-C9-1B-CF-C3-61-EA-2A-73-D8-00-3C-6E-BA-33-63-A6-24-16-AB-AB-11-93-3D-7B-20-29-C6-37-43-CF-78-C7-25-CE-03-02-0C-58-B3-34-24-61-06-FE-DD-00-65-BC-51-99-6E-08-51-8B-8D-83-CA-F1-36-ED-94-F6-FF-19-30-1C-6D-4E-6F-8C-59-08-2D-60-E6-3D-A8-39-C2-FE-96-2D-CF-AD-15-F5-68-B1-5B-2A-2F-6D-86-92-D9-F8-45-4F-09-80-01-3C-D1-8B-37-88-AA-52-02-58-18-7B-B5-86-60-BE-2C-E6-EE-3D-85-02-F9-CC-09-4A-44-55-73-24-A1-9B-01-ED-B1-FD-FF-C8-E1-F4-78-A9-DB-DD-F0-50-2F-A7-AB-EF-3C-0F-FC-82-5B-D5-35-E0-32-60-2F-F1-A3-DF-BE-70-68-F5-1A-AB-21-55-34-A4-45-86-B2-75-36-D1-50-36-DC-4B-78-5F-7D-44-F9-83-A4-A3-68-E9-4B-08-FA-7F-55-85-55-31-35-E5-C1-14-A4-E3-E2-AC-8A-2D-64-36-6F-46-4E-B1-AF-FE-66-BF-38-DD-1D-40-BC-DA-3F-0A-48-91-BD-09-84-B6-42-35-2A-E9-3A-57-63-92-BB-94-44-91-C7-C4-46-79-C4-60-7F-95-88-53-24-CF-CE-0A-4C-28-9B-B9-4F-10-68-CC-E3-A5-3F-F1-A1-43-4A-C8-FF-98-AC-F5-BF-36-7F-08-01-05-02-2F-C4-3D-EA-F9-0B-3E-99-FD-3A-C2-03-E7-D3-CE-79-2D-EE-F4-01-C8-6B-4B-AA-81-BA-49-D1-87-84-72-32-7F-6B-7F')
key = get_bytes('23-D7-11-75-77-78-5D-74-6F-74-C4-23-D3-99-66-E7-77-8B-5A-92-94-A5-88-C2-C3-D4-46-B7-F1-0C-D7-FA')
iv = get_bytes('F8-DC-50-26-71-C3-56-72-D7-07-78-17-57-D3-B6-5D-A6-EB-02-DB-7D-73-0A-0B-1C-29-15-40-CC-6C-03-7E')
plain_text = AES.decrypt_ige(cipher_text, key, iv)
real = get_bytes('FB-17-EB-54-86-B6-49-1C-DF-D8-24-E6-D9-82-37-44-66-18-84-9F-BA-0D-89-B5-81-0D-47-B2-1B-CB-56-3F-7F-69-3A-22-0A-30-39-71-EE-1F-40-B2-A7-1A-4B-BD-76-7E-7A-FD-20-68-58-5F-03-00-00-00-FE-00-01-00-C7-1C-AE-B9-C6-B1-C9-04-8E-6C-52-2F-70-F1-3F-73-98-0D-40-23-8E-3E-21-C1-49-34-D0-37-56-3D-93-0F-48-19-8A-0A-A7-C1-40-58-22-94-93-D2-25-30-F4-DB-FA-33-6F-6E-0A-C9-25-13-95-43-AE-D4-4C-CE-7C-37-20-FD-51-F6-94-58-70-5A-C6-8C-D4-FE-6B-6B-13-AB-DC-97-46-51-29-69-32-84-54-F1-8F-AF-8C-59-5F-64-24-77-FE-96-BB-2A-94-1D-5B-CD-1D-4A-C8-CC-49-88-07-08-FA-9B-37-8E-3C-4F-3A-90-60-BE-E6-7C-F9-A4-A4-A6-95-81-10-51-90-7E-16-27-53-B5-6B-0F-6B-41-0D-BA-74-D8-A8-4B-2A-14-B3-14-4E-0E-F1-28-47-54-FD-17-ED-95-0D-59-65-B4-B9-DD-46-58-2D-B1-17-8D-16-9C-6B-C4-65-B0-D6-FF-9C-A3-92-8F-EF-5B-9A-E4-E4-18-FC-15-E8-3E-BE-A0-F8-7F-A9-FF-5E-ED-70-05-0D-ED-28-49-F4-7B-F9-59-D9-56-85-0C-E9-29-85-1F-0D-81-15-F6-35-B1-05-EE-2E-4E-15-D0-4B-24-54-BF-6F-4F-AD-F0-34-B1-04-03-11-9C-D8-E3-B9-2F-CC-5B-FE-00-01-00-22-8A-8F-62-58-9E-D1-9F-4B-53-EC-FB-22-E0-52-6B-8E-09-2E-B6-4B-90-53-30-A7-1F-52-1F-5B-3C-8F-AC-12-5B-D3-35-22-2A-1E-3E-9F-BD-33-73-B3-5C-1A-A6-8E-01-35-B4-8C-92-AE-D9-A0-86-6C-EF-CA-C9-09-4E-3B-B8-E5-F6-76-EE-F9-E7-CE-F1-DF-9E-F1-2E-92-55-DF-CF-80-68-70-AD-D1-AB-B2-34-54-E0-BF-38-A6-F4-C4-5B-64-96-8F-C7-14-18-84-7A-0A-44-38-56-1A-E4-9E-16-81-9D-AF-CC-A5-0E-17-D1-9E-DB-DE-DD-14-7D-04-71-99-06-32-3E-92-CE-D4-6E-76-10-DF-17-D9-2E-97-6A-F0-81-CC-ED-D5-20-10-60-AD-D8-7B-C2-FB-7D-87-CD-1E-B7-0E-28-1A-78-61-9C-8A-CA-81-A0-03-B2-40-7F-AE-BB-E3-21-96-74-01-A6-E2-C0-D8-17-C9-19-78-AD-1C-51-12-DC-29-8F-50-CA-8A-2F-56-89-A4-AC-E0-0F-C1-71-E4-C7-33-71-AE-83-30-59-D1-33-C2-D6-A6-F1-E8-FC-1F-77-3D-ED-E9-BF-B5-1C-6F-C0-9E-81-4B-B2-5A-51-C3-94-6B-BD-AD-5C-FF-DD-4B-0C-DB-E8-DA-0D-CB-57-B5-AC-D0-95-9E-FC-8F-F8')
assert plain_text == real, 'Decrypted text does not equal the real value (expected "{}", got "{}")'\
.format(get_representation(real), get_representation(plain_text))
@staticmethod
def test_aes_encrypt():
original_text = get_bytes('CD-B7-90-A9-61-41-F7-AD-A2-F9-FA-68-A4-D9-F2-5D-D6-6A-2E-40-54-B6-43-66-81-0D-47-B2-1B-CB-56-3F-7F-69-3A-22-0A-30-39-71-EE-1F-40-B2-A7-1A-4B-BD-76-7E-7A-FD-20-68-58-5F-00-00-00-00-00-00-00-00-FE-00-01-00-53-5B-3B-61-71-BC-C5-2D-2C-E3-D3-DB-4E-BF-BC-C0-3A-D0-90-89-C5-2E-EB-86-10-B9-B4-4B-D9-CF-00-DD-DA-D9-12-5E-27-DD-00-D2-61-E2-8A-93-97-30-38-0E-AC-49-C5-A2-7A-C8-67-6B-2C-B0-3C-95-BA-E2-C3-AC-6D-F7-87-7E-0F-9F-F1-A1-FD-87-81-04-C5-F0-14-35-E6-67-5A-E4-4E-57-A8-8D-C2-66-AF-F9-0B-82-13-CD-BC-FF-26-68-90-81-FA-26-34-80-B5-A7-C3-69-15-B1-31-BE-46-C2-B7-14-6B-75-B8-F2-71-6A-27-5B-3B-30-CF-A9-97-65-C6-E5-7D-46-EC-12-D3-6D-8F-64-58-03-1E-66-24-D5-87-9A-8B-CE-E3-D1-71-9B-F2-A9-49-19-69-B4-0A-D0-97-0E-91-5E-B0-F3-C2-FB-FF-AC-1F-CD-30-7E-C0-79-9F-DE-E0-85-17-16-13-10-FF-E8-24-B3-71-B5-C2-BC-72-B3-39-DB-53-D3-52-CB-6C-48-19-6D-CA-98-FB-C2-D4-24-6F-FD-8C-68-31-2F-C9-F4-6F-9B-55-9B-A5-6D-B9-54-D0-53-BC-8B-EC-4B-3F-D2-3C-E9-5E-34-79-80-9A-D2-8C-B9-5F-95-7A-46-72-11-4E-E6')
key = get_bytes('23-D7-11-75-77-78-5D-74-6F-74-C4-23-D3-99-66-E7-77-8B-5A-92-94-A5-88-C2-C3-D4-46-B7-F1-0C-D7-FA')
iv = get_bytes('F8-DC-50-26-71-C3-56-72-D7-07-78-17-57-D3-B6-5D-A6-EB-02-DB-7D-73-0A-0B-1C-29-15-40-CC-6C-03-7E')
cipher_text = AES.encrypt_ige(original_text, key, iv)
real = get_bytes('57-A9-1C-BB-4A-B5-C7-B1-51-9E-A9-24-15-94-4B-63-CB-2F-50-93-B7-54-11-D8-F1-77-13-AE-DE-58-12-AF-C2-E1-10-1D-2C-38-7D-DD-A2-BD-6B-43-84-7E-B1-E5-51-69-62-36-A1-86-8D-02-25-B9-AA-0B-E2-32-13-2A-0F-D1-58-67-47-07-C9-FE-E0-0F-EE-EC-92-B8-65-BD-C4-69-31-B6-10-4E-8F-20-B6-8F-72-79-A0-A3-8F-63-37-4B-95-5F-A4-B0-E6-EE-49-BE-76-47-3E-F9-FF-AA-F6-B7-16-CD-24-09-B1-63-26-02-13-B8-99-BD-19-7F-E2-A5-91-BE-52-86-FF-EA-C9-05-3C-D6-19-AE-E6-D1-25-7F-38-AF-66-CF-F8-B7-E6-53-E3-F6-98-0D-EF-A8-BA-97-6F-20-09-69-29-73-12-5E-0F-77-10-DC-22-BB-23-25-1D-53-A6-10-26-EB-EB-5E-C4-86-04-F5-30-5B-BA-53-AE-EA-84-28-34-91-75-B8-F2-0B-74-D1-00-3A-7E-FE-F0-B5-BE-0E-86-21-61-5F-81-75-23-49-45-CB-07-57-78-AB-9B-80-A2-0A-46-DB-35-49-6A-08-5B-8B-55-4E-6E-1B-E0-E0-3E-E7-2A-06-A0-5D-4F-EA-71-1A-24-F4-F4-AF-95-4B-F2-9A-C5-FD-7F-6A-DD-61-2D-B3-29-DB-5B-D9-A8-CF-60-8F-36-85-04-B5-85-3F-78-EB-09-0C-B4-F4-2D-B8-67-71-2A-4B-1B-08-0C-18-A2-9E-30-13-0C-23-18-7A-43-73-D3-DC-38-F5-9A-4A-08-28-BD-A2-DC-A8-70-33-63-9E-A9-72-DF-72-A5-43-AB-A2')
assert cipher_text == real, 'Decrypted text does not equal the real value (expected "{}", got "{}")'\
.format(get_representation(real), get_representation(cipher_text))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

61
utils/aes.py Normal file
View File

@ -0,0 +1,61 @@
import pyaes
class AES:
@staticmethod
def decrypt_ige(cipher_text, key, iv):
iv1 = iv[:len(iv)//2]
iv2 = iv[len(iv)//2:]
aes = pyaes.AES(key)
plain_text = [0] * len(cipher_text)
blocks_count = len(cipher_text) // 16
cipher_text_block = [0] * 16
for block_index in range(blocks_count):
for i in range(16):
cipher_text_block[i] = cipher_text[block_index * 16 + i] ^ iv2[i]
plain_text_block = aes.decrypt(cipher_text_block)
for i in range(16):
plain_text_block[i] ^= iv1[i]
iv1 = cipher_text[block_index * 16:block_index * 16 + 16]
iv2 = plain_text_block[0:16]
plain_text[block_index * 16:block_index * 16 + 16] = plain_text_block[:16]
return bytes(plain_text)
@staticmethod
def encrypt_ige(plain_text, key, iv):
# TODO: Random padding
padding = bytes(16 - len(plain_text) % 16)
plain_text += padding
iv1 = iv[:len(iv)//2]
iv2 = iv[len(iv)//2:]
aes = pyaes.AES(key)
blocks_count = len(plain_text) // 16
cipher_text = [0] * len(plain_text)
for block_index in range(blocks_count):
plain_text_block = list(plain_text[block_index * 16:block_index * 16 + 16])
for i in range(16):
plain_text_block[i] ^= iv1[i]
cipher_text_block = aes.encrypt(plain_text_block)
for i in range(16):
cipher_text_block[i] ^= iv2[i]
iv1 = cipher_text_block[0:16]
iv2 = plain_text[block_index * 16:block_index * 16 + 16]
cipher_text[block_index * 16:block_index * 16 + 16] = cipher_text_block[:16]
return bytes(cipher_text)