Exemple #1
0
    def test_aes_encrypt(self):
        value = AES.encrypt_ige(self.plain_text, self.key, self.iv)
        take = 16  # Don't take all the bytes, since latest involve are random padding
        assert value[:take] == self.cipher_text[:take],\
            ('Ciphered text ("{}") does not equal expected ("{}")'
             .format(value[:take], self.cipher_text[:take]))

        value = AES.encrypt_ige(self.plain_text_padded, self.key, self.iv)
        assert value == self.cipher_text_padded, (
            'Ciphered text ("{}") does not equal expected ("{}")'.format(
                value, self.cipher_text_padded))
Exemple #2
0
    def test_aes_encrypt(self):
        value = AES.encrypt_ige(self.plain_text, self.key, self.iv)
        take = 16  # Don't take all the bytes, since latest involve are random padding
        assert value[:take] == self.cipher_text[:take],\
            ('Ciphered text ("{}") does not equal expected ("{}")'
             .format(value[:take], self.cipher_text[:take]))

        value = AES.encrypt_ige(self.plain_text_padded, self.key, self.iv)
        assert value == self.cipher_text_padded, (
            'Ciphered text ("{}") does not equal expected ("{}")'
            .format(value, self.cipher_text_padded))
Exemple #3
0
    def decode_msg(self, body):
        """Decodes an received encrypted message body bytes"""
        message = None
        remote_msg_id = None
        remote_sequence = None

        with BinaryReader(body) as reader:
            if len(body) < 8:
                raise BufferError("Can't decode packet ({})".format(body))

            # TODO Check for both auth key ID and msg_key correctness
            reader.read_long()  # remote_auth_key_id
            msg_key = reader.read(16)

            key, iv = utils.calc_key(self.session.auth_key.key, msg_key, False)
            plain_text = AES.decrypt_ige(
                reader.read(len(body) - reader.tell_position()), key, iv)

            with BinaryReader(plain_text) as plain_text_reader:
                plain_text_reader.read_long()  # remote_salt
                plain_text_reader.read_long()  # remote_session_id
                remote_msg_id = plain_text_reader.read_long()
                remote_sequence = plain_text_reader.read_int()
                msg_len = plain_text_reader.read_int()
                message = plain_text_reader.read(msg_len)

        return message, remote_msg_id, remote_sequence
Exemple #4
0
    def send_packet(self, packet, request):
        """Sends the given packet bytes with the additional
           information of the original request. This does NOT lock the threads!"""
        request.msg_id = self.session.get_new_msg_id()

        # First calculate plain_text to encrypt it
        with BinaryWriter() as plain_writer:
            plain_writer.write_long(self.session.salt, signed=False)
            plain_writer.write_long(self.session.id, signed=False)
            plain_writer.write_long(request.msg_id)
            plain_writer.write_int(self.generate_sequence(request.confirmed))
            plain_writer.write_int(len(packet))
            plain_writer.write(packet)

            msg_key = utils.calc_msg_key(plain_writer.get_bytes())

            key, iv = utils.calc_key(self.session.auth_key.key, msg_key, True)
            cipher_text = AES.encrypt_ige(plain_writer.get_bytes(), key, iv)

        # And then finally send the encrypted packet
        with BinaryWriter() as cipher_writer:
            cipher_writer.write_long(self.session.auth_key.key_id,
                                     signed=False)
            cipher_writer.write(msg_key)
            cipher_writer.write(cipher_text)
            self.transport.send(cipher_writer.get_bytes())
def unpack_message(session, reader):
    """Unpacks a message following MtProto 2.0 guidelines"""
    # See https://core.telegram.org/mtproto/description
    if reader.read_long(signed=False) != session.auth_key.key_id:
        raise SecurityError('Server replied with an invalid auth key')

    msg_key = reader.read(16)
    aes_key, aes_iv = calc_key(session.auth_key.key, msg_key, False)
    data = BinaryReader(AES.decrypt_ige(reader.read(), aes_key, aes_iv))

    data.read_long()  # remote_salt
    if data.read_long() != session.id:
        raise SecurityError('Server replied with a wrong session ID')

    remote_msg_id = data.read_long()
    remote_sequence = data.read_int()
    msg_len = data.read_int()
    message = data.read(msg_len)

    # https://core.telegram.org/mtproto/security_guidelines
    # Sections "checking sha256 hash" and "message length"
    if msg_key != sha256(session.auth_key.key[96:96 + 32] +
                         data.get_bytes()).digest()[8:24]:
        raise SecurityError("Received msg_key doesn't match with expected one")

    return message, remote_msg_id, remote_sequence
