Start of refactoring
A lot of work to be done
This commit is contained in:
parent
0d79c88636
commit
94ed33bb15
145
TL.py
Normal file
145
TL.py
Normal file
@ -0,0 +1,145 @@
|
||||
__author__ = 'agrigoryev'
|
||||
import os
|
||||
import struct
|
||||
import json
|
||||
import io
|
||||
import numbers
|
||||
|
||||
class TlConstructor:
|
||||
def __init__(self, json_dict):
|
||||
self.id = int(json_dict['id'])
|
||||
self.type = json_dict['type']
|
||||
self.predicate = json_dict['predicate']
|
||||
self.params = []
|
||||
# case of vector
|
||||
for param in json_dict['params']:
|
||||
if param['type'] == "Vector<long>":
|
||||
param['type'] = "Vector t"
|
||||
param['subtype'] = "long"
|
||||
else:
|
||||
param['subtype'] = None
|
||||
self.params.append(param)
|
||||
|
||||
class TlMethod:
|
||||
def __init__(self, json_dict):
|
||||
self.id = int(json_dict['id'])
|
||||
self.type = json_dict['type']
|
||||
self.method = json_dict['method']
|
||||
self.params = json_dict['params']
|
||||
|
||||
|
||||
class TL:
|
||||
def __init__(self, filename):
|
||||
with open(filename, 'r') as f:
|
||||
TL_dict = json.load(f)
|
||||
|
||||
# Read constructors
|
||||
|
||||
self.constructors = TL_dict['constructors']
|
||||
self.constructor_id = {}
|
||||
self.constructor_type = {}
|
||||
for elem in self.constructors:
|
||||
z = TlConstructor(elem)
|
||||
self.constructor_id[z.id] = z
|
||||
self.constructor_type[z.predicate] = z
|
||||
|
||||
self.methods = TL_dict['methods']
|
||||
self.method_id = {}
|
||||
self.method_name = {}
|
||||
for elem in self.methods:
|
||||
z = TlMethod(elem)
|
||||
self.method_id[z.id] = z
|
||||
self.method_name[z.method] = z
|
||||
|
||||
|
||||
## Loading TL_schema (should be placed in the same directory as mtproto.py
|
||||
tl = TL(os.path.join(os.path.dirname(__file__), "TL_schema.JSON"))
|
||||
|
||||
|
||||
def serialize_obj(bytes_io, type_, **kwargs):
|
||||
try:
|
||||
tl_constructor = tl.constructor_type[type_]
|
||||
except KeyError:
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
bytes_io.write(struct.pack('<i', tl_constructor.id))
|
||||
for arg in tl_constructor.params:
|
||||
serialize_param(bytes_io, type_=arg['type'], value=kwargs[arg['name']])
|
||||
|
||||
|
||||
def serialize_method(bytes_io, type_, **kwargs):
|
||||
try:
|
||||
tl_method = tl.method_name[type_]
|
||||
except KeyError:
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
bytes_io.write(struct.pack('<i', tl_method.id))
|
||||
for arg in tl_method.params:
|
||||
serialize_param(bytes_io, type_=arg['type'], value=kwargs[arg['name']])
|
||||
|
||||
|
||||
def serialize_param(bytes_io, type_, value):
|
||||
if type_ == "int":
|
||||
assert isinstance(value, int)
|
||||
bytes_io.write(struct.pack('<i', value))
|
||||
elif type_ == "long":
|
||||
assert isinstance(value, numbers.Real)
|
||||
bytes_io.write(struct.pack('<q', value))
|
||||
elif type_ in ["int128", "int256"]:
|
||||
assert isinstance(value, bytes)
|
||||
bytes_io.write(value)
|
||||
elif type_ == 'string' or 'bytes':
|
||||
l = len(value)
|
||||
if l < 254: # short string format
|
||||
bytes_io.write(struct.pack('<b', l)) # 1 byte of string
|
||||
bytes_io.write(value) # string
|
||||
bytes_io.write(b'\x00'*((-l-1) % 4)) # padding bytes
|
||||
else:
|
||||
bytes_io.write(b'\xfe') # byte 254
|
||||
bytes_io.write(struct.pack('<i', l)[:3]) # 3 bytes of string
|
||||
bytes_io.write(value) # string
|
||||
bytes_io.write(b'\x00'*(-l % 4)) # padding bytes
|
||||
|
||||
def deserialize(bytes_io, type_=None, subtype=None):
|
||||
"""
|
||||
:type bytes_io: io.BytesIO object
|
||||
"""
|
||||
assert isinstance(bytes_io, io.BytesIO)
|
||||
|
||||
# Built-in bare types
|
||||
if type_ == 'int': x = struct.unpack('<i', bytes_io.read(4))[0]
|
||||
elif type_ == '#': x = struct.unpack('<I', bytes_io.read(4))[0]
|
||||
elif type_ == 'long': x = struct.unpack('<q', bytes_io.read(8))[0]
|
||||
elif type_ == 'double': x = struct.unpack('<d', bytes_io.read(8))[0]
|
||||
elif type_ == 'int128': x = bytes_io.read(16)
|
||||
elif type_ == 'int256': x = bytes_io.read(32)
|
||||
elif type_ == 'string' or type_ == 'bytes':
|
||||
l = struct.unpack('<B', bytes_io.read(1))[0]
|
||||
assert l <= 254 # In general, 0xFF byte is not allowed here
|
||||
if l == 254:
|
||||
# We have a long string
|
||||
long_len = struct.unpack('<I', bytes_io.read(3)+b'\x00')[0]
|
||||
x = bytes_io.read(long_len)
|
||||
bytes_io.read(-long_len % 4) # skip padding bytes
|
||||
else:
|
||||
# We have a short string
|
||||
x = bytes_io.read(l)
|
||||
bytes_io.read(-(l+1) % 4) # skip padding bytes
|
||||
assert isinstance(x, bytes)
|
||||
elif type_ == 'vector':
|
||||
assert subtype is not None
|
||||
count = struct.unpack('<l', bytes_io.read(4))[0]
|
||||
x = [deserialize(bytes_io, type_=subtype) for i in range(count)]
|
||||
else:
|
||||
# Boxed types
|
||||
i = struct.unpack('<i', bytes_io.read(4))[0] # read type ID
|
||||
try:
|
||||
tl_elem = tl.constructor_id[i]
|
||||
except KeyError:
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
base_boxed_types = ["Vector t", "Int", "Long", "Double", "String", "Int128", "Int256"]
|
||||
if tl_elem.type in base_boxed_types:
|
||||
x = deserialize(bytes_io, type_=tl_elem.predicate, subtype=subtype)
|
||||
else: # other types
|
||||
x = {}
|
||||
for arg in tl_elem.params:
|
||||
x[arg['name']] = deserialize(bytes_io, type_=arg['type'], subtype=arg['subtype'])
|
||||
return x
|
331
mtproto.py
331
mtproto.py
@ -6,10 +6,7 @@ Created on Tue Sep 2 19:26:15 2014
|
||||
@author: Sammy Pfeiffer
|
||||
"""
|
||||
from binascii import crc32 as originalcrc32
|
||||
MIN_SUPPORTED_PY3_VERSION = (3, 2, 0)
|
||||
import numbers
|
||||
def crc32(data):
|
||||
return originalcrc32(data) & 0xffffffff
|
||||
from datetime import datetime
|
||||
from time import time
|
||||
import io
|
||||
@ -18,6 +15,19 @@ import json
|
||||
import socket
|
||||
import struct
|
||||
|
||||
# pycrypto module
|
||||
from Crypto.Hash import SHA
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.strxor import strxor
|
||||
|
||||
# local modules
|
||||
import crypt
|
||||
import prime
|
||||
import TL
|
||||
|
||||
def crc32(data):
|
||||
return originalcrc32(data) & 0xffffffff
|
||||
|
||||
def vis(bs):
|
||||
"""
|
||||
Function to visualize byte streams. Split into bytes, print to console.
|
||||
@ -32,156 +42,22 @@ def vis(bs):
|
||||
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 TlConstructor:
|
||||
def __init__(self, json_dict):
|
||||
self.id = int(json_dict['id'])
|
||||
self.type = json_dict['type']
|
||||
self.predicate = json_dict['predicate']
|
||||
self.params = []
|
||||
# case of vector
|
||||
for param in json_dict['params']:
|
||||
if param['type'] == "Vector<long>":
|
||||
param['type'] = "Vector t"
|
||||
param['subtype'] = "long"
|
||||
else:
|
||||
param['subtype'] = None
|
||||
self.params.append(param)
|
||||
|
||||
class TlMethod:
|
||||
def __init__(self, json_dict):
|
||||
self.id = int(json_dict['id'])
|
||||
self.type = json_dict['type']
|
||||
self.method = json_dict['method']
|
||||
self.params = json_dict['params']
|
||||
|
||||
|
||||
class TL:
|
||||
def __init__(self, filename):
|
||||
with open(filename, 'r') as f:
|
||||
TL_dict = json.load(f)
|
||||
|
||||
# Read constructors
|
||||
|
||||
self.constructors = TL_dict['constructors']
|
||||
self.constructor_id = {}
|
||||
self.constructor_type = {}
|
||||
for elem in self.constructors:
|
||||
z = TlConstructor(elem)
|
||||
self.constructor_id[z.id] = z
|
||||
self.constructor_type[z.predicate] = z
|
||||
|
||||
self.methods = TL_dict['methods']
|
||||
self.method_id = {}
|
||||
self.method_name = {}
|
||||
for elem in self.methods:
|
||||
z = TlMethod(elem)
|
||||
self.method_id[z.id] = z
|
||||
self.method_name[z.method] = z
|
||||
|
||||
|
||||
## Loading TL_schema (should be placed in the same directory as mtproto.py
|
||||
tl = TL(os.path.join(os.path.dirname(__file__), "TL_schema.JSON"))
|
||||
|
||||
|
||||
def serialize_obj(bytes_io, type_, **kwargs):
|
||||
try:
|
||||
tl_constructor = tl.constructor_type[type_]
|
||||
except KeyError:
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
bytes_io.write(struct.pack('<i', tl_constructor.id))
|
||||
for arg in tl_constructor.params:
|
||||
serialize_param(bytes_io, type_=arg['type'], value=kwargs[arg['name']])
|
||||
|
||||
|
||||
def serialize_method(bytes_io, type_, **kwargs):
|
||||
try:
|
||||
tl_method = tl.method_name[type_]
|
||||
except KeyError:
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
bytes_io.write(struct.pack('<i', tl_method.id))
|
||||
for arg in tl_method.params:
|
||||
serialize_param(bytes_io, type_=arg['type'], value=kwargs[arg['name']])
|
||||
|
||||
|
||||
def serialize_param(bytes_io, type_, value):
|
||||
if type_ == "int":
|
||||
assert isinstance(value, int)
|
||||
bytes_io.write(struct.pack('<i', value))
|
||||
elif type_ == "long":
|
||||
assert isinstance(value, numbers.Real)
|
||||
bytes_io.write(struct.pack('<q', value))
|
||||
elif type_ in ["int128", "int256"]:
|
||||
assert isinstance(value, bytes)
|
||||
bytes_io.write(value)
|
||||
elif type_ == 'string' or 'bytes':
|
||||
l = len(value)
|
||||
if l < 254: # short string format
|
||||
bytes_io.write(struct.pack('<b', l)) # 1 byte of string
|
||||
bytes_io.write(value) # string
|
||||
bytes_io.write(b'\x00'*((-l-1) % 4)) # padding bytes
|
||||
else:
|
||||
bytes_io.write(b'\xfe') # byte 254
|
||||
bytes_io.write(struct.pack('<i', l)[:3]) # 3 bytes of string
|
||||
bytes_io.write(value) # string
|
||||
bytes_io.write(b'\x00'*(-l % 4)) # padding bytes
|
||||
|
||||
def deserialize(bytes_io, type_=None, subtype=None):
|
||||
"""
|
||||
:type bytes_io: io.BytesIO object
|
||||
"""
|
||||
assert isinstance(bytes_io, io.BytesIO)
|
||||
|
||||
# Built-in bare types
|
||||
if type_ == 'int': x = struct.unpack('<i', bytes_io.read(4))[0]
|
||||
elif type_ == '#': x = struct.unpack('<I', bytes_io.read(4))[0]
|
||||
elif type_ == 'long': x = struct.unpack('<q', bytes_io.read(8))[0]
|
||||
elif type_ == 'double': x = struct.unpack('<d', bytes_io.read(8))[0]
|
||||
elif type_ == 'int128': x = bytes_io.read(16)
|
||||
elif type_ == 'int256': x = bytes_io.read(32)
|
||||
elif type_ == 'string' or type_ == 'bytes':
|
||||
l = struct.unpack('<B', bytes_io.read(1))[0]
|
||||
assert l <= 254 # In general, 0xFF byte is not allowed here
|
||||
if l == 254:
|
||||
# We have a long string
|
||||
long_len = struct.unpack('<I', bytes_io.read(3)+b'\x00')[0]
|
||||
x = bytes_io.read(long_len)
|
||||
bytes_io.read(-long_len % 4) # skip padding bytes
|
||||
else:
|
||||
# We have a short string
|
||||
x = bytes_io.read(l)
|
||||
bytes_io.read(-(l+1) % 4) # skip padding bytes
|
||||
assert isinstance(x, bytes)
|
||||
elif type_ == 'vector':
|
||||
assert subtype is not None
|
||||
count = struct.unpack('<l', bytes_io.read(4))[0]
|
||||
print("count is: " + str(count))
|
||||
x = [deserialize(bytes_io, type_=subtype) for i in range(count)]
|
||||
else:
|
||||
# Boxed types
|
||||
i = struct.unpack('<i', bytes_io.read(4))[0] # read type ID
|
||||
try:
|
||||
tl_elem = tl.constructor_id[i]
|
||||
except KeyError:
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
base_boxed_types = ["Vector t", "Int", "Long", "Double", "String", "Int128", "Int256"]
|
||||
if tl_elem.type in base_boxed_types:
|
||||
x = deserialize(bytes_io, type_=tl_elem.predicate, subtype=subtype)
|
||||
else: # other types
|
||||
x = {}
|
||||
for arg in tl_elem.params:
|
||||
x[arg['name']] = deserialize(bytes_io, type_=arg['type'], subtype=arg['subtype'])
|
||||
return x
|
||||
|
||||
|
||||
class Session:
|
||||
""" Manages TCP Transport. encryption and message frames """
|
||||
def __init__(self, ip, port, auth_key_id=None):
|
||||
def __init__(self, ip, port, auth_key=None):
|
||||
# creating socket
|
||||
self.sock = socket.socket()
|
||||
self.sock.connect((ip, port))
|
||||
self.auth_key_id = auth_key_id
|
||||
self.number = 0
|
||||
|
||||
if auth_key is None:
|
||||
self.create_auth_key()
|
||||
else:
|
||||
self.auth_key = auth_key
|
||||
|
||||
def __del__(self):
|
||||
# closing socket when session object is deleted
|
||||
self.sock.close()
|
||||
@ -203,7 +79,7 @@ class Session:
|
||||
|
||||
return (b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
||||
struct.pack('<Q', msg_id) +
|
||||
struct.pack('<L', len(message)))
|
||||
struct.pack('<I', len(message)))
|
||||
|
||||
# TCP Transport
|
||||
|
||||
@ -219,16 +95,14 @@ class Session:
|
||||
Forming the message frame and sending message to server
|
||||
:param message: byte string to send
|
||||
"""
|
||||
|
||||
|
||||
data = self.header_unencrypted(message) + message
|
||||
step1 = struct.pack('<LL', len(data)+12, self.number) + data
|
||||
step2 = step1 + struct.pack('<L', crc32(step1))
|
||||
step1 = struct.pack('<II', len(data)+12, self.number) + data
|
||||
step2 = step1 + struct.pack('<I', crc32(step1))
|
||||
self.sock.send(step2)
|
||||
self.number += 1
|
||||
# Sending message visualisation to console
|
||||
print('>>')
|
||||
vis(step2)
|
||||
# print('>>')
|
||||
# vis(step2)
|
||||
|
||||
def recv_message(self):
|
||||
"""
|
||||
@ -237,27 +111,13 @@ class Session:
|
||||
packet_length_data = self.sock.recv(4) # reads how many bytes to read
|
||||
|
||||
if len(packet_length_data) > 0: # if we have smth. in the socket
|
||||
packet_length = struct.unpack("<L", packet_length_data)[0]
|
||||
packet_length = struct.unpack("<I", packet_length_data)[0]
|
||||
packet = self.sock.recv(packet_length - 4) # read the rest of bytes from socket
|
||||
|
||||
# Received message visualisation to console
|
||||
print('<<')
|
||||
vis(packet_length_data+packet)
|
||||
|
||||
(x, auth_key_id, message_id, message_length)= struct.unpack("<L8s8sI", packet[0:24])
|
||||
(x, auth_key_id, message_id, message_length)= struct.unpack("<I8s8sI", packet[0:24])
|
||||
data = packet[24:24+message_length]
|
||||
crc = packet[-4:]
|
||||
# print("crc is: " + str(crc))
|
||||
# print("type of crc: " + str(type(crc)))
|
||||
# print("crc.__repr__(): " + crc.__repr__())
|
||||
# print("struct.unpack('<L', crc): (next line)")
|
||||
# print(struct.unpack('<L', crc))
|
||||
# print("crc32(packet_length_data + packet[0:-4]): " + str(crc32(packet_length_data + packet[0:-4])))
|
||||
# print("crc32(packet_length_data + packet[0:-4]).__repr__(): " + crc32(packet_length_data + packet[0:-4]).__repr__())
|
||||
|
||||
# Checking the CRC32 correctness of received data
|
||||
if crc32(packet_length_data + packet[0:-4]) == struct.unpack('<L', crc)[0]:
|
||||
|
||||
if crc32(packet_length_data + packet[0:-4]) == struct.unpack('<I', crc)[0]:
|
||||
return data
|
||||
else:
|
||||
raise Exception("CRC32 was not correct!")
|
||||
@ -266,7 +126,7 @@ class Session:
|
||||
|
||||
def method_call(self, method, **kwargs):
|
||||
z=io.BytesIO()
|
||||
serialize_method(z, method, **kwargs)
|
||||
TL.serialize_method(z, method, **kwargs)
|
||||
# z.getvalue() on py2.7 returns str, which means bytes
|
||||
# on py3.4 returns bytes
|
||||
# bytearray is closer to the same data type to be shared
|
||||
@ -276,4 +136,137 @@ class Session:
|
||||
# print("len of z_val: " + str(len(z_val)))
|
||||
self.send_message(z_val)
|
||||
server_answer = self.recv_message()
|
||||
return deserialize(io.BytesIO(server_answer))
|
||||
return TL.deserialize(io.BytesIO(server_answer))
|
||||
|
||||
def create_auth_key(self):
|
||||
|
||||
nonce = os.urandom(16)
|
||||
print("Requesting PQ")
|
||||
|
||||
ResPQ = self.method_call('req_pq', nonce=nonce)
|
||||
server_nonce = ResPQ['server_nonce']
|
||||
|
||||
# TODO: selecting RSA public key based on this fingerprint
|
||||
public_key_fingerprint = ResPQ['server_public_key_fingerprints'][0]
|
||||
|
||||
PQ_bytes = ResPQ['pq']
|
||||
|
||||
# TODO: from_bytes here
|
||||
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)
|
||||
|
||||
f = open(os.path.join(os.path.dirname(__file__), "rsa.pub"))
|
||||
|
||||
key = RSA.importKey(f.read())
|
||||
|
||||
z = io.BytesIO()
|
||||
|
||||
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)
|
||||
data = z.getvalue()
|
||||
|
||||
sha_digest = SHA.new(data).digest()
|
||||
random_bytes = os.urandom(255-len(data)-len(sha_digest))
|
||||
to_encrypt = sha_digest + data + random_bytes
|
||||
encrypted_data = key.encrypt(to_encrypt, 0)[0]
|
||||
|
||||
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)
|
||||
assert nonce == server_DH_params['nonce']
|
||||
assert server_nonce == server_DH_params['server_nonce']
|
||||
|
||||
encrypted_answer = server_DH_params['encrypted_answer']
|
||||
|
||||
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_hash = answer_with_hash[:20]
|
||||
answer = answer_with_hash[20:]
|
||||
# TODO: SHA hash assertion here
|
||||
|
||||
server_DH_inner_data = TL.deserialize(io.BytesIO(answer))
|
||||
assert nonce == server_DH_inner_data['nonce']
|
||||
assert server_nonce == server_DH_inner_data['server_nonce']
|
||||
dh_prime_str = server_DH_inner_data['dh_prime']
|
||||
g = server_DH_inner_data['g']
|
||||
g_a_str = server_DH_inner_data['g_a']
|
||||
server_time = server_DH_inner_data['server_time']
|
||||
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')
|
||||
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)
|
||||
|
||||
g_b_str = int.to_bytes(g_b, g_b.bit_length() // 8 + 1, 'big')
|
||||
|
||||
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)
|
||||
|
||||
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]
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
|
||||
|
136
testing.py
136
testing.py
@ -7,14 +7,8 @@ try: # this only works in py2.7
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
from Crypto.Hash import SHA
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.strxor import strxor
|
||||
|
||||
# local modules
|
||||
import crypt
|
||||
import mtproto
|
||||
import prime
|
||||
|
||||
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
@ -25,130 +19,4 @@ if not config.read('credentials'):
|
||||
ip = config.get('App data', 'ip_address')
|
||||
port = config.getint('App data', 'port')
|
||||
|
||||
Session = mtproto.Session(ip, port)
|
||||
client_nonce = os.urandom(16)
|
||||
x = Session.method_call('req_pq', nonce=client_nonce)
|
||||
|
||||
server_nonce = x['server_nonce']
|
||||
public_key_fingerprint = x['server_public_key_fingerprints'][0]
|
||||
# TODO: selecting RSA public key based on this fingerprint
|
||||
PQ_bytes = x['pq']
|
||||
|
||||
# doing len(PQ_bytes) I saw it was 8 bytes, so we unpack with Q
|
||||
# as in the docs: https://docs.python.org/2/library/struct.html
|
||||
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("PQ = %d\np = %d, q = %d" % (PQ, p, q))
|
||||
|
||||
P_bytes = struct.pack('>i', p)
|
||||
Q_bytes = struct.pack('>i', q)
|
||||
|
||||
f = open('rsa.pub', 'r')
|
||||
key = RSA.importKey(f.read())
|
||||
|
||||
z = io.BytesIO()
|
||||
|
||||
new_nonce = os.urandom(32)
|
||||
|
||||
mtproto.serialize_obj(z, 'p_q_inner_data',
|
||||
pq=PQ_bytes,
|
||||
p=P_bytes,
|
||||
q=Q_bytes,
|
||||
nonce=client_nonce,
|
||||
server_nonce=server_nonce,
|
||||
new_nonce=new_nonce)
|
||||
data = z.getvalue()
|
||||
|
||||
sha_digest = SHA.new(data).digest()
|
||||
random_bytes = os.urandom(255-len(data)-len(sha_digest))
|
||||
to_encrypt = sha_digest + data + random_bytes
|
||||
encrypted_data = key.encrypt(to_encrypt, 0)[0]
|
||||
|
||||
server_DH_params = Session.method_call('req_DH_params',
|
||||
nonce=client_nonce, # 16 bytes
|
||||
server_nonce=server_nonce,
|
||||
p=P_bytes,
|
||||
q=Q_bytes,
|
||||
public_key_fingerprint=public_key_fingerprint,
|
||||
encrypted_data=encrypted_data)
|
||||
assert client_nonce == server_DH_params['nonce']
|
||||
assert server_nonce == server_DH_params['server_nonce']
|
||||
|
||||
encrypted_answer = server_DH_params['encrypted_answer']
|
||||
|
||||
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_hash = answer_with_hash[:20]
|
||||
answer = answer_with_hash[20:]
|
||||
mtproto.vis(answer) # To start off BA0D89 ...
|
||||
|
||||
server_DH_inner_data = mtproto.deserialize(io.BytesIO(answer))
|
||||
assert client_nonce == server_DH_inner_data['nonce']
|
||||
assert server_nonce == server_DH_inner_data['server_nonce']
|
||||
dh_prime_str = server_DH_inner_data['dh_prime']
|
||||
g = server_DH_inner_data['g']
|
||||
g_a_str = server_DH_inner_data['g_a']
|
||||
server_time = server_DH_inner_data['server_time']
|
||||
|
||||
|
||||
dh_prime = int.from_bytes(dh_prime_str,'big')
|
||||
g_a = int.from_bytes(g_a_str,'big')
|
||||
print(dh_prime)
|
||||
print(g)
|
||||
print(g_a)
|
||||
print(prime.isprime(dh_prime))
|
||||
|
||||
b_str = os.urandom(256)
|
||||
b = int.from_bytes(b_str,'big')
|
||||
g_b = pow(g,b,dh_prime)
|
||||
|
||||
g_b_str = int.to_bytes(g_b, g_b.bit_length() // 8 + 1, 'big')
|
||||
|
||||
|
||||
retry_id = 0
|
||||
z = io.BytesIO()
|
||||
mtproto.serialize_obj(z, 'client_DH_inner_data',
|
||||
nonce=client_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)
|
||||
|
||||
Set_client_DH_params_answer = Session.method_call('set_client_DH_params',
|
||||
nonce=client_nonce,
|
||||
server_nonce=server_nonce,
|
||||
encrypted_data=encrypted_data)
|
||||
print(encrypted_data)
|
||||
print(Set_client_DH_params_answer)
|
||||
|
||||
auth_key = pow(g_a, b, dh_prime)
|
||||
auth_key_str = int.to_bytes(auth_key, auth_key.bit_length() // 8 + 1, 'big')
|
||||
print("auth_key = %d" % 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:]
|
||||
|
||||
assert Set_client_DH_params_answer['nonce'] == client_nonce
|
||||
assert Set_client_DH_params_answer['server_nonce'] == server_nonce
|
||||
assert Set_client_DH_params_answer['new_nonce_hash1'] == new_nonce_hash1
|
||||
|
||||
Session.__del__()
|
||||
|
||||
server_salt = strxor(new_nonce[0:8], server_nonce[0:8])
|
||||
mtproto.vis(server_salt)
|
||||
Session = mtproto.Session(ip, port)
|
Loading…
Reference in New Issue
Block a user