mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2026-09-02 07:00:43 +00:00
Initial release
The initial release contains the most basic implementation of TLSharp core. This is also fully untested, since no test can be done until more work is done.
This commit is contained in:
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
87
utils/binary_reader.py
Normal file
87
utils/binary_reader.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from io import BytesIO, BufferedReader
|
||||
import os
|
||||
|
||||
|
||||
class BinaryReader:
|
||||
"""
|
||||
Small utility class to read binary data.
|
||||
Also creates a "Memory Stream" if necessary
|
||||
"""
|
||||
def __init__(self, data=None, stream=None):
|
||||
if data:
|
||||
self.stream = BytesIO(data)
|
||||
elif stream:
|
||||
self.stream = stream
|
||||
else:
|
||||
raise ValueError("Either bytes or a stream must be provided")
|
||||
|
||||
self.reader = BufferedReader(self.stream)
|
||||
|
||||
# region Reading
|
||||
|
||||
def read_int(self, signed=True):
|
||||
return int.from_bytes(self.reader.read(4), signed=signed, byteorder='big')
|
||||
|
||||
def read_long(self, signed=True):
|
||||
return int.from_bytes(self.reader.read(8), signed=signed, byteorder='big')
|
||||
|
||||
def read(self, length):
|
||||
return self.reader.read(length)
|
||||
|
||||
def get_bytes(self):
|
||||
return self.stream.getbuffer()
|
||||
|
||||
# endregion
|
||||
|
||||
# region Telegram custom reading
|
||||
|
||||
def tgread_bytes(self):
|
||||
first_byte = self.read(1)
|
||||
if first_byte == 254:
|
||||
length = self.read(1) | (self.read(1) << 8) | (self.read(1) << 16)
|
||||
padding = length % 4
|
||||
else:
|
||||
length = first_byte
|
||||
padding = (length + 1) % 4
|
||||
|
||||
data = self.read(length)
|
||||
if padding > 0:
|
||||
padding = 4 - padding
|
||||
self.read(padding)
|
||||
|
||||
return data
|
||||
|
||||
def tgread_string(self):
|
||||
return str(self.tgread_bytes(), encoding='utf-8')
|
||||
|
||||
# endregion
|
||||
|
||||
def close(self):
|
||||
self.reader.close()
|
||||
# TODO Do I need to close the underlying stream?
|
||||
|
||||
# region Position related
|
||||
|
||||
def tell_position(self):
|
||||
"""Tells the current position on the stream"""
|
||||
return self.reader.tell()
|
||||
|
||||
def set_position(self, position):
|
||||
"""Sets the current position on the stream"""
|
||||
self.reader.seek(position)
|
||||
|
||||
def seek(self, offset):
|
||||
"""Seeks the stream position given an offset from the current position. May be negative"""
|
||||
self.reader.seek(offset, os.SEEK_CUR)
|
||||
|
||||
# endregion
|
||||
|
||||
# region with block
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
|
||||
# endregion
|
||||
93
utils/binary_writer.py
Normal file
93
utils/binary_writer.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from io import BytesIO, BufferedWriter
|
||||
from struct import pack
|
||||
|
||||
|
||||
class BinaryWriter:
|
||||
"""
|
||||
Small utility class to write binary data.
|
||||
Also creates a "Memory Stream" if necessary
|
||||
"""
|
||||
|
||||
def __init__(self, stream=None):
|
||||
if not stream:
|
||||
stream = BytesIO()
|
||||
|
||||
self.stream = stream
|
||||
self.writer = BufferedWriter(self.stream)
|
||||
|
||||
# region Writing
|
||||
|
||||
def write_byte(self, byte):
|
||||
self.writer.write(pack('B', byte))
|
||||
|
||||
def write_int(self, integer, signed=True):
|
||||
if not signed:
|
||||
integer &= 0xFFFFFFFF # Ensure it's unsigned (see http://stackoverflow.com/a/30092291/4759433)
|
||||
self.writer.write(pack('I', integer))
|
||||
|
||||
def write_long(self, long, signed=True):
|
||||
if not signed:
|
||||
long &= 0xFFFFFFFFFFFFFFFF
|
||||
self.writer.write(pack('Q', long))
|
||||
|
||||
def write(self, data):
|
||||
self.writer.write(data)
|
||||
|
||||
# endregion
|
||||
|
||||
# region Telegram custom writing
|
||||
|
||||
def tgwrite_bytes(self, data):
|
||||
|
||||
if len(data) < 254:
|
||||
padding = (len(data) + 1) % 4
|
||||
if padding != 0:
|
||||
padding = 4 - padding
|
||||
|
||||
self.write(bytes([len(data)]))
|
||||
self.write(data)
|
||||
|
||||
else:
|
||||
padding = len(data) % 4
|
||||
if padding != 0:
|
||||
padding = 4 - padding
|
||||
|
||||
# TODO ensure that _this_ is right (it appears to be)
|
||||
self.write(bytes([254]))
|
||||
self.write(bytes([len(data) % 256]))
|
||||
self.write(bytes([(len(data) >> 8) % 256]))
|
||||
self.write(bytes([(len(data) >> 16) % 256]))
|
||||
self.write(data)
|
||||
|
||||
""" Original:
|
||||
binaryWriter.Write((byte)254);
|
||||
binaryWriter.Write((byte)(bytes.Length));
|
||||
binaryWriter.Write((byte)(bytes.Length >> 8));
|
||||
binaryWriter.Write((byte)(bytes.Length >> 16));
|
||||
"""
|
||||
|
||||
self.write(bytes(padding))
|
||||
|
||||
def tgwrite_string(self, string):
|
||||
return self.tgwrite_bytes(string.encode('utf-8'))
|
||||
|
||||
# endregion
|
||||
|
||||
def flush(self):
|
||||
self.writer.flush()
|
||||
|
||||
def close(self):
|
||||
self.writer.close()
|
||||
# TODO Do I need to close the underlying stream?
|
||||
|
||||
def get_bytes(self, flush=True):
|
||||
if flush:
|
||||
self.writer.flush()
|
||||
self.stream.getbuffer()
|
||||
|
||||
# with block
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
56
utils/helpers.py
Normal file
56
utils/helpers.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import random
|
||||
from utils.binary_writer import BinaryWriter
|
||||
from hashlib import sha1
|
||||
|
||||
|
||||
def generate_random_long(signed=True):
|
||||
result = random.getrandbits(64)
|
||||
if not signed:
|
||||
result &= 0xFFFFFFFFFFFFFFFF # Ensure it's unsigned
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_random_bytes(count):
|
||||
with BinaryWriter() as writer:
|
||||
for _ in range(count):
|
||||
writer.write(random.getrandbits(8))
|
||||
|
||||
return writer.get_bytes()
|
||||
|
||||
|
||||
def calc_key(shared_key, msg_key, client):
|
||||
x = 0 if client else 8
|
||||
|
||||
buffer = [0] * 48
|
||||
buffer[0:16] = msg_key
|
||||
buffer[16:48] = shared_key[x:x + 32]
|
||||
sha1a = sha1(buffer)
|
||||
|
||||
buffer[0:16] = shared_key[x + 32:x + 48]
|
||||
buffer[16:32] = msg_key
|
||||
buffer[32:48] = shared_key[x + 48:x + 64]
|
||||
sha1b = sha1(buffer)
|
||||
|
||||
buffer[0:32] = shared_key[x + 64:x + 96]
|
||||
buffer[32:48] = msg_key
|
||||
sha1c = sha1(buffer)
|
||||
|
||||
buffer[0:16] = msg_key
|
||||
buffer[16:48] = shared_key[x + 96:x + 128]
|
||||
sha1d = sha1(buffer)
|
||||
|
||||
key = sha1a[0:8] + sha1b[8:20] + sha1c[4:16]
|
||||
iv = sha1a[8:20] + sha1b[0:8] + sha1c[16:20] + sha1d[0:8]
|
||||
|
||||
return key, iv
|
||||
|
||||
|
||||
def calc_msg_key(data):
|
||||
return sha1(data)[4:20]
|
||||
|
||||
|
||||
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]
|
||||
Reference in New Issue
Block a user