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
|
@author: Sammy Pfeiffer
|
||||||
"""
|
"""
|
||||||
from binascii import crc32 as originalcrc32
|
from binascii import crc32 as originalcrc32
|
||||||
MIN_SUPPORTED_PY3_VERSION = (3, 2, 0)
|
|
||||||
import numbers
|
import numbers
|
||||||
def crc32(data):
|
|
||||||
return originalcrc32(data) & 0xffffffff
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import time
|
from time import time
|
||||||
import io
|
import io
|
||||||
@ -18,6 +15,19 @@ import json
|
|||||||
import socket
|
import socket
|
||||||
import struct
|
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):
|
def vis(bs):
|
||||||
"""
|
"""
|
||||||
Function to visualize byte streams. Split into bytes, print to console.
|
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
|
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:
|
class Session:
|
||||||
""" Manages TCP Transport. encryption and message frames """
|
""" 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
|
# creating socket
|
||||||
self.sock = socket.socket()
|
self.sock = socket.socket()
|
||||||
self.sock.connect((ip, port))
|
self.sock.connect((ip, port))
|
||||||
self.auth_key_id = auth_key_id
|
|
||||||
self.number = 0
|
self.number = 0
|
||||||
|
|
||||||
|
if auth_key is None:
|
||||||
|
self.create_auth_key()
|
||||||
|
else:
|
||||||
|
self.auth_key = auth_key
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
# closing socket when session object is deleted
|
# closing socket when session object is deleted
|
||||||
self.sock.close()
|
self.sock.close()
|
||||||
@ -203,7 +79,7 @@ class Session:
|
|||||||
|
|
||||||
return (b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
return (b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
||||||
struct.pack('<Q', msg_id) +
|
struct.pack('<Q', msg_id) +
|
||||||
struct.pack('<L', len(message)))
|
struct.pack('<I', len(message)))
|
||||||
|
|
||||||
# TCP Transport
|
# TCP Transport
|
||||||
|
|
||||||
@ -219,16 +95,14 @@ class Session:
|
|||||||
Forming the message frame and sending message to server
|
Forming the message frame and sending message to server
|
||||||
:param message: byte string to send
|
:param message: byte string to send
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
data = self.header_unencrypted(message) + message
|
data = self.header_unencrypted(message) + message
|
||||||
step1 = struct.pack('<LL', len(data)+12, self.number) + data
|
step1 = struct.pack('<II', len(data)+12, self.number) + data
|
||||||
step2 = step1 + struct.pack('<L', crc32(step1))
|
step2 = step1 + struct.pack('<I', crc32(step1))
|
||||||
self.sock.send(step2)
|
self.sock.send(step2)
|
||||||
self.number += 1
|
self.number += 1
|
||||||
# Sending message visualisation to console
|
# Sending message visualisation to console
|
||||||
print('>>')
|
# print('>>')
|
||||||
vis(step2)
|
# vis(step2)
|
||||||
|
|
||||||
def recv_message(self):
|
def recv_message(self):
|
||||||
"""
|
"""
|
||||||
@ -237,27 +111,13 @@ class Session:
|
|||||||
packet_length_data = self.sock.recv(4) # reads how many bytes to read
|
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
|
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
|
packet = self.sock.recv(packet_length - 4) # read the rest of bytes from socket
|
||||||
|
(x, auth_key_id, message_id, message_length)= struct.unpack("<I8s8sI", packet[0:24])
|
||||||
# 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])
|
|
||||||
data = packet[24:24+message_length]
|
data = packet[24:24+message_length]
|
||||||
crc = packet[-4:]
|
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
|
# 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
|
return data
|
||||||
else:
|
else:
|
||||||
raise Exception("CRC32 was not correct!")
|
raise Exception("CRC32 was not correct!")
|
||||||
@ -266,7 +126,7 @@ class Session:
|
|||||||
|
|
||||||
def method_call(self, method, **kwargs):
|
def method_call(self, method, **kwargs):
|
||||||
z=io.BytesIO()
|
z=io.BytesIO()
|
||||||
serialize_method(z, method, **kwargs)
|
TL.serialize_method(z, method, **kwargs)
|
||||||
# z.getvalue() on py2.7 returns str, which means bytes
|
# z.getvalue() on py2.7 returns str, which means bytes
|
||||||
# on py3.4 returns bytes
|
# on py3.4 returns bytes
|
||||||
# bytearray is closer to the same data type to be shared
|
# 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)))
|
# print("len of z_val: " + str(len(z_val)))
|
||||||
self.send_message(z_val)
|
self.send_message(z_val)
|
||||||
server_answer = self.recv_message()
|
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
|
import configparser
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import ConfigParser as configparser
|
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 mtproto
|
||||||
import prime
|
|
||||||
|
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
@ -25,130 +19,4 @@ if not config.read('credentials'):
|
|||||||
ip = config.get('App data', 'ip_address')
|
ip = config.get('App data', 'ip_address')
|
||||||
port = config.getint('App data', 'port')
|
port = config.getint('App data', 'port')
|
||||||
|
|
||||||
Session = mtproto.Session(ip, 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)
|
|
Loading…
Reference in New Issue
Block a user