Initial attempt at Perfect Forward Secrecy

This commit is contained in:
Lonami Exo 2017-10-13 11:28:19 +02:00
parent 9cf5506ee4
commit 260e006abe
6 changed files with 91 additions and 8 deletions

View File

@ -21,3 +21,9 @@ class AuthKey:
new_nonce = new_nonce.to_bytes(32, 'little', signed=True) new_nonce = new_nonce.to_bytes(32, 'little', signed=True)
data = new_nonce + struct.pack('<BQ', number, self.aux_hash) data = new_nonce + struct.pack('<BQ', number, self.aux_hash)
return utils.calc_msg_key(data) return utils.calc_msg_key(data)
class TempAuthKey(AuthKey):
def __init__(self, data, expires_at):
super().__init__(data)
self.expires_at = expires_at

View File

@ -1,13 +1,14 @@
"""Various helpers not related to the Telegram API itself""" """Various helpers not related to the Telegram API itself"""
import struct
from hashlib import sha1, sha256 from hashlib import sha1, sha256
import os import os
# region Multiple utilities # region Multiple utilities
def generate_random_long(signed=True): def random_long(signed=True):
"""Generates a random long integer (8 bytes), which is optionally signed""" """Generates a random long integer (8 bytes), which is optionally signed"""
return int.from_bytes(os.urandom(8), signed=signed, byteorder='little') return struct.unpack('<q' if signed else '<Q', os.urandom(8))[0]
def ensure_parent_dir_exists(file_path): def ensure_parent_dir_exists(file_path):
@ -72,4 +73,14 @@ def get_password_hash(pw, current_salt):
pw_hash = current_salt + data + current_salt pw_hash = current_salt + data + current_salt
return sha256(pw_hash).digest() return sha256(pw_hash).digest()
def reinterpret(value, fmt, endian='<'):
"""Reinterprets the given value from its original format to its new
format, for instance, reinterpret(-1, 'qQ') -> -1 as unsigned.
"""
return struct.unpack(
endian + fmt[1], struct.pack(endian + fmt[0], value)
)[0]
# endregion # endregion

View File

@ -1,20 +1,25 @@
import os import os
import struct
import time import time
from datetime import datetime, timedelta
from hashlib import sha1 from hashlib import sha1
from ..tl.types import (
ResPQ, PQInnerData, ServerDHParamsFail, ServerDHParamsOk,
ServerDHInnerData, ClientDHInnerData, DhGenOk, DhGenRetry, DhGenFail
)
from .. import helpers as utils from .. import helpers as utils
from ..crypto import AES, AuthKey, Factorization from ..crypto import AES, AuthKey, Factorization
from ..crypto import rsa from ..crypto import rsa
from ..errors import SecurityError from ..errors import SecurityError
from ..extensions import BinaryReader from ..extensions import BinaryReader
from ..network import MtProtoPlainSender from ..network import MtProtoPlainSender
from ..tl import TLMessage, TLObject
from ..tl.functions import ( from ..tl.functions import (
ReqPqRequest, ReqDHParamsRequest, SetClientDHParamsRequest ReqPqRequest, ReqDHParamsRequest, SetClientDHParamsRequest
) )
from ..tl.functions.auth import BindTempAuthKeyRequest
from ..tl.types import (
ResPQ, PQInnerData, ServerDHParamsFail, ServerDHParamsOk,
ServerDHInnerData, ClientDHInnerData, DhGenOk, DhGenRetry, DhGenFail,
BindAuthKeyInner
)
def do_authentication(connection, retries=5): def do_authentication(connection, retries=5):
@ -190,6 +195,65 @@ def _do_authentication(connection):
raise NotImplementedError('DH Gen unknown: {}'.format(dh_gen)) raise NotImplementedError('DH Gen unknown: {}'.format(dh_gen))
def bind_temp_auth_key(temp_auth_key, auth_key, sender):
"""
:param TempAuthKey temp_auth_key:
:param AuthKey auth_key:
:param MtProtoSender sender:
"""
assert sender.session.auth_key.key_id == temp_auth_key.key_id
bind_inner = BindAuthKeyInner(
nonce=utils.random_long(),
temp_auth_key_id=utils.reinterpret(temp_auth_key.key_id, 'Qq'),
perm_auth_key_id=utils.reinterpret(sender.session.auth_key.key_id, 'Qq'),
temp_session_id=utils.reinterpret(sender.session.id, 'Qq'),
expires_at=datetime.now() + timedelta(days=1)
)
inner_message = TLMessage(sender.session, bind_inner)
inner_message.seq_no = 0
# TODO Copy paste from MtProtoSender._send_message
plain_text = (
os.urandom(16) # random unsigned salt + random unsigned id
+ inner_message.to_bytes()
)
msg_key = utils.calc_msg_key(plain_text)
key_id = struct.pack('<Q', auth_key.key_id)
key, iv = utils.calc_key(auth_key.key, msg_key, client=True)
cipher_text = AES.encrypt_ige(plain_text, key, iv)
encrypted_inner_message = key_id + msg_key + cipher_text
# Now perform the actual request
request = BindTempAuthKeyRequest(
perm_auth_key_id=utils.reinterpret(auth_key.key_id, 'Qq'),
nonce=bind_inner.nonce,
expires_at=bind_inner.expires_at,
encrypted_message=TLObject.serialize_bytes(encrypted_inner_message)
)
message = TLMessage(sender.session, request)
message.msg_id = inner_message.msg_id
# TODO Copy paste from MtProtoSender._send_message
plain_text = \
struct.pack('<QQ', sender.session.salt, sender.session.id) \
+ message.to_bytes()
msg_key = utils.calc_msg_key(plain_text)
key_id = struct.pack('<Q', temp_auth_key.key_id)
key, iv = utils.calc_key(temp_auth_key.key, msg_key, client=True)
cipher_text = AES.encrypt_ige(plain_text, key, iv)
encrypted_message = key_id + msg_key + cipher_text
sender._pending_receive[message.msg_id] = message
sender.connection.send(encrypted_message)
while not request.confirm_received.is_set():
sender.receive(update_state=None)
print('Success?')
print(request.result.stringify())
def get_int(byte_array, signed=True): def get_int(byte_array, signed=True):
"""Gets the specified integer from its byte array. """Gets the specified integer from its byte array.
This should be used by the authenticator, This should be used by the authenticator,

View File

@ -601,7 +601,7 @@ class TelegramBareClient:
is_large = file_size > 10 * 1024 * 1024 is_large = file_size > 10 * 1024 * 1024
part_count = (file_size + part_size - 1) // part_size part_count = (file_size + part_size - 1) // part_size
file_id = utils.generate_random_long() file_id = utils.random_long()
hash_md5 = md5() hash_md5 = md5()
stream = open(file, 'rb') if isinstance(file, str) else BytesIO(file) stream = open(file, 'rb') if isinstance(file, str) else BytesIO(file)

View File

@ -58,7 +58,7 @@ class Session:
self._msg_id_lock = Lock() self._msg_id_lock = Lock()
self._save_lock = Lock() self._save_lock = Lock()
self.id = helpers.generate_random_long(signed=False) self.id = helpers.random_long(signed=False)
self._sequence = 0 self._sequence = 0
self.time_offset = 0 self.time_offset = 0
self._last_msg_id = 0 # Long self._last_msg_id = 0 # Long

View File

@ -51,6 +51,8 @@ destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes;
destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes; destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes;
destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes; destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner;
---functions--- ---functions---
req_pq#60469778 nonce:int128 = ResPQ; req_pq#60469778 nonce:int128 = ResPQ;