mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-09 13:29:47 +00:00
Rename more subpackages and modules
This commit is contained in:
10
telethon/_crypto/__init__.py
Normal file
10
telethon/_crypto/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
This module contains several utilities regarding cryptographic purposes,
|
||||
such as the AES IGE mode used by Telegram, the authorization key bound with
|
||||
their data centers, and so on.
|
||||
"""
|
||||
from .aes import AES
|
||||
from .aesctr import AESModeCTR
|
||||
from .authkey import AuthKey
|
||||
from .factorization import Factorization
|
||||
from .cdndecrypter import CdnDecrypter
|
111
telethon/_crypto/aes.py
Normal file
111
telethon/_crypto/aes.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
AES IGE implementation in Python.
|
||||
|
||||
If available, cryptg will be used instead, otherwise
|
||||
if available, libssl will be used instead, otherwise
|
||||
the Python implementation will be used.
|
||||
"""
|
||||
import os
|
||||
import pyaes
|
||||
import logging
|
||||
from . import libssl
|
||||
|
||||
|
||||
__log__ = logging.getLogger(__name__)
|
||||
|
||||
|
||||
try:
|
||||
import cryptg
|
||||
__log__.info('cryptg detected, it will be used for encryption')
|
||||
except ImportError:
|
||||
cryptg = None
|
||||
if libssl.encrypt_ige and libssl.decrypt_ige:
|
||||
__log__.info('libssl detected, it will be used for encryption')
|
||||
else:
|
||||
__log__.info('cryptg module not installed and libssl not found, '
|
||||
'falling back to (slower) Python encryption')
|
||||
|
||||
|
||||
class AES:
|
||||
"""
|
||||
Class that servers as an interface to encrypt and decrypt
|
||||
text through the AES IGE mode.
|
||||
"""
|
||||
@staticmethod
|
||||
def decrypt_ige(cipher_text, key, iv):
|
||||
"""
|
||||
Decrypts the given text in 16-bytes blocks by using the
|
||||
given key and 32-bytes initialization vector.
|
||||
"""
|
||||
if cryptg:
|
||||
return cryptg.decrypt_ige(cipher_text, key, iv)
|
||||
if libssl.decrypt_ige:
|
||||
return libssl.decrypt_ige(cipher_text, key, iv)
|
||||
|
||||
iv1 = iv[:len(iv) // 2]
|
||||
iv2 = iv[len(iv) // 2:]
|
||||
|
||||
aes = pyaes.AES(key)
|
||||
|
||||
plain_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
|
||||
|
||||
plain_text.extend(plain_text_block)
|
||||
|
||||
return bytes(plain_text)
|
||||
|
||||
@staticmethod
|
||||
def encrypt_ige(plain_text, key, iv):
|
||||
"""
|
||||
Encrypts the given text in 16-bytes blocks by using the
|
||||
given key and 32-bytes initialization vector.
|
||||
"""
|
||||
padding = len(plain_text) % 16
|
||||
if padding:
|
||||
plain_text += os.urandom(16 - padding)
|
||||
|
||||
if cryptg:
|
||||
return cryptg.encrypt_ige(plain_text, key, iv)
|
||||
if libssl.encrypt_ige:
|
||||
return libssl.encrypt_ige(plain_text, key, iv)
|
||||
|
||||
iv1 = iv[:len(iv) // 2]
|
||||
iv2 = iv[len(iv) // 2:]
|
||||
|
||||
aes = pyaes.AES(key)
|
||||
|
||||
cipher_text = []
|
||||
blocks_count = len(plain_text) // 16
|
||||
|
||||
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
|
||||
iv2 = plain_text[block_index * 16:block_index * 16 + 16]
|
||||
|
||||
cipher_text.extend(cipher_text_block)
|
||||
|
||||
return bytes(cipher_text)
|
42
telethon/_crypto/aesctr.py
Normal file
42
telethon/_crypto/aesctr.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
This module holds the AESModeCTR wrapper class.
|
||||
"""
|
||||
import pyaes
|
||||
|
||||
|
||||
class AESModeCTR:
|
||||
"""Wrapper around pyaes.AESModeOfOperationCTR mode with custom IV"""
|
||||
# TODO Maybe make a pull request to pyaes to support iv on CTR
|
||||
|
||||
def __init__(self, key, iv):
|
||||
"""
|
||||
Initializes the AES CTR mode with the given key/iv pair.
|
||||
|
||||
:param key: the key to be used as bytes.
|
||||
:param iv: the bytes initialization vector. Must have a length of 16.
|
||||
"""
|
||||
# TODO Use libssl if available
|
||||
assert isinstance(key, bytes)
|
||||
self._aes = pyaes.AESModeOfOperationCTR(key)
|
||||
|
||||
assert isinstance(iv, bytes)
|
||||
assert len(iv) == 16
|
||||
self._aes._counter._counter = list(iv)
|
||||
|
||||
def encrypt(self, data):
|
||||
"""
|
||||
Encrypts the given plain text through AES CTR.
|
||||
|
||||
:param data: the plain text to be encrypted.
|
||||
:return: the encrypted cipher text.
|
||||
"""
|
||||
return self._aes.encrypt(data)
|
||||
|
||||
def decrypt(self, data):
|
||||
"""
|
||||
Decrypts the given cipher text through AES CTR
|
||||
|
||||
:param data: the cipher text to be decrypted.
|
||||
:return: the decrypted plain text.
|
||||
"""
|
||||
return self._aes.decrypt(data)
|
63
telethon/_crypto/authkey.py
Normal file
63
telethon/_crypto/authkey.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
This module holds the AuthKey class.
|
||||
"""
|
||||
import struct
|
||||
from hashlib import sha1
|
||||
|
||||
from ..extensions import BinaryReader
|
||||
|
||||
|
||||
class AuthKey:
|
||||
"""
|
||||
Represents an authorization key, used to encrypt and decrypt
|
||||
messages sent to Telegram's data centers.
|
||||
"""
|
||||
def __init__(self, data):
|
||||
"""
|
||||
Initializes a new authorization key.
|
||||
|
||||
:param data: the data in bytes that represent this auth key.
|
||||
"""
|
||||
self.key = data
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self._key
|
||||
|
||||
@key.setter
|
||||
def key(self, value):
|
||||
if not value:
|
||||
self._key = self.aux_hash = self.key_id = None
|
||||
return
|
||||
|
||||
if isinstance(value, type(self)):
|
||||
self._key, self.aux_hash, self.key_id = \
|
||||
value._key, value.aux_hash, value.key_id
|
||||
return
|
||||
|
||||
self._key = value
|
||||
with BinaryReader(sha1(self._key).digest()) as reader:
|
||||
self.aux_hash = reader.read_long(signed=False)
|
||||
reader.read(4)
|
||||
self.key_id = reader.read_long(signed=False)
|
||||
|
||||
# TODO This doesn't really fit here, it's only used in authentication
|
||||
def calc_new_nonce_hash(self, new_nonce, number):
|
||||
"""
|
||||
Calculates the new nonce hash based on the current attributes.
|
||||
|
||||
:param new_nonce: the new nonce to be hashed.
|
||||
:param number: number to prepend before the hash.
|
||||
:return: the hash for the given new nonce.
|
||||
"""
|
||||
new_nonce = new_nonce.to_bytes(32, 'little', signed=True)
|
||||
data = new_nonce + struct.pack('<BQ', number, self.aux_hash)
|
||||
|
||||
# Calculates the message key from the given data
|
||||
return int.from_bytes(sha1(data).digest()[4:20], 'little', signed=True)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._key)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, type(self)) and other.key == self._key
|
105
telethon/_crypto/cdndecrypter.py
Normal file
105
telethon/_crypto/cdndecrypter.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
This module holds the CdnDecrypter utility class.
|
||||
"""
|
||||
from hashlib import sha256
|
||||
|
||||
from ..tl.functions.upload import GetCdnFileRequest, ReuploadCdnFileRequest
|
||||
from ..tl.types.upload import CdnFileReuploadNeeded, CdnFile
|
||||
from ..crypto import AESModeCTR
|
||||
from ..errors import CdnFileTamperedError
|
||||
|
||||
|
||||
class CdnDecrypter:
|
||||
"""
|
||||
Used when downloading a file results in a 'FileCdnRedirect' to
|
||||
both prepare the redirect, decrypt the file as it downloads, and
|
||||
ensure the file hasn't been tampered. https://core.telegram.org/cdn
|
||||
"""
|
||||
def __init__(self, cdn_client, file_token, cdn_aes, cdn_file_hashes):
|
||||
"""
|
||||
Initializes the CDN decrypter.
|
||||
|
||||
:param cdn_client: a client connected to a CDN.
|
||||
:param file_token: the token of the file to be used.
|
||||
:param cdn_aes: the AES CTR used to decrypt the file.
|
||||
:param cdn_file_hashes: the hashes the decrypted file must match.
|
||||
"""
|
||||
self.client = cdn_client
|
||||
self.file_token = file_token
|
||||
self.cdn_aes = cdn_aes
|
||||
self.cdn_file_hashes = cdn_file_hashes
|
||||
|
||||
@staticmethod
|
||||
async def prepare_decrypter(client, cdn_client, cdn_redirect):
|
||||
"""
|
||||
Prepares a new CDN decrypter.
|
||||
|
||||
:param client: a TelegramClient connected to the main servers.
|
||||
:param cdn_client: a new client connected to the CDN.
|
||||
:param cdn_redirect: the redirect file object that caused this call.
|
||||
:return: (CdnDecrypter, first chunk file data)
|
||||
"""
|
||||
cdn_aes = AESModeCTR(
|
||||
key=cdn_redirect.encryption_key,
|
||||
# 12 first bytes of the IV..4 bytes of the offset (0, big endian)
|
||||
iv=cdn_redirect.encryption_iv[:12] + bytes(4)
|
||||
)
|
||||
|
||||
# We assume that cdn_redirect.cdn_file_hashes are ordered by offset,
|
||||
# and that there will be enough of these to retrieve the whole file.
|
||||
decrypter = CdnDecrypter(
|
||||
cdn_client, cdn_redirect.file_token,
|
||||
cdn_aes, cdn_redirect.cdn_file_hashes
|
||||
)
|
||||
|
||||
cdn_file = await cdn_client(GetCdnFileRequest(
|
||||
file_token=cdn_redirect.file_token,
|
||||
offset=cdn_redirect.cdn_file_hashes[0].offset,
|
||||
limit=cdn_redirect.cdn_file_hashes[0].limit
|
||||
))
|
||||
if isinstance(cdn_file, CdnFileReuploadNeeded):
|
||||
# We need to use the original client here
|
||||
await client(ReuploadCdnFileRequest(
|
||||
file_token=cdn_redirect.file_token,
|
||||
request_token=cdn_file.request_token
|
||||
))
|
||||
|
||||
# We want to always return a valid upload.CdnFile
|
||||
cdn_file = decrypter.get_file()
|
||||
else:
|
||||
cdn_file.bytes = decrypter.cdn_aes.encrypt(cdn_file.bytes)
|
||||
cdn_hash = decrypter.cdn_file_hashes.pop(0)
|
||||
decrypter.check(cdn_file.bytes, cdn_hash)
|
||||
|
||||
return decrypter, cdn_file
|
||||
|
||||
def get_file(self):
|
||||
"""
|
||||
Calls GetCdnFileRequest and decrypts its bytes.
|
||||
Also ensures that the file hasn't been tampered.
|
||||
|
||||
:return: the CdnFile result.
|
||||
"""
|
||||
if self.cdn_file_hashes:
|
||||
cdn_hash = self.cdn_file_hashes.pop(0)
|
||||
cdn_file = self.client(GetCdnFileRequest(
|
||||
self.file_token, cdn_hash.offset, cdn_hash.limit
|
||||
))
|
||||
cdn_file.bytes = self.cdn_aes.encrypt(cdn_file.bytes)
|
||||
self.check(cdn_file.bytes, cdn_hash)
|
||||
else:
|
||||
cdn_file = CdnFile(bytes(0))
|
||||
|
||||
return cdn_file
|
||||
|
||||
@staticmethod
|
||||
def check(data, cdn_hash):
|
||||
"""
|
||||
Checks the integrity of the given data.
|
||||
Raises CdnFileTamperedError if the integrity check fails.
|
||||
|
||||
:param data: the data to be hashed.
|
||||
:param cdn_hash: the expected hash.
|
||||
"""
|
||||
if sha256(data).digest() != cdn_hash.hash:
|
||||
raise CdnFileTamperedError()
|
67
telethon/_crypto/factorization.py
Normal file
67
telethon/_crypto/factorization.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
This module holds a fast Factorization class.
|
||||
"""
|
||||
from random import randint
|
||||
|
||||
|
||||
class Factorization:
|
||||
"""
|
||||
Simple module to factorize large numbers really quickly.
|
||||
"""
|
||||
@classmethod
|
||||
def factorize(cls, pq):
|
||||
"""
|
||||
Factorizes the given large integer.
|
||||
|
||||
Implementation from https://comeoncodeon.wordpress.com/2010/09/18/pollard-rho-brent-integer-factorization/.
|
||||
|
||||
:param pq: the prime pair pq.
|
||||
:return: a tuple containing the two factors p and q.
|
||||
"""
|
||||
if pq % 2 == 0:
|
||||
return 2, pq // 2
|
||||
|
||||
y, c, m = randint(1, pq - 1), randint(1, pq - 1), randint(1, pq - 1)
|
||||
g = r = q = 1
|
||||
x = ys = 0
|
||||
|
||||
while g == 1:
|
||||
x = y
|
||||
for i in range(r):
|
||||
y = (pow(y, 2, pq) + c) % pq
|
||||
|
||||
k = 0
|
||||
while k < r and g == 1:
|
||||
ys = y
|
||||
for i in range(min(m, r - k)):
|
||||
y = (pow(y, 2, pq) + c) % pq
|
||||
q = q * (abs(x - y)) % pq
|
||||
|
||||
g = cls.gcd(q, pq)
|
||||
k += m
|
||||
|
||||
r *= 2
|
||||
|
||||
if g == pq:
|
||||
while True:
|
||||
ys = (pow(ys, 2, pq) + c) % pq
|
||||
g = cls.gcd(abs(x - ys), pq)
|
||||
if g > 1:
|
||||
break
|
||||
|
||||
p, q = g, pq // g
|
||||
return (p, q) if p < q else (q, p)
|
||||
|
||||
@staticmethod
|
||||
def gcd(a, b):
|
||||
"""
|
||||
Calculates the Greatest Common Divisor.
|
||||
|
||||
:param a: the first number.
|
||||
:param b: the second number.
|
||||
:return: GCD(a, b)
|
||||
"""
|
||||
while b:
|
||||
a, b = b, a % b
|
||||
|
||||
return a
|
140
telethon/_crypto/libssl.py
Normal file
140
telethon/_crypto/libssl.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
Helper module around the system's libssl library if available for IGE mode.
|
||||
"""
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import platform
|
||||
import sys
|
||||
try:
|
||||
import ctypes.macholib.dyld
|
||||
except ImportError:
|
||||
pass
|
||||
import logging
|
||||
import os
|
||||
|
||||
__log__ = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _find_ssl_lib():
|
||||
lib = ctypes.util.find_library('ssl')
|
||||
# macOS 10.15 segfaults on unversioned crypto libraries.
|
||||
# We therefore pin the current stable version here
|
||||
# Credit for fix goes to Sarah Harvey (@worldwise001)
|
||||
# https://www.shh.sh/2020/01/04/python-abort-trap-6.html
|
||||
if sys.platform == 'darwin':
|
||||
release, _version_info, _machine = platform.mac_ver()
|
||||
ver, major, *_ = release.split('.')
|
||||
# macOS 10.14 "mojave" is the last known major release
|
||||
# to support unversioned libssl.dylib. Anything above
|
||||
# needs specific versions
|
||||
if int(ver) > 10 or int(ver) == 10 and int(major) > 14:
|
||||
lib = (
|
||||
ctypes.util.find_library('libssl.46') or
|
||||
ctypes.util.find_library('libssl.44') or
|
||||
ctypes.util.find_library('libssl.42')
|
||||
)
|
||||
if not lib:
|
||||
raise OSError('no library called "ssl" found')
|
||||
|
||||
# First, let ctypes try to handle it itself.
|
||||
try:
|
||||
libssl = ctypes.cdll.LoadLibrary(lib)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
return libssl
|
||||
|
||||
# This is a best-effort attempt at finding the full real path of lib.
|
||||
#
|
||||
# Unfortunately ctypes doesn't tell us *where* it finds the library,
|
||||
# so we have to do that ourselves.
|
||||
try:
|
||||
# This is not documented, so it could fail. Be on the safe side.
|
||||
paths = ctypes.macholib.dyld.DEFAULT_LIBRARY_FALLBACK
|
||||
except AttributeError:
|
||||
paths = [
|
||||
os.path.expanduser("~/lib"),
|
||||
"/usr/local/lib",
|
||||
"/lib",
|
||||
"/usr/lib",
|
||||
]
|
||||
|
||||
for path in paths:
|
||||
if os.path.isdir(path):
|
||||
for root, _, files in os.walk(path):
|
||||
if lib in files:
|
||||
# Manually follow symbolic links on *nix systems.
|
||||
# Fix for https://github.com/LonamiWebs/Telethon/issues/1167
|
||||
lib = os.path.realpath(os.path.join(root, lib))
|
||||
return ctypes.cdll.LoadLibrary(lib)
|
||||
else:
|
||||
raise OSError('no absolute path for "%s" and cannot load by name' % lib)
|
||||
|
||||
|
||||
try:
|
||||
_libssl = _find_ssl_lib()
|
||||
except OSError as e:
|
||||
# See https://github.com/LonamiWebs/Telethon/issues/1167
|
||||
# Sometimes `find_library` returns improper filenames.
|
||||
__log__.info('Failed to load SSL library: %s (%s)', type(e), e)
|
||||
_libssl = None
|
||||
|
||||
if not _libssl:
|
||||
decrypt_ige = None
|
||||
encrypt_ige = None
|
||||
else:
|
||||
# https://github.com/openssl/openssl/blob/master/include/openssl/aes.h
|
||||
AES_ENCRYPT = ctypes.c_int(1)
|
||||
AES_DECRYPT = ctypes.c_int(0)
|
||||
AES_MAXNR = 14
|
||||
|
||||
class AES_KEY(ctypes.Structure):
|
||||
"""Helper class representing an AES key"""
|
||||
_fields_ = [
|
||||
('rd_key', ctypes.c_uint32 * (4 * (AES_MAXNR + 1))),
|
||||
('rounds', ctypes.c_uint),
|
||||
]
|
||||
|
||||
def decrypt_ige(cipher_text, key, iv):
|
||||
aes_key = AES_KEY()
|
||||
key_len = ctypes.c_int(8 * len(key))
|
||||
key = (ctypes.c_ubyte * len(key))(*key)
|
||||
iv = (ctypes.c_ubyte * len(iv))(*iv)
|
||||
|
||||
in_len = ctypes.c_size_t(len(cipher_text))
|
||||
in_ptr = (ctypes.c_ubyte * len(cipher_text))(*cipher_text)
|
||||
out_ptr = (ctypes.c_ubyte * len(cipher_text))()
|
||||
|
||||
_libssl.AES_set_decrypt_key(key, key_len, ctypes.byref(aes_key))
|
||||
_libssl.AES_ige_encrypt(
|
||||
ctypes.byref(in_ptr),
|
||||
ctypes.byref(out_ptr),
|
||||
in_len,
|
||||
ctypes.byref(aes_key),
|
||||
ctypes.byref(iv),
|
||||
AES_DECRYPT
|
||||
)
|
||||
|
||||
return bytes(out_ptr)
|
||||
|
||||
def encrypt_ige(plain_text, key, iv):
|
||||
aes_key = AES_KEY()
|
||||
key_len = ctypes.c_int(8 * len(key))
|
||||
key = (ctypes.c_ubyte * len(key))(*key)
|
||||
iv = (ctypes.c_ubyte * len(iv))(*iv)
|
||||
|
||||
in_len = ctypes.c_size_t(len(plain_text))
|
||||
in_ptr = (ctypes.c_ubyte * len(plain_text))(*plain_text)
|
||||
out_ptr = (ctypes.c_ubyte * len(plain_text))()
|
||||
|
||||
_libssl.AES_set_encrypt_key(key, key_len, ctypes.byref(aes_key))
|
||||
_libssl.AES_ige_encrypt(
|
||||
ctypes.byref(in_ptr),
|
||||
ctypes.byref(out_ptr),
|
||||
in_len,
|
||||
ctypes.byref(aes_key),
|
||||
ctypes.byref(iv),
|
||||
AES_ENCRYPT
|
||||
)
|
||||
|
||||
return bytes(out_ptr)
|
165
telethon/_crypto/rsa.py
Normal file
165
telethon/_crypto/rsa.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
This module holds several utilities regarding RSA and server fingerprints.
|
||||
"""
|
||||
import os
|
||||
import struct
|
||||
from hashlib import sha1
|
||||
try:
|
||||
import rsa
|
||||
import rsa.core
|
||||
except ImportError:
|
||||
rsa = None
|
||||
raise ImportError('Missing module "rsa", please install via pip.')
|
||||
|
||||
from ..tl import TLObject
|
||||
|
||||
|
||||
# {fingerprint: (Crypto.PublicKey.RSA._RSAobj, old)} dictionary
|
||||
_server_keys = {}
|
||||
|
||||
|
||||
def get_byte_array(integer):
|
||||
"""Return the variable length bytes corresponding to the given int"""
|
||||
# Operate in big endian (unlike most of Telegram API) since:
|
||||
# > "...pq is a representation of a natural number
|
||||
# (in binary *big endian* format)..."
|
||||
# > "...current value of dh_prime equals
|
||||
# (in *big-endian* byte order)..."
|
||||
# Reference: https://core.telegram.org/mtproto/auth_key
|
||||
return int.to_bytes(
|
||||
integer,
|
||||
(integer.bit_length() + 8 - 1) // 8, # 8 bits per byte,
|
||||
byteorder='big',
|
||||
signed=False
|
||||
)
|
||||
|
||||
|
||||
def _compute_fingerprint(key):
|
||||
"""
|
||||
Given a RSA key, computes its fingerprint like Telegram does.
|
||||
|
||||
:param key: the Crypto.RSA key.
|
||||
:return: its 8-bytes-long fingerprint.
|
||||
"""
|
||||
n = TLObject.serialize_bytes(get_byte_array(key.n))
|
||||
e = TLObject.serialize_bytes(get_byte_array(key.e))
|
||||
# Telegram uses the last 8 bytes as the fingerprint
|
||||
return struct.unpack('<q', sha1(n + e).digest()[-8:])[0]
|
||||
|
||||
|
||||
def add_key(pub, *, old):
|
||||
"""Adds a new public key to be used when encrypting new data is needed"""
|
||||
global _server_keys
|
||||
key = rsa.PublicKey.load_pkcs1(pub)
|
||||
_server_keys[_compute_fingerprint(key)] = (key, old)
|
||||
|
||||
|
||||
def encrypt(fingerprint, data, *, use_old=False):
|
||||
"""
|
||||
Encrypts the given data known the fingerprint to be used
|
||||
in the way Telegram requires us to do so (sha1(data) + data + padding)
|
||||
|
||||
:param fingerprint: the fingerprint of the RSA key.
|
||||
:param data: the data to be encrypted.
|
||||
:param use_old: whether old keys should be used.
|
||||
:return:
|
||||
the cipher text, or None if no key matching this fingerprint is found.
|
||||
"""
|
||||
global _server_keys
|
||||
key, old = _server_keys.get(fingerprint, [None, None])
|
||||
if (not key) or (old and not use_old):
|
||||
return None
|
||||
|
||||
# len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding
|
||||
to_encrypt = sha1(data).digest() + data + os.urandom(235 - len(data))
|
||||
|
||||
# rsa module rsa.encrypt adds 11 bits for padding which we don't want
|
||||
# rsa module uses rsa.transform.bytes2int(to_encrypt), easier way:
|
||||
payload = int.from_bytes(to_encrypt, 'big')
|
||||
encrypted = rsa.core.encrypt_int(payload, key.e, key.n)
|
||||
# rsa module uses transform.int2bytes(encrypted, keylength), easier:
|
||||
block = encrypted.to_bytes(256, 'big')
|
||||
return block
|
||||
|
||||
|
||||
# Add default keys
|
||||
# https://github.com/DrKLO/Telegram/blob/a724d96e9c008b609fe188d122aa2922e40de5fc/TMessagesProj/jni/tgnet/Handshake.cpp#L356-L436
|
||||
for pub in (
|
||||
'''-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAruw2yP/BCcsJliRoW5eBVBVle9dtjJw+OYED160Wybum9SXtBBLX
|
||||
riwt4rROd9csv0t0OHCaTmRqBcQ0J8fxhN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/
|
||||
j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvdl84Kd9ORYjDEAyFnEA7dD556OptgLQQ2
|
||||
e2iVNq8NZLYTzLp5YpOdO1doK+ttrltggTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnS
|
||||
Lj16yE5HvJQn0CNpRdENvRUXe6tBP78O39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wF
|
||||
XGF710w9lwCGNbmNxNYhtIkdqfsEcwR5JwIDAQAB
|
||||
-----END RSA PUBLIC KEY-----''',
|
||||
|
||||
'''-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAvfLHfYH2r9R70w8prHblWt/nDkh+XkgpflqQVcnAfSuTtO05lNPs
|
||||
pQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOOKPi0OfJXoRVylFzAQG/j83u5K3kRLbae
|
||||
7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ3TDS2pQOCtovG4eDl9wacrXOJTG2990V
|
||||
jgnIKNA0UMoP+KF03qzryqIt3oTvZq03DyWdGK+AZjgBLaDKSnC6qD2cFY81UryR
|
||||
WOab8zKkWAnhw2kFpcqhI0jdV5QaSCExvnsjVaX0Y1N0870931/5Jb9ICe4nweZ9
|
||||
kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV/wIDAQAB
|
||||
-----END RSA PUBLIC KEY-----''',
|
||||
|
||||
'''-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAs/ditzm+mPND6xkhzwFIz6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGr
|
||||
zqTDHkO30R8VeRM/Kz2f4nR05GIFiITl4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+
|
||||
th6knSU0yLtNKuQVP6voMrnt9MV1X92LGZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvS
|
||||
Uwwc+yi1/gGaybwlzZwqXYoPOhwMebzKUk0xW14htcJrRrq+PXXQbRzTMynseCoP
|
||||
Ioke0dtCodbA3qQxQovE16q9zz4Otv2k4j63cz53J+mhkVWAeWxVGI0lltJmWtEY
|
||||
K6er8VqqWot3nqmWMXogrgRLggv/NbbooQIDAQAB
|
||||
-----END RSA PUBLIC KEY-----''',
|
||||
|
||||
'''-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q05shjg8/4p6047bn6/m8yPy1RBsvIyvuD
|
||||
uGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xbnfxL5BXHplJhMtADXKM9bWB11PU1Eioc
|
||||
3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvi
|
||||
fRLJbY08/Gp66KpQvy7g8w7VB8wlgePexW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqe
|
||||
Pji9NP3tJUFQjcECqcm0yV7/2d0t/pbCm+ZH1sadZspQCEPPrtbkQBlvHb4OLiIW
|
||||
PGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6MAQIDAQAB
|
||||
-----END RSA PUBLIC KEY-----''',
|
||||
|
||||
):
|
||||
add_key(pub, old=False)
|
||||
|
||||
|
||||
for pub in (
|
||||
'''-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
|
||||
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
|
||||
an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
|
||||
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
|
||||
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
|
||||
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
|
||||
-----END RSA PUBLIC KEY-----''',
|
||||
|
||||
'''-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAxq7aeLAqJR20tkQQMfRn+ocfrtMlJsQ2Uksfs7Xcoo77jAid0bRt
|
||||
ksiVmT2HEIJUlRxfABoPBV8wY9zRTUMaMA654pUX41mhyVN+XoerGxFvrs9dF1Ru
|
||||
vCHbI02dM2ppPvyytvvMoefRoL5BTcpAihFgm5xCaakgsJ/tH5oVl74CdhQw8J5L
|
||||
xI/K++KJBUyZ26Uba1632cOiq05JBUW0Z2vWIOk4BLysk7+U9z+SxynKiZR3/xdi
|
||||
XvFKk01R3BHV+GUKM2RYazpS/P8v7eyKhAbKxOdRcFpHLlVwfjyM1VlDQrEZxsMp
|
||||
NTLYXb6Sce1Uov0YtNx5wEowlREH1WOTlwIDAQAB
|
||||
-----END RSA PUBLIC KEY-----''',
|
||||
|
||||
'''-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAsQZnSWVZNfClk29RcDTJQ76n8zZaiTGuUsi8sUhW8AS4PSbPKDm+
|
||||
DyJgdHDWdIF3HBzl7DHeFrILuqTs0vfS7Pa2NW8nUBwiaYQmPtwEa4n7bTmBVGsB
|
||||
1700/tz8wQWOLUlL2nMv+BPlDhxq4kmJCyJfgrIrHlX8sGPcPA4Y6Rwo0MSqYn3s
|
||||
g1Pu5gOKlaT9HKmE6wn5Sut6IiBjWozrRQ6n5h2RXNtO7O2qCDqjgB2vBxhV7B+z
|
||||
hRbLbCmW0tYMDsvPpX5M8fsO05svN+lKtCAuz1leFns8piZpptpSCFn7bWxiA9/f
|
||||
x5x17D7pfah3Sy2pA+NDXyzSlGcKdaUmwQIDAQAB
|
||||
-----END RSA PUBLIC KEY-----''',
|
||||
|
||||
'''-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAwqjFW0pi4reKGbkc9pK83Eunwj/k0G8ZTioMMPbZmW99GivMibwa
|
||||
xDM9RDWabEMyUtGoQC2ZcDeLWRK3W8jMP6dnEKAlvLkDLfC4fXYHzFO5KHEqF06i
|
||||
qAqBdmI1iBGdQv/OQCBcbXIWCGDY2AsiqLhlGQfPOI7/vvKc188rTriocgUtoTUc
|
||||
/n/sIUzkgwTqRyvWYynWARWzQg0I9olLBBC2q5RQJJlnYXZwyTL3y9tdb7zOHkks
|
||||
WV9IMQmZmyZh/N7sMbGWQpt4NMchGpPGeJ2e5gHBjDnlIf2p1yZOYeUYrdbwcS0t
|
||||
UiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB
|
||||
-----END RSA PUBLIC KEY-----''',
|
||||
):
|
||||
add_key(pub, old=True)
|
Reference in New Issue
Block a user