Exemple #6
0
def unpack_message(session, reader):
    """Unpacks a message following MtProto 2.0 guidelines"""
    # See https://core.telegram.org/mtproto/description
    if reader.read_long(signed=False) != session.auth_key.key_id:
        raise SecurityError('Server replied with an invalid auth key')

    msg_key = reader.read(16)
    aes_key, aes_iv = calc_key(session.auth_key.key, msg_key, False)
    data = BinaryReader(AES.decrypt_ige(reader.read(), aes_key, aes_iv))

    data.read_long()  # remote_salt
    if data.read_long() != session.id:
        raise SecurityError('Server replied with a wrong session ID')

    remote_msg_id = data.read_long()
    remote_sequence = data.read_int()
    msg_len = data.read_int()
    message = data.read(msg_len)

    # https://core.telegram.org/mtproto/security_guidelines
    # Sections "checking sha256 hash" and "message length"
    if msg_key != sha256(
            session.auth_key.key[96:96 + 32] + data.get_bytes()).digest()[8:24]:
        raise SecurityError("Received msg_key doesn't match with expected one")

    return message, remote_msg_id, remote_sequence
Exemple #7
0
 def test_aes_decrypt(self):
     # The ciphered text must always be padded
     value = AES.decrypt_ige(self.cipher_text_padded, self.key, self.iv)
     self.assertEqual(
         value,
         self.plain_text_padded,
         msg='Decrypted text ("{}") does not equal expected ("{}")'.format(
             value, self.plain_text_padded))
def pack_message(session, message):
    """Packs a message following MtProto 2.0 guidelines"""
    # See https://core.telegram.org/mtproto/description
    data = struct.pack('<qq', session.salt, session.id) + bytes(message)
    padding = os.urandom(-(len(data) + 12) % 16 + 12)

    # Being substr(what, offset, length); x = 0 for client
    # "msg_key_large = SHA256(substr(auth_key, 88+x, 32) + pt + padding)"
    msg_key_large = sha256(session.auth_key.key[88:88 + 32] + data +
                           padding).digest()

    # "msg_key = substr (msg_key_large, 8, 16)"
    msg_key = msg_key_large[8:24]
    aes_key, aes_iv = calc_key(session.auth_key.key, msg_key, True)

    key_id = struct.pack('<Q', session.auth_key.key_id)
    return key_id + msg_key + AES.encrypt_ige(data + padding, aes_key, aes_iv)
