mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-08 04:52:30 +00:00
Several fixes to authenticator, added more unit tests
Some fixes include, in more detail: - Using little over big endianess in some parts - Flagging all the constructor numbers as unsigned - Fixed bugs with factorizer - Implemented TLSharp's RSA
This commit is contained in:
0
utils/__init__.py
Normal file → Executable file
0
utils/__init__.py
Normal file → Executable file
0
utils/auth_key.py
Normal file → Executable file
0
utils/auth_key.py
Normal file → Executable file
20
utils/binary_reader.py
Normal file → Executable file
20
utils/binary_reader.py
Normal file → Executable file
@@ -21,34 +21,38 @@ class BinaryReader:
|
||||
|
||||
# region Reading
|
||||
|
||||
# "All numbers are written as little endian." |> Source: https://core.telegram.org/mtproto
|
||||
def read_byte(self):
|
||||
"""Reads a single byte value"""
|
||||
return self.reader.read(1)[0]
|
||||
return self.read(1)[0]
|
||||
|
||||
def read_int(self, signed=True):
|
||||
"""Reads an integer (4 bytes) value"""
|
||||
return int.from_bytes(self.reader.read(4), byteorder='big', signed=signed)
|
||||
return int.from_bytes(self.read(4), byteorder='little', signed=signed)
|
||||
|
||||
def read_long(self, signed=True):
|
||||
"""Reads a long integer (8 bytes) value"""
|
||||
return int.from_bytes(self.reader.read(8), byteorder='big', signed=signed)
|
||||
return int.from_bytes(self.read(8), byteorder='little', signed=signed)
|
||||
|
||||
# Network is always big-endian, this is, '>'
|
||||
def read_float(self):
|
||||
"""Reads a real floating point (4 bytes) value"""
|
||||
return unpack('>f', self.reader.read(4))[0]
|
||||
return unpack('<f', self.read(4))[0]
|
||||
|
||||
def read_double(self):
|
||||
"""Reads a real floating point (8 bytes) value"""
|
||||
return unpack('>d', self.reader.read(8))[0]
|
||||
return unpack('<d', self.read(8))[0]
|
||||
|
||||
def read_large_int(self, bits, signed=True):
|
||||
"""Reads a n-bits long integer value"""
|
||||
return int.from_bytes(self.reader.read(bits // 8), byteorder='big', signed=signed)
|
||||
return int.from_bytes(self.read(bits // 8), byteorder='little', signed=signed)
|
||||
|
||||
def read(self, length):
|
||||
"""Read the given amount of bytes"""
|
||||
return self.reader.read(length)
|
||||
result = self.reader.read(length)
|
||||
if len(result) != length:
|
||||
raise BufferError('Trying to read outside the data bounds (no more data left to read)')
|
||||
|
||||
return result
|
||||
|
||||
def get_bytes(self):
|
||||
"""Gets the byte array representing the current buffer as a whole"""
|
||||
|
16
utils/binary_writer.py
Normal file → Executable file
16
utils/binary_writer.py
Normal file → Executable file
@@ -17,34 +17,30 @@ class BinaryWriter:
|
||||
|
||||
# region Writing
|
||||
|
||||
# "All numbers are written as little endian." |> Source: https://core.telegram.org/mtproto
|
||||
def write_byte(self, value):
|
||||
"""Writes a single byte value"""
|
||||
self.writer.write(pack('B', value))
|
||||
|
||||
def write_int(self, value, signed=True):
|
||||
"""Writes an integer value (4 bytes), which can or cannot be signed"""
|
||||
if not signed:
|
||||
value &= 0xFFFFFFFF # Ensure it's unsigned (see http://stackoverflow.com/a/30092291/4759433)
|
||||
self.writer.write(int.to_bytes(value, length=4, byteorder='big', signed=signed))
|
||||
self.writer.write(int.to_bytes(value, length=4, byteorder='little', signed=signed))
|
||||
|
||||
def write_long(self, value, signed=True):
|
||||
"""Writes a long integer value (8 bytes), which can or cannot be signed"""
|
||||
if not signed:
|
||||
value &= 0xFFFFFFFFFFFFFFFF
|
||||
self.writer.write(int.to_bytes(value, length=8, byteorder='big', signed=signed))
|
||||
self.writer.write(int.to_bytes(value, length=8, byteorder='little', signed=signed))
|
||||
|
||||
# Network is always big-endian, this is, '>' when packing
|
||||
def write_float(self, value):
|
||||
"""Writes a floating point value (4 bytes)"""
|
||||
self.writer.write(pack('>f', value))
|
||||
self.writer.write(pack('<f', value))
|
||||
|
||||
def write_double(self, value):
|
||||
"""Writes a floating point value (8 bytes)"""
|
||||
self.writer.write(pack('>d', value))
|
||||
self.writer.write(pack('<d', value))
|
||||
|
||||
def write_large_int(self, value, bits, signed=True):
|
||||
"""Writes a n-bits long integer value"""
|
||||
self.writer.write(int.to_bytes(value, length=bits // 8, byteorder='big', signed=signed))
|
||||
self.writer.write(int.to_bytes(value, length=bits // 8, byteorder='little', signed=signed))
|
||||
|
||||
def write(self, data):
|
||||
"""Writes the given bytes array"""
|
||||
|
21
utils/factorizator.py
Normal file → Executable file
21
utils/factorizator.py
Normal file → Executable file
@@ -1,7 +1,6 @@
|
||||
# This file is based on TLSharp
|
||||
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/MTProto/Crypto/Factorizator.cs
|
||||
from random import randint
|
||||
from math import gcd
|
||||
|
||||
|
||||
class Factorizator:
|
||||
@@ -10,7 +9,7 @@ class Factorizator:
|
||||
g = 0
|
||||
for i in range(3):
|
||||
q = (randint(0, 127) & 15) + 17
|
||||
x = randint(1000000000) + 1
|
||||
x = randint(0, 1000000000) + 1
|
||||
y = x
|
||||
lim = 1 << (i + 18)
|
||||
for j in range(1, lim):
|
||||
@@ -27,7 +26,7 @@ class Factorizator:
|
||||
|
||||
x = c
|
||||
z = y - x if x < y else x - y
|
||||
g = gcd(z, what)
|
||||
g = Factorizator.gcd(z, what)
|
||||
if g != 1:
|
||||
break
|
||||
|
||||
@@ -40,6 +39,22 @@ class Factorizator:
|
||||
p = what // g
|
||||
return min(p, g)
|
||||
|
||||
@staticmethod
|
||||
def gcd(a, b):
|
||||
while a != 0 and b != 0:
|
||||
while b & 1 == 0:
|
||||
b >>= 1
|
||||
|
||||
while a & 1 == 0:
|
||||
a >>= 1
|
||||
|
||||
if a > b:
|
||||
a -= b
|
||||
else:
|
||||
b -= a
|
||||
|
||||
return a if b == 0 else b
|
||||
|
||||
@staticmethod
|
||||
def factorize(pq):
|
||||
divisor = Factorizator.find_small_multiplier_lopatin(pq)
|
||||
|
5
utils/helpers.py
Normal file → Executable file
5
utils/helpers.py
Normal file → Executable file
@@ -8,7 +8,7 @@ bits_per_byte = 8
|
||||
|
||||
def generate_random_long(signed=True):
|
||||
"""Generates a random long integer (8 bytes), which is optionally signed"""
|
||||
return int.from_bytes(os.urandom(8), signed=signed, byteorder='big')
|
||||
return int.from_bytes(os.urandom(8), signed=signed, byteorder='little')
|
||||
|
||||
|
||||
def generate_random_bytes(count):
|
||||
@@ -19,6 +19,7 @@ def generate_random_bytes(count):
|
||||
def get_byte_array(integer, signed):
|
||||
bits = integer.bit_length()
|
||||
byte_length = (bits + bits_per_byte - 1) // bits_per_byte
|
||||
# For some strange reason, this has to be big!
|
||||
return int.to_bytes(integer, length=byte_length, byteorder='big', signed=signed)
|
||||
|
||||
|
||||
@@ -65,6 +66,6 @@ def generate_key_data_from_nonces(server_nonce, new_nonce):
|
||||
|
||||
|
||||
def sha1(data):
|
||||
sha = hashlib.sha1
|
||||
sha = hashlib.sha1()
|
||||
sha.update(data)
|
||||
return sha.digest()
|
||||
|
62
utils/rsa.py
Executable file
62
utils/rsa.py
Executable file
@@ -0,0 +1,62 @@
|
||||
# This file is based on TLSharp
|
||||
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/Auth/Authenticator.cs
|
||||
from utils.binary_writer import BinaryWriter
|
||||
import utils.helpers as utils
|
||||
|
||||
|
||||
class RSAServerKey:
|
||||
def __init__(self, fingerprint, m, e):
|
||||
self.fingerprint = fingerprint
|
||||
self.m = m
|
||||
self.e = e
|
||||
|
||||
def encrypt(self, data, offset=None, length=None):
|
||||
if offset is None:
|
||||
offset = 0
|
||||
if length is None:
|
||||
length = len(data)
|
||||
|
||||
with BinaryWriter() as writer:
|
||||
# Write SHA
|
||||
writer.write(utils.sha1(data[offset:offset+length]))
|
||||
# Write data
|
||||
writer.write(data[offset:offset+length])
|
||||
# Add padding if required
|
||||
if length < 235:
|
||||
writer.write(utils.generate_random_bytes(235 - length))
|
||||
|
||||
cipher_text = utils.get_byte_array(
|
||||
pow(int.from_bytes(writer.get_bytes(), byteorder='big'), self.e, self.m),
|
||||
signed=False)
|
||||
|
||||
if len(cipher_text) == 256:
|
||||
return cipher_text
|
||||
|
||||
else:
|
||||
padding = bytes(256 - len(cipher_text))
|
||||
return padding + cipher_text
|
||||
|
||||
|
||||
|
||||
|
||||
class RSA:
|
||||
_server_keys = {
|
||||
'216be86c022bb4c3':
|
||||
RSAServerKey('216be86c022bb4c3', int('C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9'
|
||||
'1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E'
|
||||
'580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F'
|
||||
'9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934'
|
||||
'EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F'
|
||||
'81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F'
|
||||
'6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1'
|
||||
'5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F',
|
||||
16), int('010001', 16))
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def encrypt(fingerprint, data, offset=None, length=None):
|
||||
if fingerprint.lower() not in RSA._server_keys:
|
||||
return None
|
||||
|
||||
key = RSA._server_keys[fingerprint.lower()]
|
||||
return key.encrypt(data, offset, length)
|
Reference in New Issue
Block a user