Exemple #9
0
def pack_message(session, message):
    """Packs a message following MtProto 2.0 guidelines"""
    # See https://core.telegram.org/mtproto/description
    data = struct.pack('<qq', session.salt, session.id) + bytes(message)
    padding = os.urandom(-(len(data) + 12) % 16 + 12)

    # Being substr(what, offset, length); x = 0 for client
    # "msg_key_large = SHA256(substr(auth_key, 88+x, 32) + pt + padding)"
    msg_key_large = sha256(
        session.auth_key.key[88:88 + 32] + data + padding).digest()

    # "msg_key = substr (msg_key_large, 8, 16)"
    msg_key = msg_key_large[8:24]
    aes_key, aes_iv = calc_key(session.auth_key.key, msg_key, True)

    key_id = struct.pack('<Q', session.auth_key.key_id)
    return key_id + msg_key + AES.encrypt_ige(data + padding, aes_key, aes_iv)
    def decrypt_mtproto1(self, message_key, chat_id, encrypted_data):
        aes_key, aes_iv = _old_calc_key(
            self.get_secret_chat(chat_id).auth_key, message_key, True)
        decrypted_data = AES.decrypt_ige(encrypted_data, aes_key, aes_iv)
        message_data_length = struct.unpack('<I', decrypted_data[:4])[0]
        message_data = decrypted_data[4:message_data_length + 4]

        if message_data_length > len(decrypted_data):
            raise SecurityError("message data length is too big")

        if message_key != sha1(
                decrypted_data[:4 + message_data_length]).digest()[-16:]:
            raise SecurityError("Message key mismatch")
        if len(decrypted_data) - 4 - message_data_length > 15:
            raise SecurityError("Difference is too big")
        if len(decrypted_data) % 16 != 0:
            raise SecurityError("Decrypted data can not be divided by 16")

        return BinaryReader(message_data).tgread_object()
    async def encrypt_secret_message(
            self, peer: [int, SecretChat, InputEncryptedChat,
                         EncryptedChat], message):
        peer = self.get_secret_chat(peer)
        peer.ttr -= 1
        if peer.layer > 8:
            if (peer.ttr <= 0 or (time() - peer.updated) > 7 * 24 * 60 * 60
                ) and peer.rekeying[0] == 0:
                await self.rekey(peer)
            message = DecryptedMessageLayer(
                layer=peer.layer,
                random_bytes=os.urandom(15 + 4 * random.randint(0, 2)),
                in_seq_no=self.generate_secret_in_seq_no(peer.id),
                out_seq_no=self.generate_secret_out_seq_no(peer.id),
                message=message)

            peer.out_seq_no += 1

        peer.outgoing[peer.out_seq_no] = message
        message = bytes(message)
        message = struct.pack('<I', len(message)) + message
        if peer.mtproto == 2:
            padding = (16 - len(message) % 16) % 16
            if padding < 12:
                padding += 16
            message += os.urandom(padding)
            is_admin = (0 if peer.admin else 8)
            first_str = peer.auth_key[88 + is_admin:88 + 32 + is_admin]
            message_key = sha256(first_str + message).digest()[8:24]
            aes_key, aes_iv = MTProtoState._calc_key(peer.auth_key,
                                                     message_key, peer.admin)
        else:
            message_key = sha1(message).digest()[-16:]
            aes_key, aes_iv = _old_calc_key(peer.auth_key, message_key, True)
            padding = (16 - len(message) % 16) % 16
            message += os.urandom(padding)
        key_fingerprint = struct.unpack('<q',
                                        sha1(peer.auth_key).digest()[-8:])[0]
        message = struct.pack(
            '<q', key_fingerprint) + message_key + AES.encrypt_ige(
                bytes.fromhex(message.hex()), aes_key, aes_iv)
        return message
    def decrypt_mtproto2(self, message_key, chat_id, encrypted_data):
        peer = self.get_secret_chat(chat_id)
        aes_key, aes_iv = MTProtoState._calc_key(peer.auth_key, message_key,
                                                 not peer.admin)

        decrypted_data = AES.decrypt_ige(encrypted_data, aes_key, aes_iv)
        message_data_length = struct.unpack('<I', decrypted_data[:4])[0]
        message_data = decrypted_data[4:message_data_length + 4]
        if message_data_length > len(decrypted_data):
            raise SecurityError("message data length is too big")
        is_admin = (8 if peer.admin else 0)
        first_str = peer.auth_key[88 + is_admin:88 + 32 + is_admin]

        if message_key != sha256(first_str + decrypted_data).digest()[8:24]:
            raise SecurityError("Message key mismatch")
        if len(decrypted_data) - 4 - message_data_length < 12:
            raise SecurityError("Padding is too small")
        if len(decrypted_data) % 16 != 0:
            raise SecurityError("Decrpyted data not divisble by 16")

        return BinaryReader(message_data).tgread_object()
Exemple #13
0
 def test_aes_decrypt(self):
     # The ciphered text must always be padded
     value = AES.decrypt_ige(self.cipher_text_padded, self.key, self.iv)
     assert value == self.plain_text_padded, (
         'Decrypted text ("{}") does not equal expected ("{}")'
         .format(value, self.plain_text_padded))
Exemple #14
0
def do_authentication(transport):
    """Executes the authentication process with the Telegram servers.
    If no error is rose, returns both the authorization key and the time offset"""
    sender = MtProtoPlainSender(transport)

    # Step 1 sending: PQ Request
    nonce = os.urandom(16)
    with BinaryWriter() as writer:
        writer.write_int(0x60469778, signed=False)  # Constructor number
        writer.write(nonce)
        sender.send(writer.get_bytes())

    # Step 1 response: PQ Request
    pq, pq_bytes, server_nonce, fingerprints = None, None, None, []
    with BinaryReader(sender.receive()) as reader:
        response_code = reader.read_int(signed=False)
        if response_code != 0x05162463:
            raise AssertionError('Invalid response code: {}'.format(
                hex(response_code)))

        nonce_from_server = reader.read(16)
        if nonce_from_server != nonce:
            raise AssertionError('Invalid nonce from server')

        server_nonce = reader.read(16)

        pq_bytes = reader.tgread_bytes()
        pq = get_int(pq_bytes)

        vector_id = reader.read_int()
        if vector_id != 0x1cb5c415:
            raise AssertionError('Invalid vector constructor ID: {}'.format(
                hex(response_code)))

        fingerprints = []
        fingerprint_count = reader.read_int()
        for _ in range(fingerprint_count):
            fingerprints.append(reader.read(8))

    # Step 2 sending: DH Exchange
    new_nonce = os.urandom(32)
    p, q = Factorizator.factorize(pq)
    with BinaryWriter() as pq_inner_data_writer:
        pq_inner_data_writer.write_int(
            0x83c95aec, signed=False)  # PQ Inner Data
        pq_inner_data_writer.tgwrite_bytes(get_byte_array(pq, signed=False))
        pq_inner_data_writer.tgwrite_bytes(
            get_byte_array(
                min(p, q), signed=False))
        pq_inner_data_writer.tgwrite_bytes(
            get_byte_array(
                max(p, q), signed=False))
        pq_inner_data_writer.write(nonce)
        pq_inner_data_writer.write(server_nonce)
        pq_inner_data_writer.write(new_nonce)

        cipher_text, target_fingerprint = None, None
        for fingerprint in fingerprints:
            cipher_text = RSA.encrypt(
                get_fingerprint_text(fingerprint),
                pq_inner_data_writer.get_bytes())

            if cipher_text is not None:
                target_fingerprint = fingerprint
                break

        if cipher_text is None:
            raise AssertionError(
                'Could not find a valid key for fingerprints: {}'
                .format(', '.join([get_fingerprint_text(f)
                                   for f in fingerprints])))

        with BinaryWriter() as req_dh_params_writer:
            req_dh_params_writer.write_int(
                0xd712e4be, signed=False)  # Req DH Params
            req_dh_params_writer.write(nonce)
            req_dh_params_writer.write(server_nonce)
            req_dh_params_writer.tgwrite_bytes(
                get_byte_array(
                    min(p, q), signed=False))
            req_dh_params_writer.tgwrite_bytes(
                get_byte_array(
                    max(p, q), signed=False))
            req_dh_params_writer.write(target_fingerprint)
            req_dh_params_writer.tgwrite_bytes(cipher_text)

            req_dh_params_bytes = req_dh_params_writer.get_bytes()
            sender.send(req_dh_params_bytes)

    # Step 2 response: DH Exchange
    encrypted_answer = None
    with BinaryReader(sender.receive()) as reader:
        response_code = reader.read_int(signed=False)

        if response_code == 0x79cb045d:
            raise AssertionError('Server DH params fail: TODO')

        if response_code != 0xd0e8075c:
            raise AssertionError('Invalid response code: {}'.format(
                hex(response_code)))

        nonce_from_server = reader.read(16)
        if nonce_from_server != nonce:
            raise NotImplementedError('Invalid nonce from server')

        server_nonce_from_server = reader.read(16)
        if server_nonce_from_server != server_nonce:
            raise NotImplementedError('Invalid server nonce from server')

        encrypted_answer = reader.tgread_bytes()

    # Step 3 sending: Complete DH Exchange
    key, iv = utils.generate_key_data_from_nonces(server_nonce, new_nonce)
    plain_text_answer = AES.decrypt_ige(encrypted_answer, key, iv)

    g, dh_prime, ga, time_offset = None, None, None, None
    with BinaryReader(plain_text_answer) as dh_inner_data_reader:
        dh_inner_data_reader.read(20)  # hashsum
        code = dh_inner_data_reader.read_int(signed=False)
        if code != 0xb5890dba:
            raise AssertionError('Invalid DH Inner Data code: {}'.format(code))

        nonce_from_server1 = dh_inner_data_reader.read(16)
        if nonce_from_server1 != nonce:
            raise AssertionError('Invalid nonce in encrypted answer')

        server_nonce_from_server1 = dh_inner_data_reader.read(16)
        if server_nonce_from_server1 != server_nonce:
            raise AssertionError('Invalid server nonce in encrypted answer')

        g = dh_inner_data_reader.read_int()
        dh_prime = get_int(dh_inner_data_reader.tgread_bytes(), signed=False)
        ga = get_int(dh_inner_data_reader.tgread_bytes(), signed=False)

        server_time = dh_inner_data_reader.read_int()
        time_offset = server_time - int(time.time())

    b = get_int(os.urandom(2048), signed=False)
    gb = pow(g, b, dh_prime)
    gab = pow(ga, b, dh_prime)

    # Prepare client DH Inner Data
    with BinaryWriter() as client_dh_inner_data_writer:
        client_dh_inner_data_writer.write_int(
            0x6643b654, signed=False)  # Client DH Inner Data
        client_dh_inner_data_writer.write(nonce)
        client_dh_inner_data_writer.write(server_nonce)
        client_dh_inner_data_writer.write_long(0)  # TODO retry_id
        client_dh_inner_data_writer.tgwrite_bytes(
            get_byte_array(
                gb, signed=False))

        with BinaryWriter() as client_dh_inner_data_with_hash_writer:
            client_dh_inner_data_with_hash_writer.write(
                utils.sha1(client_dh_inner_data_writer.get_bytes()))
            client_dh_inner_data_with_hash_writer.write(
                client_dh_inner_data_writer.get_bytes())
            client_dh_inner_data_bytes = client_dh_inner_data_with_hash_writer.get_bytes(
            )

    # Encryption
    client_dh_inner_data_encrypted_bytes = AES.encrypt_ige(
        client_dh_inner_data_bytes, key, iv)

    # Prepare Set client DH params
    with BinaryWriter() as set_client_dh_params_writer:
        set_client_dh_params_writer.write_int(0xf5045f1f, signed=False)
        set_client_dh_params_writer.write(nonce)
        set_client_dh_params_writer.write(server_nonce)
        set_client_dh_params_writer.tgwrite_bytes(
            client_dh_inner_data_encrypted_bytes)

        set_client_dh_params_bytes = set_client_dh_params_writer.get_bytes()
        sender.send(set_client_dh_params_bytes)

    # Step 3 response: Complete DH Exchange
    with BinaryReader(sender.receive()) as reader:
        code = reader.read_int(signed=False)
        if code == 0x3bcbf734:  # DH Gen OK
            nonce_from_server = reader.read(16)
            if nonce_from_server != nonce:
                raise NotImplementedError('Invalid nonce from server')

            server_nonce_from_server = reader.read(16)
            if server_nonce_from_server != server_nonce:
                raise NotImplementedError('Invalid server nonce from server')

            new_nonce_hash1 = reader.read(16)
            auth_key = AuthKey(get_byte_array(gab, signed=False))

            new_nonce_hash_calculated = auth_key.calc_new_nonce_hash(new_nonce,
                                                                     1)
            if new_nonce_hash1 != new_nonce_hash_calculated:
                raise AssertionError('Invalid new nonce hash')

            return auth_key, time_offset

        elif code == 0x46dc1fb9:  # DH Gen Retry
            raise NotImplementedError('dh_gen_retry')

        elif code == 0xa69dae02:  # DH Gen Fail
            raise NotImplementedError('dh_gen_fail')

        else:
            raise AssertionError('DH Gen unknown: {}'.format(hex(code)))