mirror of
https://github.com/danog/MadelineProto.git
synced 2024-11-30 05:58:58 +01:00
Added tests, added layer 55 json
This commit is contained in:
parent
f037212ac8
commit
90af9857ff
18
.travis.yml
Normal file
18
.travis.yml
Normal file
@ -0,0 +1,18 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
group: edge
|
||||
language: php
|
||||
php:
|
||||
- '7.0'
|
||||
- nightly
|
||||
- hhvm
|
||||
- '5.6'
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- python3
|
||||
|
||||
before_script:
|
||||
- composer update --dev
|
||||
script:
|
||||
- ./testing.php
|
@ -1,6 +1,6 @@
|
||||
# MadelineProto
|
||||
[![StyleCI](https://styleci.io/repos/61838413/shield)](https://styleci.io/repos/61838413)
|
||||
|
||||
[![Build Status](https://travis-ci.org/danog/MadelineProto.svg?branch=master)](https://travis-ci.org/danog/MadelineProto)
|
||||
Licensed under AGPLv3.
|
||||
|
||||
PHP implementation of MTProto, based on [telepy](https://github.com/griganton/telepy_old).
|
||||
|
@ -1,170 +0,0 @@
|
||||
__author__ = 'agrigoryev'
|
||||
import os
|
||||
import struct
|
||||
import json
|
||||
import io
|
||||
from numbers import Number
|
||||
from binascii import hexlify
|
||||
|
||||
class TlConstructor:
|
||||
def __init__(self, json_dict):
|
||||
self.id = int(json_dict['id'])
|
||||
self.type = json_dict['type']
|
||||
self.predicate = json_dict['predicate']
|
||||
self.params = []
|
||||
# case of vector
|
||||
for param in json_dict['params']:
|
||||
if param['type'] == "Vector<long>":
|
||||
param['type'] = "Vector t"
|
||||
param['subtype'] = "long"
|
||||
elif param['type'] == "vector<%Message>":
|
||||
param['type'] = "vector"
|
||||
param['subtype'] = "message"
|
||||
elif param['type'] == "vector<future_salt>":
|
||||
param['type'] = "vector"
|
||||
param['subtype'] = "future_salt"
|
||||
else:
|
||||
param['subtype'] = None
|
||||
self.params.append(param)
|
||||
|
||||
class TlMethod:
|
||||
def __init__(self, json_dict):
|
||||
self.id = int(json_dict['id'])
|
||||
self.type = json_dict['type']
|
||||
self.method = json_dict['method']
|
||||
self.params = json_dict['params']
|
||||
|
||||
|
||||
class TLObject(dict):
|
||||
def __init__(self, tl_elem):
|
||||
self.name = tl_elem.predicate
|
||||
|
||||
class TL:
|
||||
def __init__(self, filename):
|
||||
with open(filename, 'r') as f:
|
||||
TL_dict = json.load(f)
|
||||
|
||||
# Read constructors
|
||||
|
||||
self.constructors = TL_dict['constructors']
|
||||
self.constructor_id = {}
|
||||
self.constructor_type = {}
|
||||
for elem in self.constructors:
|
||||
z = TlConstructor(elem)
|
||||
self.constructor_id[z.id] = z
|
||||
self.constructor_type[z.predicate] = z
|
||||
|
||||
self.methods = TL_dict['methods']
|
||||
self.method_id = {}
|
||||
self.method_name = {}
|
||||
for elem in self.methods:
|
||||
z = TlMethod(elem)
|
||||
self.method_id[z.id] = z
|
||||
self.method_name[z.method] = z
|
||||
|
||||
|
||||
## Loading TL_schema (should be placed in the same directory as mtproto.py
|
||||
tl = TL(os.path.join(os.path.dirname(__file__), "TL_schema.JSON"))
|
||||
|
||||
|
||||
def serialize_obj(type_, **kwargs):
|
||||
bytes_io = io.BytesIO()
|
||||
try:
|
||||
tl_constructor = tl.constructor_type[type_]
|
||||
except KeyError:
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
bytes_io.write(struct.pack('<i', tl_constructor.id))
|
||||
for arg in tl_constructor.params:
|
||||
serialize_param(bytes_io, type_=arg['type'], value=kwargs[arg['name']])
|
||||
return bytes_io.getvalue()
|
||||
|
||||
|
||||
def serialize_method(type_, **kwargs):
|
||||
bytes_io = io.BytesIO()
|
||||
try:
|
||||
tl_method = tl.method_name[type_]
|
||||
except KeyError:
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
bytes_io.write(struct.pack('<i', tl_method.id))
|
||||
for arg in tl_method.params:
|
||||
serialize_param(bytes_io, type_=arg['type'], value=kwargs[arg['name']])
|
||||
return bytes_io.getvalue()
|
||||
|
||||
|
||||
def serialize_param(bytes_io, type_, value):
|
||||
if type_ == "int":
|
||||
assert isinstance(value, Number)
|
||||
assert value.bit_length() <= 32
|
||||
bytes_io.write(struct.pack('<i', value))
|
||||
elif type_ == "long":
|
||||
assert isinstance(value, Number)
|
||||
bytes_io.write(struct.pack('<q', value))
|
||||
elif type_ in ["int128", "int256"]:
|
||||
assert isinstance(value, bytes)
|
||||
bytes_io.write(value)
|
||||
elif type_ == 'string' or type_ == 'bytes':
|
||||
l = len(value)
|
||||
if l < 254: # short string format
|
||||
bytes_io.write(struct.pack('<b', l)) # 1 byte of string
|
||||
bytes_io.write(value) # string
|
||||
bytes_io.write(b'\x00'*((-l-1) % 4)) # padding bytes
|
||||
else:
|
||||
bytes_io.write(b'\xfe') # byte 254
|
||||
bytes_io.write(struct.pack('<i', l)[:3]) # 3 bytes of string
|
||||
bytes_io.write(value) # string
|
||||
bytes_io.write(b'\x00'*(-l % 4)) # padding bytes
|
||||
|
||||
def deserialize(bytes_io, type_=None, subtype=None):
|
||||
"""
|
||||
:type bytes_io: io.BytesIO object
|
||||
"""
|
||||
assert isinstance(bytes_io, io.BytesIO)
|
||||
# Built-in bare types
|
||||
if type_ == 'int': x = struct.unpack('<i', bytes_io.read(4))[0]
|
||||
elif type_ == '#': x = struct.unpack('<I', bytes_io.read(4))[0]
|
||||
elif type_ == 'long': x = struct.unpack('<q', bytes_io.read(8))[0]
|
||||
elif type_ == 'double': x = struct.unpack('<d', bytes_io.read(8))[0]
|
||||
elif type_ == 'int128': x = bytes_io.read(16)
|
||||
elif type_ == 'int256': x = bytes_io.read(32)
|
||||
elif type_ == 'string' or type_ == 'bytes':
|
||||
l = struct.unpack('<B', bytes_io.read(1))[0]
|
||||
assert l <= 254 # In general, 0xFF byte is not allowed here
|
||||
if l == 254:
|
||||
# We have a long string
|
||||
long_len = struct.unpack('<I', bytes_io.read(3)+b'\x00')[0]
|
||||
x = bytes_io.read(long_len)
|
||||
bytes_io.read(-long_len % 4) # skip padding bytes
|
||||
else:
|
||||
# We have a short string
|
||||
x = bytes_io.read(l)
|
||||
bytes_io.read(-(l+1) % 4) # skip padding bytes
|
||||
assert isinstance(x, bytes)
|
||||
elif type_ == 'vector':
|
||||
assert subtype is not None
|
||||
count = struct.unpack('<l', bytes_io.read(4))[0]
|
||||
x = [deserialize(bytes_io, type_=subtype) for i in range(count)]
|
||||
else:
|
||||
# known types
|
||||
try:
|
||||
# Bare types
|
||||
tl_elem = tl.constructor_type[type_]
|
||||
except KeyError:
|
||||
# Boxed types
|
||||
Idata = bytes_io.read(4)
|
||||
i = struct.unpack('<i', Idata)[0] # read type ID
|
||||
try:
|
||||
tl_elem = tl.constructor_id[i]
|
||||
except KeyError:
|
||||
# Unknown type
|
||||
raise Exception("Could not extract type: %s" % type_)
|
||||
|
||||
base_boxed_types = ["Vector t", "Int", "Long", "Double", "String", "Int128", "Int256"]
|
||||
if tl_elem.type in base_boxed_types:
|
||||
x = deserialize(bytes_io, type_=tl_elem.predicate, subtype=subtype)
|
||||
else: # other types
|
||||
x = TLObject(tl_elem)
|
||||
for arg in tl_elem.params:
|
||||
x[arg['name']] = deserialize(bytes_io, type_=arg['type'], subtype=arg['subtype'])
|
||||
return x
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
||||
require_once 'libpy2php.php';
|
||||
$__all__ = ['Chat', 'User', 'Message', 'Contact'];
|
@ -1,11 +0,0 @@
|
||||
|
||||
|
||||
from . import chat, user, message, contact
|
||||
|
||||
from .chat import Chat
|
||||
from .user import User
|
||||
from .message import Message
|
||||
from .contact import Contact
|
||||
|
||||
__all__ = ['Chat', 'User', 'Message', 'Contact']
|
||||
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
||||
require_once 'libpy2php.php';
|
||||
class chat
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->_users = [];
|
||||
}
|
||||
|
||||
public function add_user($user)
|
||||
{
|
||||
$this->_users += $user;
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
|
||||
class Chat():
|
||||
def __init__(self):
|
||||
self._users = [] # users in this chatroom
|
||||
def add_user(self, user):
|
||||
self._users += user
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
||||
require_once 'libpy2php.php';
|
||||
class contact
|
||||
{
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
|
||||
class Contact():
|
||||
pass
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
||||
require_once 'libpy2php.php';
|
||||
require_once 'os.php';
|
||||
class file
|
||||
{
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->_path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* truncates the file and create new with :param bytes.
|
||||
* :return number of bytes written.
|
||||
*/
|
||||
public function write_bytes($bytes)
|
||||
{
|
||||
// py2php.fixme "with" unsupported.
|
||||
}
|
||||
|
||||
/**
|
||||
* read the file as bytes. :return b'' on file not exist.
|
||||
*/
|
||||
public function read_bytes()
|
||||
{
|
||||
if (!(new exists($this->_path))) {
|
||||
return '';
|
||||
}
|
||||
// py2php.fixme "with" unsupported.
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to open with os default viewer.
|
||||
*/
|
||||
public function open()
|
||||
{
|
||||
new call((os::name == 'nt') ? 'cmd /c start "" "'.$this->_path.'"' : [platform::startswith('darwin') ? 'open' : 'xdg-open', $this->_path]);
|
||||
}
|
||||
|
||||
/**
|
||||
* try to remove the file.
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
try {
|
||||
os::remove($this->_path);
|
||||
} catch (FileNotFoundError $e) {
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
from sys import platform
|
||||
import os
|
||||
from subprocess import call
|
||||
from os.path import exists
|
||||
|
||||
class File():
|
||||
def __init__(self, path):
|
||||
self._path = path
|
||||
|
||||
def write_bytes(self, bytes):
|
||||
''' truncates the file and create new with :param bytes.
|
||||
:return number of bytes written'''
|
||||
with open(self._path, 'w+b') as file:
|
||||
return file.write(bytes)
|
||||
|
||||
def read_bytes(self):
|
||||
''' read the file as bytes. :return b'' on file not exist '''
|
||||
if not exists(self._path): return b''
|
||||
# buf = b''
|
||||
with open(self._path, 'r+b') as file:
|
||||
return file.read()
|
||||
# return buf
|
||||
|
||||
def open(self):
|
||||
'''tries to open with os default viewer'''
|
||||
call(('cmd /c start "" "'+ self._path +'"')if os.name is 'nt' else ('open' if platform.startswith('darwin') else 'xdg-open', self._path))
|
||||
|
||||
def remove(self):
|
||||
''' try to remove the file '''
|
||||
try:
|
||||
os.remove(self._path)
|
||||
except FileNotFoundError: pass
|
@ -1 +0,0 @@
|
||||
../libpy2php/
|
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
||||
require_once 'libpy2php.php';
|
||||
require_once 'crypt.php';
|
||||
class message
|
||||
{
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
import crypt
|
||||
|
||||
class Message():
|
||||
pass
|
@ -1,448 +0,0 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
||||
require_once 'libpy2php.php';
|
||||
require_once 'os.php';
|
||||
class TelepyShell
|
||||
{
|
||||
public $intro = 'Welcome to telepy interactive shell. Type help or ? for help.
|
||||
';
|
||||
public $prompt = '>';
|
||||
|
||||
public function preloop()
|
||||
{
|
||||
require_once 'classes/telepy.php';
|
||||
$this->_telepy = new Telepy();
|
||||
}
|
||||
|
||||
public function precmd($line)
|
||||
{
|
||||
$line = $line->lstrip();
|
||||
$blank_pos = $line->find(' ');
|
||||
if (($blank_pos < 0)) {
|
||||
return $line->lower();
|
||||
}
|
||||
|
||||
return array_slice($line, null, $blank_pos)->lower().' '.array_slice($line, ($blank_pos + 1), null);
|
||||
}
|
||||
|
||||
public function completedefault(...$ignored)
|
||||
{
|
||||
pyjslib_printnl($ignored);
|
||||
}
|
||||
|
||||
public function complete($text, $state)
|
||||
{
|
||||
$this->super()->complete($text, $state);
|
||||
pyjslib_printnl('completing');
|
||||
}
|
||||
|
||||
/**
|
||||
* shell <command-line>
|
||||
* lets you use external shell. !<command-line> for short-hand.
|
||||
*/
|
||||
public function do_shell($line)
|
||||
{
|
||||
pyjslib_printnl(os::popen($line)->read());
|
||||
}
|
||||
|
||||
/**
|
||||
* msg <peer>
|
||||
* sends message to this peer.
|
||||
*/
|
||||
public function do_msg($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* fwd <user> <msg-no>
|
||||
* forward message to user. You can see message numbers starting client with -N.
|
||||
*/
|
||||
public function do_fwd($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* chat_with_peer <peer>
|
||||
* starts one on one chat session with this peer. /exit or /quit to end this mode.
|
||||
*/
|
||||
public function do_chat_with_peer($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* add_contact <phone-number> <first-name> <last-name>
|
||||
* tries to add contact to contact-list by phone.
|
||||
*/
|
||||
public function do_add_contact($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* rename_contact <user> <first-name> <last-name>
|
||||
* tries to rename contact. If you have another device it will be a fight.
|
||||
*/
|
||||
public function do_rename_contact($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* mark_read <peer>
|
||||
* mark read all received messages with peer.
|
||||
*/
|
||||
public function do_mark_read($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* delete_msg <msg-no>
|
||||
* deletes message (not completly, though).
|
||||
*/
|
||||
public function do_delete_msg($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* restore_msg <msg-no>
|
||||
* restores delete message. Impossible for secret chats. Only possible short time (one hour, I think) after deletion.
|
||||
*/
|
||||
public function do_restore_msg($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* send_photo <peer> <photo-file-name>
|
||||
* sends photo to peer.
|
||||
*/
|
||||
public function do_send_photo($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* send_video <peer> <video-file-name>
|
||||
* sends video to peer.
|
||||
*/
|
||||
public function do_send_video($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* send_text <peer> <text-file-name>
|
||||
* sends text file as plain messages.
|
||||
*/
|
||||
public function do_send_text($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* load_photo <msg-no>
|
||||
* loads photo to download dir.
|
||||
*/
|
||||
public function do_load_photo($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* load_video <msg-no>
|
||||
* loads video to download dir.
|
||||
*/
|
||||
public function do_load_video($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* load_video_thumb <msg-no>
|
||||
* loads video thumbnail to download dir.
|
||||
*/
|
||||
public function do_load_video_thumb($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* load_audio <msg-no>
|
||||
* loads audio to download dir.
|
||||
*/
|
||||
public function do_load_audio($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* load_document <msg-no>
|
||||
* loads document to download dir.
|
||||
*/
|
||||
public function do_load_document($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* load_document_thumb <msg-no>
|
||||
* loads document thumbnail to download dir.
|
||||
*/
|
||||
public function do_load_document_thumb($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* view_photo <msg-no>
|
||||
* loads photo/video to download dir and starts system default viewer.
|
||||
*/
|
||||
public function do_view_photo($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* view_video <msg-no>.
|
||||
*/
|
||||
public function do_view_video($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* view_video_thumb <msg-no>.
|
||||
*/
|
||||
public function do_view_video_thumb($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* view_audio <msg-no>.
|
||||
*/
|
||||
public function do_view_audio($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* view_document <msg-no>.
|
||||
*/
|
||||
public function do_view_document($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* view_document_thumb <msg-no>.
|
||||
*/
|
||||
public function do_view_document_thumb($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* fwd_media <msg-no>
|
||||
* send media in your message. Use this to prevent sharing info about author of media (though, it is possible to determine user_id from media itself, it is not possible get access_hash of this user).
|
||||
*/
|
||||
public function do_fwd_media($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* set_profile_photo <photo-file-name>
|
||||
* sets userpic. Photo should be square, or server will cut biggest central square part.
|
||||
*/
|
||||
public function do_set_profile_photo($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* chat_info <chat>
|
||||
* prints info about chat.
|
||||
*/
|
||||
public function do_chat_info($arg)
|
||||
{
|
||||
$arg = $arg->split();
|
||||
if ((count($arg) == 1)) {
|
||||
pyjslib_printnl(['chat_info called with ', $arg[0]]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* chat_add_user <chat> <user>
|
||||
* add user to chat.
|
||||
*/
|
||||
public function do_chat_add_user($arg)
|
||||
{
|
||||
pyjslib_printnl($arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* chat_del_user <chat> <user>
|
||||
* remove user from chat.
|
||||
*/
|
||||
public function do_chat_del_user($arg)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* chat_rename <chat> <new-name>
|
||||
* rename chat room.
|
||||
*/
|
||||
public function do_chat_rename($arg)
|
||||
{
|
||||
$arg = $arg->split();
|
||||
}
|
||||
|
||||
/**
|
||||
* create_group_chat <chat topic> <user1> <user2> <user3> ...
|
||||
* creates a groupchat with users, use chat_add_user to add more users.
|
||||
*/
|
||||
public function do_create_group_chat($chat_topic, $user1, $user2, $user3)
|
||||
{
|
||||
pyjslib_printnl($chat_topic);
|
||||
pyjslib_printnl([$user1, $user2, $user3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* chat_set_photo <chat> <photo-file-name>
|
||||
* sets group chat photo. Same limits as for profile photos.
|
||||
*/
|
||||
public function do_chat_set_photo($chat, $photo)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* search <peer> <pattern>
|
||||
* searches pattern in messages with peer.
|
||||
*/
|
||||
public function do_search($pattern)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* global_search <pattern>
|
||||
* searches pattern in all messages.
|
||||
*/
|
||||
public function do_global_search($pattern)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* create_secret_chat <user>
|
||||
* creates secret chat with this user.
|
||||
*/
|
||||
public function do_create_secret_chat($user)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* visualize_key <secret_chat>
|
||||
* prints visualization of encryption key. You should compare it to your partner's one.
|
||||
*/
|
||||
public function do_visualize_key($secret_chat)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* set_ttl <secret_chat> <ttl>
|
||||
* sets ttl to secret chat. Though client does ignore it, client on other end can make use of it.
|
||||
*/
|
||||
public function do_set_ttl($secret_chat, $ttl)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* accept_secret_chat <secret_chat>
|
||||
* manually accept secret chat (only useful when starting with -E key).
|
||||
*/
|
||||
public function do_accept_secret_chat($secret_chat)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* user_info <user>
|
||||
* prints info about user.
|
||||
*/
|
||||
public function do_user_info($user)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* history <peer> [limit]
|
||||
* prints history (and marks it as read). Default limit = 40.
|
||||
*/
|
||||
public function do_history($peer, $limit = 40)
|
||||
{
|
||||
if (($peer == '')) {
|
||||
pyjslib_printnl('no peer have specified');
|
||||
|
||||
return;
|
||||
}
|
||||
$args = $peer->split();
|
||||
if (!in_array(count($args), [1, 2])) {
|
||||
pyjslib_printnl(['not appropriate number of arguments : ', $peer]);
|
||||
|
||||
return;
|
||||
}
|
||||
if ((count($args) == 2)) {
|
||||
if (!($args[1]->isdecimal()) || (pyjslib_int($args[1]) < 1)) {
|
||||
pyjslib_printnl(['not a valid limit:', $args[1]]);
|
||||
}
|
||||
$limit = pyjslib_int($args[1]);
|
||||
}
|
||||
pyjslib_printnl($peer);
|
||||
pyjslib_printnl($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* dialog_list
|
||||
* prints info about your dialogs.
|
||||
*/
|
||||
public function do_dialog_list($ignored)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* contact_list
|
||||
* prints info about users in your contact list.
|
||||
*/
|
||||
public function do_contact_list($ignored)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* suggested_contacts
|
||||
* print info about contacts, you have max common friends.
|
||||
*/
|
||||
public function do_suggested_contacts($ignored)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* stats
|
||||
* just for debugging.
|
||||
*/
|
||||
public function do_stats($ignored)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* export_card
|
||||
* print your 'card' that anyone can later use to import your contact.
|
||||
*/
|
||||
public function do_export_card($card)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* import_card <card>
|
||||
* gets user by card. You can write messages to him after that.
|
||||
*/
|
||||
public function do_import_card($card)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* quit_force
|
||||
* quit without waiting for query ends.
|
||||
*/
|
||||
public function do_quit_force($ignored)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* quit
|
||||
* wait for all queries to end then quit.
|
||||
*/
|
||||
public function do_quit($ignored)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,337 +0,0 @@
|
||||
import os, cmd
|
||||
|
||||
class TelepyShell(cmd.Cmd):
|
||||
intro='Welcome to telepy interactive shell. Type help or ? for help.\n'
|
||||
prompt='>'
|
||||
|
||||
def preloop(self):
|
||||
from classes.telepy import Telepy
|
||||
self._telepy = Telepy()
|
||||
def precmd(self, line):
|
||||
# convert first word(command name) to lower and return it as line
|
||||
line = line.lstrip()
|
||||
blank_pos = line.find(' ')
|
||||
if blank_pos < 0: return line.lower()
|
||||
return line[:blank_pos].lower() + ' ' + line[blank_pos+1:]
|
||||
def completedefault(self, *ignored):
|
||||
print(ignored)
|
||||
def complete(self, text, state):
|
||||
self.super().complete(text, state)
|
||||
print('completing')
|
||||
|
||||
def do_shell(self, line):
|
||||
'''
|
||||
shell <command-line>
|
||||
lets you use external shell. !<command-line> for short-hand.
|
||||
'''
|
||||
print(os.popen(line).read())
|
||||
#detailed commands
|
||||
def do_msg(self, arg):
|
||||
'''
|
||||
msg <peer>
|
||||
sends message to this peer
|
||||
'''
|
||||
pass
|
||||
def do_fwd(self, arg):
|
||||
'''
|
||||
fwd <user> <msg-no>
|
||||
forward message to user. You can see message numbers starting client with -N
|
||||
'''
|
||||
pass
|
||||
def do_chat_with_peer(self, arg):
|
||||
'''
|
||||
chat_with_peer <peer>
|
||||
starts one on one chat session with this peer. /exit or /quit to end this mode.
|
||||
'''
|
||||
pass
|
||||
def do_add_contact(self, arg):
|
||||
'''
|
||||
add_contact <phone-number> <first-name> <last-name>
|
||||
tries to add contact to contact-list by phone
|
||||
'''
|
||||
pass
|
||||
def do_rename_contact(self, arg):
|
||||
'''
|
||||
rename_contact <user> <first-name> <last-name>
|
||||
tries to rename contact. If you have another device it will be a fight
|
||||
'''
|
||||
pass
|
||||
def do_mark_read(self, arg):
|
||||
'''
|
||||
mark_read <peer>
|
||||
mark read all received messages with peer
|
||||
'''
|
||||
pass
|
||||
def do_delete_msg(self, arg):
|
||||
'''
|
||||
delete_msg <msg-no>
|
||||
deletes message (not completly, though)
|
||||
'''
|
||||
pass
|
||||
def do_restore_msg(self, arg):
|
||||
'''
|
||||
restore_msg <msg-no>
|
||||
restores delete message. Impossible for secret chats. Only possible short time (one hour, I think) after deletion
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_send_photo(self, arg):
|
||||
'''
|
||||
send_photo <peer> <photo-file-name>
|
||||
sends photo to peer
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_send_video(self, arg):
|
||||
'''
|
||||
send_video <peer> <video-file-name>
|
||||
sends video to peer
|
||||
'''
|
||||
pass
|
||||
def do_send_text(self, arg):
|
||||
'''
|
||||
send_text <peer> <text-file-name>
|
||||
sends text file as plain messages
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_load_photo(self, arg):
|
||||
'''
|
||||
load_photo <msg-no>
|
||||
loads photo to download dir
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_load_video(self, arg):
|
||||
'''
|
||||
load_video <msg-no>
|
||||
loads video to download dir
|
||||
'''
|
||||
pass
|
||||
def do_load_video_thumb(self, arg):
|
||||
'''
|
||||
load_video_thumb <msg-no>
|
||||
loads video thumbnail to download dir
|
||||
'''
|
||||
pass
|
||||
def do_load_audio(self, arg):
|
||||
'''
|
||||
load_audio <msg-no>
|
||||
loads audio to download dir
|
||||
'''
|
||||
pass
|
||||
def do_load_document(self, arg):
|
||||
'''
|
||||
load_document <msg-no>
|
||||
loads document to download dir
|
||||
'''
|
||||
pass
|
||||
def do_load_document_thumb(self, arg):
|
||||
'''
|
||||
load_document_thumb <msg-no>
|
||||
loads document thumbnail to download dir
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_view_photo(self, arg):
|
||||
'''
|
||||
view_photo <msg-no>
|
||||
loads photo/video to download dir and starts system default viewer
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_view_video(self, arg):
|
||||
'''
|
||||
view_video <msg-no>
|
||||
'''
|
||||
pass
|
||||
def do_view_video_thumb(self, arg):
|
||||
'''
|
||||
view_video_thumb <msg-no>
|
||||
'''
|
||||
pass
|
||||
def do_view_audio(self, arg):
|
||||
'''
|
||||
view_audio <msg-no>
|
||||
'''
|
||||
pass
|
||||
def do_view_document(self, arg):
|
||||
'''
|
||||
view_document <msg-no>
|
||||
'''
|
||||
pass
|
||||
def do_view_document_thumb(self, arg):
|
||||
'''
|
||||
view_document_thumb <msg-no>
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_fwd_media(self, arg):
|
||||
'''
|
||||
fwd_media <msg-no>
|
||||
send media in your message. Use this to prevent sharing info about author of media (though, it is possible to determine user_id from media itself, it is not possible get access_hash of this user)
|
||||
'''
|
||||
pass
|
||||
def do_set_profile_photo(self, arg):
|
||||
'''
|
||||
set_profile_photo <photo-file-name>
|
||||
sets userpic. Photo should be square, or server will cut biggest central square part
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_chat_info(self, arg):
|
||||
'''
|
||||
chat_info <chat>
|
||||
prints info about chat
|
||||
'''
|
||||
arg=arg.split()
|
||||
if len(arg) is 1:
|
||||
print ('chat_info called with ', arg[0])
|
||||
def do_chat_add_user(self,arg):
|
||||
'''
|
||||
chat_add_user <chat> <user>
|
||||
add user to chat
|
||||
'''
|
||||
print(arg)
|
||||
def do_chat_del_user(self,arg):
|
||||
'''
|
||||
chat_del_user <chat> <user>
|
||||
remove user from chat
|
||||
'''
|
||||
pass
|
||||
def do_chat_rename(self,arg):
|
||||
'''
|
||||
chat_rename <chat> <new-name>
|
||||
rename chat room
|
||||
'''
|
||||
arg=arg.split()
|
||||
|
||||
def do_create_group_chat(self, chat_topic, user1, user2, user3):
|
||||
'''
|
||||
create_group_chat <chat topic> <user1> <user2> <user3> ...
|
||||
creates a groupchat with users, use chat_add_user to add more users
|
||||
'''
|
||||
print(chat_topic)
|
||||
print(user1,user2,user3)
|
||||
|
||||
pass
|
||||
def do_chat_set_photo(self, chat, photo):
|
||||
'''
|
||||
chat_set_photo <chat> <photo-file-name>
|
||||
sets group chat photo. Same limits as for profile photos.
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_search(self, pattern):
|
||||
'''
|
||||
search <peer> <pattern>
|
||||
searches pattern in messages with peer
|
||||
'''
|
||||
pass
|
||||
def do_global_search(self, pattern):
|
||||
'''
|
||||
global_search <pattern>
|
||||
searches pattern in all messages
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_create_secret_chat(self, user):
|
||||
'''
|
||||
create_secret_chat <user>
|
||||
creates secret chat with this user
|
||||
'''
|
||||
pass
|
||||
def do_visualize_key(self, secret_chat):
|
||||
'''
|
||||
visualize_key <secret_chat>
|
||||
prints visualization of encryption key. You should compare it to your partner's one
|
||||
'''
|
||||
pass
|
||||
def do_set_ttl(self, secret_chat, ttl):
|
||||
'''
|
||||
set_ttl <secret_chat> <ttl>
|
||||
sets ttl to secret chat. Though client does ignore it, client on other end can make use of it
|
||||
'''
|
||||
pass
|
||||
def do_accept_secret_chat(self, secret_chat):
|
||||
'''
|
||||
accept_secret_chat <secret_chat>
|
||||
manually accept secret chat (only useful when starting with -E key)
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_user_info(self, user):
|
||||
'''
|
||||
user_info <user>
|
||||
prints info about user
|
||||
'''
|
||||
pass
|
||||
def do_history(self, peer, limit=40):
|
||||
'''
|
||||
history <peer> [limit]
|
||||
prints history (and marks it as read). Default limit = 40
|
||||
'''
|
||||
if peer is '':
|
||||
print('no peer have specified')
|
||||
return
|
||||
args = peer.split()
|
||||
if len(args) not in (1,2) :
|
||||
print('not appropriate number of arguments : ', peer)
|
||||
return
|
||||
if len(args) is 2:
|
||||
if not args[1].isdecimal() or int(args[1]) < 1:
|
||||
print('not a valid limit:', args[1])
|
||||
limit = int(args[1])
|
||||
print(peer)
|
||||
print(limit)
|
||||
def do_dialog_list(self, ignored):
|
||||
'''
|
||||
dialog_list
|
||||
prints info about your dialogs
|
||||
'''
|
||||
pass
|
||||
def do_contact_list(self, ignored):
|
||||
'''
|
||||
contact_list
|
||||
prints info about users in your contact list
|
||||
'''
|
||||
pass
|
||||
def do_suggested_contacts(self, ignored):
|
||||
'''
|
||||
suggested_contacts
|
||||
print info about contacts, you have max common friends
|
||||
'''
|
||||
pass
|
||||
def do_stats(self, ignored):
|
||||
'''
|
||||
stats
|
||||
just for debugging
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_export_card(self, card):
|
||||
'''
|
||||
export_card
|
||||
print your 'card' that anyone can later use to import your contact
|
||||
'''
|
||||
pass
|
||||
def do_import_card(self, card):
|
||||
'''
|
||||
import_card <card>
|
||||
gets user by card. You can write messages to him after that.
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_quit_force(self, ignored):
|
||||
'''
|
||||
quit_force
|
||||
quit without waiting for query ends
|
||||
'''
|
||||
return True
|
||||
def do_quit(self, ignored):
|
||||
'''
|
||||
quit
|
||||
wait for all queries to end then quit
|
||||
'''
|
||||
#TODO:safely end queries
|
||||
return True
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
||||
require_once 'libpy2php.php';
|
||||
class telepy
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
try {
|
||||
require_once 'configparser.php';
|
||||
} catch (ImportError $e) {
|
||||
require_once 'ConfigParser.php';
|
||||
}
|
||||
require_once 'mtproto.php';
|
||||
$this->_config = $configparser->ConfigParser();
|
||||
if (!($this->_config->read('credentials'))) {
|
||||
pyjslib_printnl('File \'credentials\' seems to not exist.');
|
||||
$exit(-1);
|
||||
}
|
||||
$ip = $this->_config->get('App data', 'ip_address');
|
||||
$port = $this->_config->getint('App data', 'port');
|
||||
$this->_session = $mtproto->Session($ip, $port);
|
||||
$this->_session->create_auth_key();
|
||||
$__temp22 = py2php_kwargs_method_call($this->_session, 'method_call', ['get_future_salts'], ['num' => 3]);
|
||||
$this->_salt = $__temp22;
|
||||
$future_salts = $__temp22;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
|
||||
class Telepy():
|
||||
def __init__(self):
|
||||
# Deal with py2 and py3 differences
|
||||
try: # this only works in py2.7
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
import mtproto
|
||||
|
||||
self._config = configparser.ConfigParser()
|
||||
# Check if credentials is correctly loaded (when it doesn't read anything it returns [])
|
||||
if not self._config.read('credentials'):
|
||||
print("File 'credentials' seems to not exist.")
|
||||
exit(-1)
|
||||
ip = self._config.get('App data', 'ip_address')
|
||||
port = self._config.getint('App data', 'port')
|
||||
|
||||
self._session = mtproto.Session(ip, port)
|
||||
self._session.create_auth_key()
|
||||
|
||||
self._salt = future_salts = self._session.method_call('get_future_salts', num=3)
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).DIRECTORY_SEPARATOR.'libpy2php');
|
||||
require_once 'libpy2php.php';
|
||||
class user
|
||||
{
|
||||
public $me = null;
|
||||
// current connected user
|
||||
public $friends = [];
|
||||
|
||||
// current connected user's friends
|
||||
public function __construct($uid)
|
||||
{
|
||||
$this->uid = $uid;
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
|
||||
|
||||
class User():
|
||||
me = None
|
||||
''' current connected user '''
|
||||
|
||||
friends = []
|
||||
''' current connected user's friends '''
|
||||
|
||||
def __init__(self, uid):
|
||||
self.uid = uid
|
@ -1,5 +0,0 @@
|
||||
[App data]
|
||||
api_id = 25628
|
||||
api_hash = 1fe17cda7d355166cdaa71f04122873c
|
||||
ip_address = 149.154.167.50
|
||||
port = 443
|
@ -1,67 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Author: Sammy Pfeiffer
|
||||
# Author: Anton Grigoryev
|
||||
# This file implements the AES 256 IGE cipher
|
||||
# working in Python 2.7 and Python 3.4 (other versions untested)
|
||||
# as it's needed for the implementation of Telegram API
|
||||
# It's based on PyCryto
|
||||
|
||||
from __future__ import print_function
|
||||
from Crypto.Util.strxor import strxor
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
# AES 256 IGE part
|
||||
|
||||
def ige_encrypt(message, key, iv):
|
||||
return _ige(message, key, iv, operation="encrypt")
|
||||
|
||||
def ige_decrypt(message, key, iv):
|
||||
return _ige(message, key, iv, operation="decrypt")
|
||||
|
||||
def _ige(message, key, iv, operation="decrypt"):
|
||||
"""Given a key, given an iv, and message
|
||||
do whatever operation asked in the operation field.
|
||||
Operation will be checked for: "decrypt" and "encrypt" strings.
|
||||
Returns the message encrypted/decrypted.
|
||||
message must be a multiple by 16 bytes (for division in 16 byte blocks)
|
||||
key must be 32 byte
|
||||
iv must be 32 byte (it's not internally used in AES 256 ECB, but it's
|
||||
needed for IGE)"""
|
||||
message = bytes(message)
|
||||
if len(key) != 32:
|
||||
raise ValueError("key must be 32 bytes long (was " +
|
||||
str(len(key)) + " bytes)")
|
||||
if len(iv) != 32:
|
||||
raise ValueError("iv must be 32 bytes long (was " +
|
||||
str(len(iv)) + " bytes)")
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB, iv)
|
||||
blocksize = cipher.block_size
|
||||
|
||||
if len(message) % blocksize != 0:
|
||||
raise ValueError("message must be a multiple of 16 bytes (try adding " +
|
||||
str(16 - len(message) % 16) + " bytes of padding)")
|
||||
|
||||
ivp = iv[0:blocksize]
|
||||
ivp2 = iv[blocksize:]
|
||||
|
||||
ciphered = bytes()
|
||||
|
||||
for i in range(0, len(message), blocksize):
|
||||
indata = message[i:i+blocksize]
|
||||
if operation == "decrypt":
|
||||
xored = strxor(indata, ivp2)
|
||||
decrypt_xored = cipher.decrypt(xored)
|
||||
outdata = strxor(decrypt_xored, ivp)
|
||||
ivp = indata
|
||||
ivp2 = outdata
|
||||
elif operation == "encrypt":
|
||||
xored = strxor(indata, ivp)
|
||||
encrypt_xored = cipher.encrypt(xored)
|
||||
outdata = strxor(encrypt_xored, ivp2)
|
||||
ivp = outdata
|
||||
ivp2 = indata
|
||||
else:
|
||||
raise ValueError("operation must be either 'decrypt' or 'encrypt'")
|
||||
ciphered += outdata
|
||||
return ciphered
|
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import prime
|
||||
import sys
|
||||
pq = prime.primefactors(int(sys.argv[1]))
|
||||
sys.stdout.write(str(pq[0]) + " " + str(pq[1]))
|
||||
sys.stdout.flush()
|
@ -1,270 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on Tue Sep 2 19:26:15 2014
|
||||
|
||||
@author: Anton Grigoryev
|
||||
@author: Sammy Pfeiffer
|
||||
"""
|
||||
from binascii import crc32 as originalcrc32
|
||||
from binascii import hexlify
|
||||
from time import time
|
||||
import io
|
||||
import os.path
|
||||
import socket
|
||||
import struct
|
||||
|
||||
# pycrypto module
|
||||
from Crypto.Hash import SHA
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.strxor import strxor
|
||||
from Crypto.Util.number import long_to_bytes, bytes_to_long
|
||||
|
||||
# local modules
|
||||
import crypt
|
||||
import prime
|
||||
import TL
|
||||
|
||||
def crc32(data):
|
||||
return originalcrc32(data) & 0xffffffff
|
||||
|
||||
def vis(bs):
|
||||
"""
|
||||
Function to visualize byte streams. Split into bytes, print to console.
|
||||
:param bs: BYTE STRING
|
||||
"""
|
||||
bs = bytearray(bs)
|
||||
symbols_in_one_line = 8
|
||||
n = len(bs) // symbols_in_one_line
|
||||
i = 0
|
||||
for i in range(n):
|
||||
print(str(i*symbols_in_one_line)+" | "+" ".join(["%02X" % b for b in bs[i*symbols_in_one_line:(i+1)*symbols_in_one_line]])) # for every 8 symbols line
|
||||
if not len(bs) % symbols_in_one_line == 0:
|
||||
print(str((i+1)*symbols_in_one_line)+" | "+" ".join(["%02X" % b for b in bs[(i+1)*symbols_in_one_line:]])+"\n") # for last line
|
||||
|
||||
class Session:
|
||||
""" Manages TCP Transport. encryption and message frames """
|
||||
def __init__(self, ip, port, auth_key=None, server_salt=None):
|
||||
# creating socket
|
||||
self.sock = socket.socket()
|
||||
self.sock.connect((ip, port))
|
||||
self.number = 0
|
||||
self.timedelta = 0
|
||||
self.session_id = os.urandom(8)
|
||||
self.auth_key = auth_key
|
||||
self.auth_key_id = SHA.new(self.auth_key).digest()[-8:] if self.auth_key else None
|
||||
self.sock.settimeout(5.0)
|
||||
self.MAX_RETRY = 5;
|
||||
self.AUTH_MAX_RETRY = 5;
|
||||
|
||||
def __del__(self):
|
||||
# closing socket when session object is deleted
|
||||
self.sock.close()
|
||||
|
||||
def send_message(self, message_data):
|
||||
"""
|
||||
Forming the message frame and sending message to server
|
||||
:param message: byte string to send
|
||||
"""
|
||||
message_id = struct.pack('<Q', int((time()+self.timedelta)*2**30)*4)
|
||||
if self.auth_key is None or self.server_salt is None:
|
||||
# Unencrypted data send
|
||||
message = (b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
||||
message_id +
|
||||
struct.pack('<I', len(message_data)) +
|
||||
message_data)
|
||||
else:
|
||||
# Encrypted data send
|
||||
encrypted_data = (self.server_salt +
|
||||
self.session_id +
|
||||
message_id +
|
||||
struct.pack('<II', self.number, len(message_data)) +
|
||||
message_data)
|
||||
message_key = SHA.new(encrypted_data).digest()[-16:]
|
||||
padding = os.urandom((-len(encrypted_data)) % 16)
|
||||
aes_key, aes_iv = self.aes_calculate(message_key)
|
||||
|
||||
message = (self.auth_key_id + message_key +
|
||||
crypt.ige_encrypt(encrypted_data+padding, aes_key, aes_iv))
|
||||
|
||||
step1 = struct.pack('<II', len(message)+12, self.number) + message
|
||||
step2 = step1 + struct.pack('<I', crc32(step1))
|
||||
self.sock.send(step2)
|
||||
self.number += 1
|
||||
|
||||
def recv_message(self):
|
||||
"""
|
||||
Reading socket and receiving message from server. Check the CRC32.
|
||||
"""
|
||||
packet_length_data = self.sock.recv(4) # reads how many bytes to read
|
||||
|
||||
if len(packet_length_data) < 4:
|
||||
raise Exception("Nothing in the socket!")
|
||||
packet_length = struct.unpack("<I", packet_length_data)[0]
|
||||
packet = self.sock.recv(packet_length - 4) # read the rest of bytes from socket
|
||||
# check the CRC32
|
||||
if not crc32(packet_length_data + packet[0:-4]) == struct.unpack('<I', packet[-4:])[0]:
|
||||
raise Exception("CRC32 was not correct!")
|
||||
x = struct.unpack("<I", packet[:4])
|
||||
auth_key_id = packet[4:12]
|
||||
if auth_key_id == b'\x00\x00\x00\x00\x00\x00\x00\x00':
|
||||
# No encryption - Plain text
|
||||
(message_id, message_length) = struct.unpack("<8sI", packet[12:24])
|
||||
data = packet[24:24+message_length]
|
||||
elif auth_key_id == self.auth_key_id:
|
||||
message_key = packet[12:28]
|
||||
encrypted_data = packet[28:-4]
|
||||
aes_key, aes_iv = self.aes_calculate(message_key, direction="from server")
|
||||
decrypted_data = crypt.ige_decrypt(encrypted_data, aes_key, aes_iv)
|
||||
assert decrypted_data[0:8] == self.server_salt
|
||||
assert decrypted_data[8:16] == self.session_id
|
||||
message_id = decrypted_data[16:24]
|
||||
seq_no = struct.unpack("<I", decrypted_data[24:28])[0]
|
||||
message_data_length = struct.unpack("<I", decrypted_data[28:32])[0]
|
||||
data = decrypted_data[32:32+message_data_length]
|
||||
else:
|
||||
raise Exception("Got unknown auth_key id")
|
||||
return data
|
||||
|
||||
def method_call(self, method, **kwargs):
|
||||
#print(kwargs)
|
||||
for i in range(1, self.MAX_RETRY):
|
||||
try:
|
||||
self.send_message(TL.serialize_method(method, **kwargs))
|
||||
server_answer = self.recv_message()
|
||||
except socket.timeout:
|
||||
print("Retry call method")
|
||||
continue
|
||||
return TL.deserialize(io.BytesIO(server_answer))
|
||||
|
||||
def create_auth_key(self):
|
||||
|
||||
nonce = os.urandom(16)
|
||||
print("Requesting pq")
|
||||
|
||||
f = open(os.path.join(os.path.dirname(__file__), "rsa.pub"))
|
||||
key = RSA.importKey(f.read())
|
||||
ResPQ = self.method_call('req_pq', nonce=nonce)
|
||||
server_nonce = ResPQ['server_nonce']
|
||||
# TODO: selecting RSA public key based on this fingerprint
|
||||
public_key_fingerprint = ResPQ['server_public_key_fingerprints'][0]
|
||||
|
||||
pq_bytes = ResPQ['pq']
|
||||
pq = bytes_to_long(pq_bytes)
|
||||
[p, q] = prime.primefactors(pq)
|
||||
if p > q: (p, q) = (q, p)
|
||||
assert p*q == pq and p < q
|
||||
|
||||
print("Factorization %d = %d * %d" % (pq, p, q))
|
||||
p_bytes = long_to_bytes(p)
|
||||
q_bytes = long_to_bytes(q)
|
||||
|
||||
new_nonce = os.urandom(32)
|
||||
data = TL.serialize_obj('p_q_inner_data',
|
||||
pq=pq_bytes,
|
||||
p=p_bytes,
|
||||
q=q_bytes,
|
||||
nonce=nonce,
|
||||
server_nonce=server_nonce,
|
||||
new_nonce=new_nonce)
|
||||
sha_digest = SHA.new(data).digest()
|
||||
random_bytes = os.urandom(255-len(data)-len(sha_digest))
|
||||
to_encrypt = sha_digest + data + random_bytes
|
||||
encrypted_data = key.encrypt(to_encrypt, 0)[0]
|
||||
print("Starting Diffie Hellman key exchange", len(to_encrypt))
|
||||
server_dh_params = self.method_call('req_DH_params',
|
||||
nonce=nonce,
|
||||
server_nonce=server_nonce,
|
||||
p=p_bytes,
|
||||
q=q_bytes,
|
||||
public_key_fingerprint=public_key_fingerprint,
|
||||
encrypted_data=encrypted_data)
|
||||
assert nonce == server_dh_params['nonce']
|
||||
assert server_nonce == server_dh_params['server_nonce']
|
||||
|
||||
encrypted_answer = server_dh_params['encrypted_answer']
|
||||
|
||||
tmp_aes_key = SHA.new(new_nonce + server_nonce).digest() + SHA.new(server_nonce + new_nonce).digest()[0:12]
|
||||
tmp_aes_iv = SHA.new(server_nonce + new_nonce).digest()[12:20] + SHA.new(new_nonce + new_nonce).digest() + new_nonce[0:4]
|
||||
|
||||
answer_with_hash = crypt.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
|
||||
|
||||
answer_hash = answer_with_hash[:20]
|
||||
answer = answer_with_hash[20:]
|
||||
# TODO: SHA hash assertion here
|
||||
|
||||
server_DH_inner_data = TL.deserialize(io.BytesIO(answer))
|
||||
assert nonce == server_DH_inner_data['nonce']
|
||||
assert server_nonce == server_DH_inner_data['server_nonce']
|
||||
dh_prime_str = server_DH_inner_data['dh_prime']
|
||||
g = server_DH_inner_data['g']
|
||||
g_a_str = server_DH_inner_data['g_a']
|
||||
server_time = server_DH_inner_data['server_time']
|
||||
self.timedelta = server_time - time()
|
||||
print("Server-client time delta = %.1f s" % self.timedelta)
|
||||
|
||||
dh_prime = bytes_to_long(dh_prime_str)
|
||||
g_a = bytes_to_long(g_a_str)
|
||||
|
||||
assert prime.isprime(dh_prime)
|
||||
retry_id = 0
|
||||
b_str = os.urandom(256)
|
||||
b = bytes_to_long(b_str)
|
||||
g_b = pow(g, b, dh_prime)
|
||||
|
||||
g_b_str = long_to_bytes(g_b)
|
||||
|
||||
data = TL.serialize_obj('client_DH_inner_data',
|
||||
nonce=nonce,
|
||||
server_nonce=server_nonce,
|
||||
retry_id=retry_id,
|
||||
g_b=g_b_str)
|
||||
data_with_sha = SHA.new(data).digest()+data
|
||||
data_with_sha_padded = data_with_sha + os.urandom(-len(data_with_sha) % 16)
|
||||
encrypted_data = crypt.ige_encrypt(data_with_sha_padded, tmp_aes_key, tmp_aes_iv)
|
||||
|
||||
for i in range(1, self.AUTH_MAX_RETRY): # retry when dh_gen_retry or dh_gen_fail
|
||||
Set_client_DH_params_answer = self.method_call('set_client_DH_params',
|
||||
nonce=nonce,
|
||||
server_nonce=server_nonce,
|
||||
encrypted_data=encrypted_data)
|
||||
|
||||
# print Set_client_DH_params_answer
|
||||
auth_key = pow(g_a, b, dh_prime)
|
||||
auth_key_str = long_to_bytes(auth_key)
|
||||
auth_key_sha = SHA.new(auth_key_str).digest()
|
||||
auth_key_aux_hash = auth_key_sha[:8]
|
||||
|
||||
new_nonce_hash1 = SHA.new(new_nonce+b'\x01'+auth_key_aux_hash).digest()[-16:]
|
||||
new_nonce_hash2 = SHA.new(new_nonce+b'\x02'+auth_key_aux_hash).digest()[-16:]
|
||||
new_nonce_hash3 = SHA.new(new_nonce+b'\x03'+auth_key_aux_hash).digest()[-16:]
|
||||
|
||||
assert Set_client_DH_params_answer['nonce'] == nonce
|
||||
assert Set_client_DH_params_answer['server_nonce'] == server_nonce
|
||||
|
||||
if Set_client_DH_params_answer.name == 'dh_gen_ok':
|
||||
assert Set_client_DH_params_answer['new_nonce_hash1'] == new_nonce_hash1
|
||||
print("Diffie Hellman key exchange processed successfully")
|
||||
|
||||
self.server_salt = strxor(new_nonce[0:8], server_nonce[0:8])
|
||||
self.auth_key = auth_key_str
|
||||
self.auth_key_id = auth_key_sha[-8:]
|
||||
print("Auth key generated")
|
||||
return "Auth Ok"
|
||||
elif Set_client_DH_params_answer.name == 'dh_gen_retry':
|
||||
assert Set_client_DH_params_answer['new_nonce_hash2'] == new_nonce_hash2
|
||||
print ("Retry Auth")
|
||||
elif Set_client_DH_params_answer.name == 'dh_gen_fail':
|
||||
assert Set_client_DH_params_answer['new_nonce_hash3'] == new_nonce_hash3
|
||||
print("Auth Failed")
|
||||
raise Exception("Auth Failed")
|
||||
else: raise Exception("Response Error")
|
||||
|
||||
def aes_calculate(self, msg_key, direction="to server"):
|
||||
x = 0 if direction == "to server" else 8
|
||||
sha1_a = SHA.new(msg_key + self.auth_key[x:x+32]).digest()
|
||||
sha1_b = SHA.new(self.auth_key[x+32:x+48] + msg_key + self.auth_key[48+x:64+x]).digest()
|
||||
sha1_c = SHA.new(self.auth_key[x+64:x+96] + msg_key).digest()
|
||||
sha1_d = SHA.new(msg_key + self.auth_key[x+96:x+128]).digest()
|
||||
aes_key = sha1_a[0:8] + sha1_b[8:20] + sha1_c[4:16]
|
||||
aes_iv = sha1_a[8:20] + sha1_b[0:8] + sha1_c[16:20] + sha1_d[0:8]
|
||||
return aes_key, aes_iv
|
@ -1,142 +0,0 @@
|
||||
# NOTICE!!! This is copied from https://stackoverflow.com/questions/4643647/fast-prime-factorization-module
|
||||
|
||||
import random
|
||||
|
||||
def primesbelow(N):
|
||||
# http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
|
||||
#""" Input N>=6, Returns a list of primes, 2 <= p < N """
|
||||
correction = N % 6 > 1
|
||||
N = {0:N, 1:N-1, 2:N+4, 3:N+3, 4:N+2, 5:N+1}[N%6]
|
||||
sieve = [True] * (N // 3)
|
||||
sieve[0] = False
|
||||
for i in range(int(N ** .5) // 3 + 1):
|
||||
if sieve[i]:
|
||||
k = (3 * i + 1) | 1
|
||||
sieve[k*k // 3::2*k] = [False] * ((N//6 - (k*k)//6 - 1)//k + 1)
|
||||
sieve[(k*k + 4*k - 2*k*(i%2)) // 3::2*k] = [False] * ((N // 6 - (k*k + 4*k - 2*k*(i%2))//6 - 1) // k + 1)
|
||||
return [2, 3] + [(3 * i + 1) | 1 for i in range(1, N//3 - correction) if sieve[i]]
|
||||
|
||||
smallprimeset = set(primesbelow(100000))
|
||||
_smallprimeset = 100000
|
||||
|
||||
def isprime(n, precision=7):
|
||||
# http://en.wikipedia.org/wiki/Miller-Rabin_primality_test#Algorithm_and_running_time
|
||||
if n == 1 or n % 2 == 0:
|
||||
return False
|
||||
elif n < 1:
|
||||
raise ValueError("Out of bounds, first argument must be > 0")
|
||||
elif n < _smallprimeset:
|
||||
return n in smallprimeset
|
||||
|
||||
|
||||
d = n - 1
|
||||
s = 0
|
||||
while d % 2 == 0:
|
||||
d //= 2
|
||||
s += 1
|
||||
|
||||
for repeat in range(precision):
|
||||
a = random.randrange(2, n - 2)
|
||||
x = pow(a, d, n)
|
||||
|
||||
if x == 1 or x == n - 1: continue
|
||||
|
||||
for r in range(s - 1):
|
||||
x = pow(x, 2, n)
|
||||
if x == 1: return False
|
||||
if x == n - 1: break
|
||||
else: return False
|
||||
|
||||
return True
|
||||
|
||||
# https://comeoncodeon.wordpress.com/2010/09/18/pollard-rho-brent-integer-factorization/
|
||||
def pollard_brent(n):
|
||||
if n % 2 == 0: return 2
|
||||
if n % 3 == 0: return 3
|
||||
|
||||
y, c, m = 87552211475113995, 330422027228888537, 226866727920975483
|
||||
#random.randint(1, n-1), random.randint(1, n-1), random.randint(1, n-1)
|
||||
|
||||
g, r, q = 1, 1, 1
|
||||
while g == 1:
|
||||
x = y
|
||||
for i in range(r):
|
||||
y = (pow(y, 2, n) + c) % n
|
||||
|
||||
k = 0
|
||||
while k < r and g==1:
|
||||
ys = y
|
||||
for i in range(min(m, r-k)):
|
||||
y = (pow(y, 2, n) + c) % n
|
||||
q = q * abs(x-y) % n
|
||||
|
||||
g = gcd(q, n)
|
||||
k += m
|
||||
r *= 2
|
||||
if g == n:
|
||||
while True:
|
||||
ys = (pow(ys, 2, n) + c) % n
|
||||
g = gcd(abs(x - ys), n)
|
||||
if g > 1:
|
||||
break
|
||||
|
||||
return g
|
||||
|
||||
smallprimes = primesbelow(10000) # might seem low, but 1000*1000 = 1000000, so this will fully factor every composite < 1000000
|
||||
def primefactors(n, sort=False):
|
||||
factors = []
|
||||
|
||||
limit = int(n ** .5) + 1
|
||||
for checker in smallprimes:
|
||||
if checker > limit: break
|
||||
while n % checker == 0:
|
||||
factors.append(checker)
|
||||
n //= checker
|
||||
limit = int(n ** .5) + 1
|
||||
if checker > limit: break
|
||||
|
||||
if n < 2: return factors
|
||||
|
||||
while n > 1:
|
||||
if isprime(n):
|
||||
factors.append(n)
|
||||
break
|
||||
factor = pollard_brent(n) # trial division did not fully factor, switch to pollard-brent
|
||||
factors.extend(primefactors(factor)) # recurse to factor the not necessarily prime factor returned by pollard-brent
|
||||
n //= factor
|
||||
|
||||
if sort: factors.sort()
|
||||
|
||||
return factors
|
||||
|
||||
def factorization(n):
|
||||
factors = {}
|
||||
for p1 in primefactors(n):
|
||||
try:
|
||||
factors[p1] += 1
|
||||
except KeyError:
|
||||
factors[p1] = 1
|
||||
return factors
|
||||
|
||||
totients = {}
|
||||
def totient(n):
|
||||
if n == 0: return 1
|
||||
|
||||
try: return totients[n]
|
||||
except KeyError: pass
|
||||
|
||||
tot = 1
|
||||
for p, exp in factorization(n).items():
|
||||
tot *= (p - 1) * p ** (exp - 1)
|
||||
|
||||
totients[n] = tot
|
||||
return tot
|
||||
|
||||
def gcd(a, b):
|
||||
if a == b: return a
|
||||
while b > 0: a, b = b, a % b
|
||||
return a
|
||||
|
||||
def lcm(a, b):
|
||||
return abs(a * b) // gcd(a, b)
|
||||
|
@ -1,8 +0,0 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
|
||||
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
|
||||
an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
|
||||
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
|
||||
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
|
||||
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
@ -1,17 +0,0 @@
|
||||
#CLI like interface
|
||||
|
||||
import argparse, getopt, os, io, struct, mtproto
|
||||
from classes.shell import TelepyShell
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser('telepy',description='Python implementation of telegram API.')
|
||||
parser.add_argument('command', nargs='?', choices=['cmd', 'dialog_list', 'contact_list'] + ['chat_' + sub for sub in ['info', 'add_user', 'add_user_to_chat', 'del_user', 'set_photo', 'rename']])
|
||||
parser.add_argument('args', nargs='*')
|
||||
|
||||
#for command, args, help in (('info', 1, 'prints info about chat'), ('add_user', 2, 'add user to chat'), ('del_user', 2, 'remove user from chat'), ('set_photo', 1, 'sets group chat photo. Same limits as for profile photos.')):
|
||||
# parser.add_argument('chat_' + command, nargs=args, help=help)
|
||||
#parser.add_argument
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command is None:
|
||||
TelepyShell().cmdloop()
|
@ -1,27 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import io
|
||||
import struct
|
||||
# Deal with py2 and py3 differences
|
||||
try: # this only works in py2.7
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
import mtproto
|
||||
|
||||
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
# Check if credentials is correctly loaded (when it doesn't read anything it returns [])
|
||||
if not config.read('credentials'):
|
||||
print("File 'credentials' seems to not exist.")
|
||||
exit(-1)
|
||||
ip = config.get('App data', 'ip_address')
|
||||
port = config.getint('App data', 'port')
|
||||
|
||||
Session = mtproto.Session(ip, port)
|
||||
|
||||
Session.create_auth_key()
|
||||
|
||||
future_salts = Session.method_call('get_future_salts', num=3)
|
||||
print(future_salts)
|
@ -1,24 +0,0 @@
|
||||
--------------------------
|
||||
- BEGIN OF Serialization -
|
||||
--------------------------
|
||||
|
||||
<?php
|
||||
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'libpy2php');
|
||||
require_once ('libpy2php.php');
|
||||
|
||||
--------------------------
|
||||
- END OF Serialization -
|
||||
--------------------------
|
||||
|
||||
--------------------------
|
||||
- BEGIN OF Serialization -
|
||||
--------------------------
|
||||
|
||||
<?php
|
||||
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'libpy2php');
|
||||
require_once ('libpy2php.php');
|
||||
|
||||
--------------------------
|
||||
- END OF Serialization -
|
||||
--------------------------
|
||||
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'libpy2php');
|
||||
require_once('libpy2php.php');
|
||||
|
@ -1,28 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
MTProto data serialization and SHA hash test
|
||||
|
||||
@author: Anton Grigoryev
|
||||
@author: Sammy Pfeiffer
|
||||
"""
|
||||
from Crypto.Hash import SHA
|
||||
import io
|
||||
import mtproto
|
||||
|
||||
# byte strings got from
|
||||
# https://core.telegram.org/mtproto/samples-auth_key - step 4
|
||||
|
||||
z = io.BytesIO()
|
||||
mtproto.serialize_obj(z, 'p_q_inner_data',
|
||||
pq=b"\x17\xED\x48\x94\x1A\x08\xF9\x81",
|
||||
p=b"\x49\x4C\x55\x3B",
|
||||
q=b"\x53\x91\x10\x73",
|
||||
nonce=b"\x3E\x05\x49\x82\x8C\xCA\x27\xE9\x66\xB3\x01\xA4\x8F\xEC\xE2\xFC",
|
||||
server_nonce=b"\xA5\xCF\x4D\x33\xF4\xA1\x1E\xA8\x77\xBA\x4A\xA5\x73\x90\x73\x30",
|
||||
new_nonce=b"\x31\x1C\x85\xDB\x23\x4A\xA2\x64\x0A\xFC\x4A\x76\xA7\x35\xCF\x5B\x1F\x0F\xD6\x8B\xD1\x7F\xA1\x81\xE1\x22\x9A\xD8\x67\xCC\x02\x4D")
|
||||
x = z.getvalue()
|
||||
if SHA.new(x).digest() == b'\xDB\x76\x1C\x27\x71\x8A\x23\x05\x04\x4F\x71\xF2\xAD\x95\x16\x29\xD7\x8B\x24\x49':
|
||||
print("Test passed successfully")
|
||||
else:
|
||||
print("Test not passed")
|
||||
|
@ -1,66 +0,0 @@
|
||||
# Author: Sammy Pfeiffer
|
||||
# This file tests the AES 256 IGE cipher
|
||||
# working in Python 2.7 and Python 3.4 (other versions untested)
|
||||
# as it's needed for the implementation of Telegram API
|
||||
from crypt import ige
|
||||
|
||||
# AES 256 IGE is using AES ECB internally, it implies (extract from PyCrypto.cipher.AES):
|
||||
# key : byte string
|
||||
# The secret key to use in the symmetric cipher.
|
||||
# It must be 16 (*AES-128*), 24 (*AES-192*), or 32 (*AES-256*) bytes long.
|
||||
# IV : byte string
|
||||
# The initialization vector to use for encryption or decryption.
|
||||
#
|
||||
# It is ignored for `MODE_ECB` and `MODE_CTR`.
|
||||
#
|
||||
# For all other modes, it must be `block_size` bytes longs.
|
||||
|
||||
# message must be a multiple of 16 in size
|
||||
msg_to_encrypt = "This is a secret message"
|
||||
padding_needed = 16 - len(msg_to_encrypt) % 16
|
||||
msg_to_encrypt_padded = msg_to_encrypt + str(0) * padding_needed
|
||||
print("Encrypting: '" + str(msg_to_encrypt) + "'")
|
||||
print("With padding: '" + str(msg_to_encrypt_padded) + "'")
|
||||
# 32 bytes long key
|
||||
aes_key = b'12345678901234567890123456789012'
|
||||
print("With key for AES 256 ECB: '" + str(aes_key) + "'")
|
||||
# Initialization Vector must be 32 bytes
|
||||
aes_iv = b'01234567890123456789012345678901'
|
||||
print("And initialization vector: '" + str(aes_iv) + "'")
|
||||
encrypted_msg = _ige(msg_to_encrypt_padded, aes_key, aes_iv, operation="encrypt")
|
||||
print("\nEncrypted msg: '" + str(encrypted_msg) + "'")
|
||||
print("In hex: " + encrypted_msg.__repr__())
|
||||
decrypted_msg = _ige(encrypted_msg, aes_key, aes_iv, operation="decrypt")
|
||||
print("\nDecrypted msg: '" + str(decrypted_msg) + "'")
|
||||
print("In hex: " + decrypted_msg.__repr__())
|
||||
|
||||
if msg_to_encrypt_padded == decrypted_msg:
|
||||
print("Encrypt + Decrypt process, completed succesfully.")
|
||||
|
||||
# Let's test incorrect inputs
|
||||
print("\n\nTesting incorrect inputs:")
|
||||
# Message with length not multiple of 16
|
||||
msg_not_multiple_of_16 = "6bytes"
|
||||
print("Trying to encrypt: '" + msg_not_multiple_of_16 +
|
||||
"' of size: " + str(len(msg_not_multiple_of_16)))
|
||||
try:
|
||||
encrypted_msg = _ige(msg_not_multiple_of_16, aes_key, aes_iv, operation="encrypt")
|
||||
except ValueError as ve:
|
||||
print(" Correctly got ValueError: '" + str(ve) + "'")
|
||||
|
||||
# key not being 32 bytes
|
||||
aes_key_not_32_bytes = b'0123456789'
|
||||
print("Trying to use key: '" + str(aes_key_not_32_bytes) + "'")
|
||||
try:
|
||||
encrypted_msg = _ige(msg_to_encrypt_padded, aes_key_not_32_bytes, aes_iv, operation="encrypt")
|
||||
except ValueError as ve:
|
||||
print(" Correctly got ValueError: '" + str(ve) + "'")
|
||||
|
||||
# iv not being 32 bytes
|
||||
iv_key_not_32_bytes = b'0123456789'
|
||||
print("Trying to use iv: '" + str(iv_key_not_32_bytes) + "'")
|
||||
try:
|
||||
encrypted_msg = _ige(msg_to_encrypt_padded, aes_key, iv_key_not_32_bytes, operation="encrypt")
|
||||
except ValueError as ve:
|
||||
print(" Correctly got ValueError: '" + str(ve) + "'")
|
||||
|
@ -1,9 +0,0 @@
|
||||
from classes.file import File
|
||||
from os.path import exists
|
||||
|
||||
f = File('text.txt')
|
||||
assert f.write_bytes(b'testing bytes i/o'), 17
|
||||
assert f.read_bytes(), b'testing bytes i/o'
|
||||
f.open() # does it open any text editor on your system?
|
||||
f.remove()
|
||||
assert exists('text.txt'), False
|
@ -1,185 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Author: Sammy Pfeiffer
|
||||
# This file implements the AES 256 IGE cipher
|
||||
# working in Python 2.7 and Python 3.4 (other versions untested)
|
||||
# as it's needed for the implementation of Telegram API
|
||||
# It's based on PyCryto
|
||||
from __future__ import print_function
|
||||
from Crypto.Util import number
|
||||
from Crypto.Cipher import AES
|
||||
MIN_SUPPORTED_PY3_VERSION = (3, 2, 0)
|
||||
from sys import version_info
|
||||
if version_info >= MIN_SUPPORTED_PY3_VERSION:
|
||||
from binascii import hexlify
|
||||
long = int
|
||||
|
||||
|
||||
# Some color codes for printing
|
||||
ENDC = '\033[0m' # To end a color
|
||||
REDFAIL = '\033[91m' # RED
|
||||
GREENOK = '\033[92m' # GREEN
|
||||
|
||||
def hex_string_to_str_bytes(val):
|
||||
"""Given a String like
|
||||
tmp_aes_key_str = "F011280887C7BB01DF0FC4E17830E0B91FBB8BE4B2267CB985AE25F33B527253"
|
||||
Convert it to it's byte representation, stored in py2 in a str, like:
|
||||
tmp_aes_key_hex = '\xf0\x11(\x08\x87\xc7\xbb\x01\xdf\x0f\xc4\xe1x0\xe0\xb9\x1f\xbb\x8b\xe4\xb2&|\xb9\x85\xae%\xf3;RrS'
|
||||
"""
|
||||
return val.decode("hex")
|
||||
|
||||
def str_bytes_to_hex_string(val):
|
||||
"""Given a str_bytes (so str()) like
|
||||
tmp_aes_key_hex = '\xf0\x11(\x08\x87\xc7\xbb\x01\xdf\x0f\xc4\xe1x0\xe0\xb9\x1f\xbb\x8b\xe4\xb2&|\xb9\x85\xae%\xf3;RrS'
|
||||
Convert it back to it's uppercase string representation, like:
|
||||
tmp_aes_key_str = "F011280887C7BB01DF0FC4E17830E0B91FBB8BE4B2267CB985AE25F33B527253" """
|
||||
if version_info >= MIN_SUPPORTED_PY3_VERSION:
|
||||
return str(hexlify(val).upper())
|
||||
return val.encode("hex").upper()
|
||||
|
||||
def hex_string_to_long(val):
|
||||
"""Given a String like
|
||||
tmp_aes_key_str = "F011280887C7BB01DF0FC4E17830E0B91FBB8BE4B2267CB985AE25F33B527253"
|
||||
Convert it to int, which is actually long"""
|
||||
return int(val, 16)
|
||||
|
||||
def xor_stuff(a, b):
|
||||
"""XOR applied to every element of a with every element of b.
|
||||
Depending on python version and depeding on input some arrangements need to be done."""
|
||||
if version_info < MIN_SUPPORTED_PY3_VERSION:
|
||||
if len(a) > len(b):
|
||||
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])
|
||||
else:
|
||||
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])
|
||||
else:
|
||||
if type(a) == str and type(b) == bytes:# cipher.encrypt returns string
|
||||
return bytes(ord(x) ^ y for x, y in zip(a, b))
|
||||
elif type(a) == bytes and type(b) == str:
|
||||
return bytes(x ^ ord(y) for x, y in zip(a, b))
|
||||
else:
|
||||
return bytes(x ^ y for x, y in zip(a, b))
|
||||
|
||||
def ige(message, key, iv, operation="decrypt"):
|
||||
"""Given a key, given an iv, and message
|
||||
do whatever operation asked in the operation field.
|
||||
Operation will be checked for: "decrypt" and "encrypt" strings.
|
||||
Returns the message encrypted/decrypted.
|
||||
message must be a multiple by 16 bytes (for division in 16 byte blocks)
|
||||
key must be 32 byte
|
||||
iv must be 32 byte (it's not internally used in AES 256 ECB, but it's
|
||||
needed for IGE)"""
|
||||
if type(message) == long:
|
||||
message = number.long_to_bytes(message)
|
||||
if type(key) == long:
|
||||
key = number.long_to_bytes(key)
|
||||
if type(iv) == long:
|
||||
iv = number.long_to_bytes(iv)
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("key must be 32 bytes long (was " +
|
||||
str(len(key)) + " bytes)")
|
||||
if len(iv) != 32:
|
||||
raise ValueError("iv must be 32 bytes long (was " +
|
||||
str(len(iv)) + " bytes)")
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB, iv)
|
||||
blocksize = cipher.block_size
|
||||
if len(message) % blocksize != 0:
|
||||
raise ValueError("message must be a multiple of 16 bytes (try adding " +
|
||||
str(16 - len(message) % 16) + " bytes of padding)")
|
||||
|
||||
ivp = iv[0:blocksize]
|
||||
ivp2 = iv[blocksize:]
|
||||
|
||||
ciphered = None
|
||||
|
||||
for i in range(0, len(message), blocksize):
|
||||
indata = message[i:i+blocksize]
|
||||
if operation == "decrypt":
|
||||
xored = xor_stuff(indata, ivp2)
|
||||
decrypt_xored = cipher.decrypt(xored)
|
||||
outdata = xor_stuff(decrypt_xored, ivp)
|
||||
ivp = indata
|
||||
ivp2 = outdata
|
||||
elif operation == "encrypt":
|
||||
xored = xor_stuff(indata, ivp)
|
||||
encrypt_xored = cipher.encrypt(xored)
|
||||
outdata = xor_stuff(encrypt_xored, ivp2)
|
||||
ivp = outdata
|
||||
ivp2 = indata
|
||||
else:
|
||||
raise ValueError("operation must be either 'decrypt' or 'encrypt'")
|
||||
|
||||
if ciphered is None:
|
||||
ciphered = outdata
|
||||
else:
|
||||
ciphered_ba = bytearray(ciphered)
|
||||
ciphered_ba.extend(outdata)
|
||||
if version_info >= MIN_SUPPORTED_PY3_VERSION:
|
||||
ciphered = bytes(ciphered_ba)
|
||||
else:
|
||||
ciphered = str(ciphered_ba)
|
||||
|
||||
return ciphered
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example data from https://core.telegram.org/mtproto/samples-auth_key#conversion-of-encrypted-answer-into-answer
|
||||
encrypted_answer_str = "28A92FE20173B347A8BB324B5FAB2667C9A8BBCE6468D5B509A4CBDDC186240AC912CF7006AF8926DE606A2E74C0493CAA57741E6C82451F54D3E068F5CCC49B4444124B9666FFB405AAB564A3D01E67F6E912867C8D20D9882707DC330B17B4E0DD57CB53BFAAFA9EF5BE76AE6C1B9B6C51E2D6502A47C883095C46C81E3BE25F62427B585488BB3BF239213BF48EB8FE34C9A026CC8413934043974DB03556633038392CECB51F94824E140B98637730A4BE79A8F9DAFA39BAE81E1095849EA4C83467C92A3A17D997817C8A7AC61C3FF414DA37B7D66E949C0AEC858F048224210FCC61F11C3A910B431CCBD104CCCC8DC6D29D4A5D133BE639A4C32BBFF153E63ACA3AC52F2E4709B8AE01844B142C1EE89D075D64F69A399FEB04E656FE3675A6F8F412078F3D0B58DA15311C1A9F8E53B3CD6BB5572C294904B726D0BE337E2E21977DA26DD6E33270251C2CA29DFCC70227F0755F84CFDA9AC4B8DD5F84F1D1EB36BA45CDDC70444D8C213E4BD8F63B8AB95A2D0B4180DC91283DC063ACFB92D6A4E407CDE7C8C69689F77A007441D4A6A8384B666502D9B77FC68B5B43CC607E60A146223E110FCB43BC3C942EF981930CDC4A1D310C0B64D5E55D308D863251AB90502C3E46CC599E886A927CDA963B9EB16CE62603B68529EE98F9F5206419E03FB458EC4BD9454AA8F6BA777573CC54B328895B1DF25EAD9FB4CD5198EE022B2B81F388D281D5E5BC580107CA01A50665C32B552715F335FD76264FAD00DDD5AE45B94832AC79CE7C511D194BC42B70EFA850BB15C2012C5215CABFE97CE66B8D8734D0EE759A638AF013"
|
||||
tmp_aes_key_str = "F011280887C7BB01DF0FC4E17830E0B91FBB8BE4B2267CB985AE25F33B527253"
|
||||
tmp_aes_iv_str = "3212D579EE35452ED23E0D0C92841AA7D31B2E9BDEF2151E80D15860311C85DB"
|
||||
answer_str = "BA0D89B53E0549828CCA27E966B301A48FECE2FCA5CF4D33F4A11EA877BA4AA57390733002000000FE000100C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5BFE000100262AABA621CC4DF587DC94CF8252258C0B9337DFB47545A49CDD5C9B8EAE7236C6CADC40B24E88590F1CC2CC762EBF1CF11DCC0B393CAAD6CEE4EE5848001C73ACBB1D127E4CB93072AA3D1C8151B6FB6AA6124B7CD782EAF981BDCFCE9D7A00E423BD9D194E8AF78EF6501F415522E44522281C79D906DDB79C72E9C63D83FB2A940FF779DFB5F2FD786FB4AD71C9F08CF48758E534E9815F634F1E3A80A5E1C2AF210C5AB762755AD4B2126DFA61A77FA9DA967D65DFD0AFB5CDF26C4D4E1A88B180F4E0D0B45BA1484F95CB2712B50BF3F5968D9D55C99C0FB9FB67BFF56D7D4481B634514FBA3488C4CDA2FC0659990E8E868B28632875A9AA703BCDCE8FCB7AE551"
|
||||
|
||||
if version_info < MIN_SUPPORTED_PY3_VERSION:
|
||||
# Crypto.Cipher.AES needs it's parameters to be 32byte str
|
||||
# So we can either give 'str' type like this ONLY WORKS ON PYTHON2.7
|
||||
encrypted_answer_hex = encrypted_answer_str.decode("hex")
|
||||
tmp_aes_key_hex = tmp_aes_key_str.decode("hex")
|
||||
tmp_aes_iv_hex = tmp_aes_iv_str.decode("hex")
|
||||
answer_hex = answer_str.decode("hex")
|
||||
decrypted_answer_in_str = ige(encrypted_answer_hex, tmp_aes_key_hex, tmp_aes_iv_hex)
|
||||
print("decrypted_answer using string version of input: ")
|
||||
print(decrypted_answer_in_str)
|
||||
|
||||
# Or give it long's representing the big numbers (ige will take care of the conversion)
|
||||
encrypted_answer = int(encrypted_answer_str, 16)
|
||||
tmp_aes_key = int(tmp_aes_key_str, 16)
|
||||
tmp_aes_iv = int(tmp_aes_iv_str, 16)
|
||||
answer = int(answer_str, 16)
|
||||
decrypted_answer_in_int = ige(encrypted_answer, tmp_aes_key, tmp_aes_iv)
|
||||
print("decrypted_answer using int version of input: ")
|
||||
print(decrypted_answer_in_int)
|
||||
|
||||
if version_info < MIN_SUPPORTED_PY3_VERSION:
|
||||
if decrypted_answer_in_str == decrypted_answer_in_int:
|
||||
print("\nBoth str input and int input give the same result")
|
||||
else:
|
||||
print("\nDifferent result!!")
|
||||
|
||||
|
||||
decrypt_ans_hex_str = str_bytes_to_hex_string(decrypted_answer_in_int)
|
||||
print("Human friendly view of decrypted_answer:")
|
||||
print(decrypt_ans_hex_str)
|
||||
print("\nAnd we should expect inside of it:")
|
||||
print(answer_str)
|
||||
|
||||
if answer_str in decrypt_ans_hex_str:
|
||||
print("\n\nanswer_str is in decrypt_ans_hex_str!")
|
||||
idx = decrypt_ans_hex_str.index(answer_str)
|
||||
print(decrypt_ans_hex_str[:idx], end="")
|
||||
print(GREENOK + decrypt_ans_hex_str[idx:idx+len(answer_str)] + ENDC, end="")
|
||||
print(decrypt_ans_hex_str[idx+len(answer_str):])
|
||||
print("There are " + str(idx/2) + " bytes at the start that are not part of the answer")
|
||||
print("Plus " + str(len(decrypt_ans_hex_str[len(answer_str)+idx:]) / 2) + " at the end not forming part")
|
||||
print("answer_str is: " + str(len(answer_str) / 2) + " bytes")
|
||||
print("decrypt_ans_hex_str is: " + str(len(decrypt_ans_hex_str) / 2) + " bytes")
|
||||
print("In total: " + str( (len(decrypt_ans_hex_str) - len(answer_str)) / 2) + " bytes that do not pertain")
|
||||
else:
|
||||
print("answer_str is not in decrypt_ans_hex_str :(")
|
||||
|
||||
|
||||
print("This is because the header (SHA1(answer)) is included and is 20 bytes long.")
|
||||
print("And in the end there are 0 to 15 bytes random to fill up the gap.")
|
||||
print("This means that we can safely ignore the starting 20bytes and all the extra bytes in the end")
|
||||
# answer_with_hash := SHA1(answer) + answer + (0-15 random bytes); such that the length be divisible by 16;
|
||||
# This... divisible by 16 is because of the blocksize of AES-256-ECB (yay!)
|
||||
|
@ -1,334 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# THIS MODULE HAS ALL THE CONVERSIONS WE NEED! (I think)
|
||||
from Crypto.Util import number
|
||||
def vis(bs):
|
||||
"""
|
||||
Function to visualize byte streams. Split into bytes, print to console.
|
||||
:param bs: BYTE STRING
|
||||
"""
|
||||
bs = bytearray(bs)
|
||||
symbols_in_one_line = 8
|
||||
n = len(bs) // symbols_in_one_line
|
||||
for i in range(n):
|
||||
print(str(i*symbols_in_one_line)+" | "+" ".join(["%02X" % b for b in bs[i*symbols_in_one_line:(i+1)*symbols_in_one_line]])) # for every 8 symbols line
|
||||
if not len(bs) % symbols_in_one_line == 0:
|
||||
print(str((i+1)*symbols_in_one_line)+" | "+" ".join(["%02X" % b for b in bs[(i+1)*symbols_in_one_line:]])+"\n") # for last line
|
||||
|
||||
def hex_string_to_str_bytes(val):
|
||||
"""Given a String like
|
||||
tmp_aes_key_str = "F011280887C7BB01DF0FC4E17830E0B91FBB8BE4B2267CB985AE25F33B527253"
|
||||
Convert it to it's byte representation, stored in py2 in a str, like:
|
||||
tmp_aes_key_hex = '\xf0\x11(\x08\x87\xc7\xbb\x01\xdf\x0f\xc4\xe1x0\xe0\xb9\x1f\xbb\x8b\xe4\xb2&|\xb9\x85\xae%\xf3;RrS'
|
||||
"""
|
||||
return val.decode("hex")
|
||||
|
||||
def str_bytes_to_hex_string(val):
|
||||
"""Given a str_bytes (so str()) like
|
||||
tmp_aes_key_hex = '\xf0\x11(\x08\x87\xc7\xbb\x01\xdf\x0f\xc4\xe1x0\xe0\xb9\x1f\xbb\x8b\xe4\xb2&|\xb9\x85\xae%\xf3;RrS'
|
||||
Convert it back to it's uppercase string representation, like:
|
||||
tmp_aes_key_str = "F011280887C7BB01DF0FC4E17830E0B91FBB8BE4B2267CB985AE25F33B527253" """
|
||||
return val.encode("hex").upper()
|
||||
|
||||
def hex_string_to_long(val):
|
||||
"""Given a String like
|
||||
tmp_aes_key_str = "F011280887C7BB01DF0FC4E17830E0B91FBB8BE4B2267CB985AE25F33B527253"
|
||||
Convert it to int, which is actually long"""
|
||||
return int(val, 16)
|
||||
|
||||
def long_to_hex_string(val):
|
||||
"""Given a long like:
|
||||
|
||||
Convert it to hex_string like:
|
||||
|
||||
from: http://stackoverflow.com/questions/4358285/is-there-a-faster-way-to-convert-an-arbitrary-large-integer-to-a-big-endian-seque/4358429#4358429
|
||||
"""
|
||||
# number.long_to_bytes(val)
|
||||
# number.bytes_to_long()
|
||||
# number.tobytes()
|
||||
# number.bstr()
|
||||
return
|
||||
|
||||
|
||||
# Got from https://core.telegram.org/mtproto/samples-auth_key#conversion-of-encrypted-answer-into-answer
|
||||
# They say they use AES 256 IGE
|
||||
# Infinite Garble Extension (IGE) is a block cipher mode. (http://www.links.org/files/openssl-ige.pdf)
|
||||
encrypted_answer_str = "28A92FE20173B347A8BB324B5FAB2667C9A8BBCE6468D5B509A4CBDDC186240AC912CF7006AF8926DE606A2E74C0493CAA57741E6C82451F54D3E068F5CCC49B4444124B9666FFB405AAB564A3D01E67F6E912867C8D20D9882707DC330B17B4E0DD57CB53BFAAFA9EF5BE76AE6C1B9B6C51E2D6502A47C883095C46C81E3BE25F62427B585488BB3BF239213BF48EB8FE34C9A026CC8413934043974DB03556633038392CECB51F94824E140B98637730A4BE79A8F9DAFA39BAE81E1095849EA4C83467C92A3A17D997817C8A7AC61C3FF414DA37B7D66E949C0AEC858F048224210FCC61F11C3A910B431CCBD104CCCC8DC6D29D4A5D133BE639A4C32BBFF153E63ACA3AC52F2E4709B8AE01844B142C1EE89D075D64F69A399FEB04E656FE3675A6F8F412078F3D0B58DA15311C1A9F8E53B3CD6BB5572C294904B726D0BE337E2E21977DA26DD6E33270251C2CA29DFCC70227F0755F84CFDA9AC4B8DD5F84F1D1EB36BA45CDDC70444D8C213E4BD8F63B8AB95A2D0B4180DC91283DC063ACFB92D6A4E407CDE7C8C69689F77A007441D4A6A8384B666502D9B77FC68B5B43CC607E60A146223E110FCB43BC3C942EF981930CDC4A1D310C0B64D5E55D308D863251AB90502C3E46CC599E886A927CDA963B9EB16CE62603B68529EE98F9F5206419E03FB458EC4BD9454AA8F6BA777573CC54B328895B1DF25EAD9FB4CD5198EE022B2B81F388D281D5E5BC580107CA01A50665C32B552715F335FD76264FAD00DDD5AE45B94832AC79CE7C511D194BC42B70EFA850BB15C2012C5215CABFE97CE66B8D8734D0EE759A638AF013"
|
||||
tmp_aes_key_str = "F011280887C7BB01DF0FC4E17830E0B91FBB8BE4B2267CB985AE25F33B527253"
|
||||
tmp_aes_iv_str = "3212D579EE35452ED23E0D0C92841AA7D31B2E9BDEF2151E80D15860311C85DB"
|
||||
answer_str = "BA0D89B53E0549828CCA27E966B301A48FECE2FCA5CF4D33F4A11EA877BA4AA57390733002000000FE000100C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5BFE000100262AABA621CC4DF587DC94CF8252258C0B9337DFB47545A49CDD5C9B8EAE7236C6CADC40B24E88590F1CC2CC762EBF1CF11DCC0B393CAAD6CEE4EE5848001C73ACBB1D127E4CB93072AA3D1C8151B6FB6AA6124B7CD782EAF981BDCFCE9D7A00E423BD9D194E8AF78EF6501F415522E44522281C79D906DDB79C72E9C63D83FB2A940FF779DFB5F2FD786FB4AD71C9F08CF48758E534E9815F634F1E3A80A5E1C2AF210C5AB762755AD4B2126DFA61A77FA9DA967D65DFD0AFB5CDF26C4D4E1A88B180F4E0D0B45BA1484F95CB2712B50BF3F5968D9D55C99C0FB9FB67BFF56D7D4481B634514FBA3488C4CDA2FC0659990E8E868B28632875A9AA703BCDCE8FCB7AE551"
|
||||
print("encrypted_answer_str:")
|
||||
print(encrypted_answer_str)
|
||||
print("tmp_aes_key_str:")
|
||||
print(tmp_aes_key_str)
|
||||
print("tmp_aes_iv_str:")
|
||||
print(tmp_aes_iv_str)
|
||||
print("answer_str:")
|
||||
print(answer_str)
|
||||
|
||||
# Convert them to bytes (strings in py2 anyways)
|
||||
# http://stackoverflow.com/questions/5649407/hexadecimal-string-to-byte-array-in-python
|
||||
encrypted_answer_hex = encrypted_answer_str.decode("hex") # int(encrypted_answer_str, 16) for py3
|
||||
tmp_aes_key_hex = tmp_aes_key_str.decode("hex")
|
||||
tmp_aes_iv_hex = tmp_aes_iv_str.decode("hex")
|
||||
answer_hex = answer_str.decode("hex")
|
||||
print("encrypted_answer_hex:")
|
||||
print(encrypted_answer_hex.__repr__())
|
||||
print("tmp_aes_key_hex:")
|
||||
print(tmp_aes_key_hex.__repr__())
|
||||
print("tmp_aes_iv_hex:")
|
||||
print(tmp_aes_iv_hex.__repr__())
|
||||
print("answer_hex:")
|
||||
print(answer_hex.__repr__())
|
||||
# Re-convert them to string
|
||||
encrypted_answer_hex_to_str = encrypted_answer_hex.encode("hex").upper() # int(encrypted_answer_str, 16) for py3
|
||||
tmp_aes_key_hex_to_str = tmp_aes_key_hex.encode("hex").upper()
|
||||
tmp_aes_iv_hex_to_str = tmp_aes_iv_hex.encode("hex").upper()
|
||||
answer_hex_to_str = answer_hex.encode("hex").upper()
|
||||
print("encrypted_answer_hex_to_str:")
|
||||
print(encrypted_answer_hex_to_str)
|
||||
print("tmp_aes_key_hex_to_str:")
|
||||
print(tmp_aes_key_hex_to_str)
|
||||
print("tmp_aes_iv_hex_to_str:")
|
||||
print(tmp_aes_iv_hex_to_str)
|
||||
print("answer_hex_to_str:")
|
||||
print(answer_hex_to_str)
|
||||
|
||||
|
||||
# Check if they are the same
|
||||
if encrypted_answer_hex_to_str == encrypted_answer_str:
|
||||
print("encrypted_answer_hex_to_str == encrypted_answer_str")
|
||||
else:
|
||||
print("encrypted_answer_hex_to_str != encrypted_answer_str")
|
||||
|
||||
encrypted_answer = int(encrypted_answer_str, 16) # int(encrypted_answer_str, 16) for py3
|
||||
tmp_aes_key = int(tmp_aes_key_str, 16)
|
||||
tmp_aes_iv = int(tmp_aes_iv_str, 16)
|
||||
answer = int(answer_str, 16)
|
||||
print("longtohexstring")
|
||||
print(long_to_hex_string(tmp_aes_key))
|
||||
|
||||
# print("len(encrypted_answer): " + str(len(encrypted_answer)))
|
||||
# print("len(answer): " + str(len(answer)))
|
||||
|
||||
# Got from testing.py
|
||||
# encrypted_answer = 'L\xd7\xddI\x0b\xc3\xeay\xf1\x07]\x93\x7fY\x0cmVAX\x03\xeb\n}\x99\xd6\x99\xaa\xba\x05\x9d\xaaB\xe2\x97\xb3\xf2\xf8\xd8\x9f\xa6\x13\x177a\xb45A\x0f}\xb3\x99\xa3D?L\x94\xa3\xbcG\xe8\xf2\x14 \xb9.\x8b\xa0\xf1\xa5\xf1\x18\x9aZ2\x8f\xae\x05\xd9\x84H\xa3&\xad\x84\x82w\x9e\xe8\xba\x8a\x87QT\xdd\x12\x8c\x86\xde\xd8\x7fLM\xb9\x81H;JX\x85\x14\x1af!\xb20\xea)\xa8>(\xa9\xce5,\x96\x14\xd7P\x0c\xb3\x02\x9a\x16\xfc\x94\xacT\xa0\xd4\x82\xe5S1\xf4\xe1\x8cB\xad\x89\xc3C\xa6\rt)\xfa\x0b\xfe3\t\xdd\x02\xbe\xecP\xd6\xd7p\xf6\xf3\xb5\xdf6\xfc\x90l\xaa\x06\x8a\xc0XO\x96>\x85\x18\xebN\x08\x13x\xc0\x1ah\xbd\xedO\x99T\xfd\xed\x87C}\x89!\x99Oz\xfe\x927z~ &"\x0e/\x01N\x13\xfa\xd1\x96\x87\x0f\x83\x98d\x12X\xa7\x8c\xa8\x1c/\xbc\xab\xb2:\x07\xa6\x14\xfa\xe3\xd2\x8cG\xd6\x84\xe4[\x8f\x9e\x8f\xbb\x9c\x8a\x80\xbd\xcf\xaf!\xf7E\x1b\x1f\x91\x18\xc2\x8eBE\xfb\x84\xb6\xc5e5Q\xfd\xb8\xcb\xbc\xb4\x9f\xb7\x92\xfe\xae\xda,\xfaA\x94\x7fq\x1e\xd1\x05\xe8=\x9d#,\xe6\xb7y1\xe6\xc7!\xa0\x0bx\xd1\xb3\xad9\xc4\xdd\x99Y\xca\r\x07+\x903\x1e?\x1d_\x8b\xb0M\xff\x14\xc3:\x95\xa8\xee\xc1\xb5\xff\xfb1\x95\xe1\xcaT\xe4D\xcf\xd2%\x11\xeb@`Att\xbe\x11\xfc/\x05\x9a\xd2\x15\r\xb6\x9d\x88\xae\xa8\xd1q\xe5\x9b\x05A\x8d[\xbf\xaaN\x1b\xee\xbf#4\x1c\xd5\xa4\x1f\x0fo\xaf\xd0\x00g\xc1\x9a\x82\x00\x8c_\xd4\xac!{K\xca\x89x\xde\xf9\x8d\x19\xec\x12\x8epY\xdb\x9f\x98\xe6\x88\xe7\xc1\x92\x90\x17\x80\x03Ry\xf1n\x97e\xe2\x8c\xe9\x8c\xd6<\xba2:\x9f\x06g\x05\xaa#\xf4\xca1\x16o\xb5\x8b\xcd\xfe\x814h\xac\xcd\x0e\xd0\x1c\x0c\xc71\x11\xbe\xa5\xb3#\xcfh\x07)\x91\xc7\xc8iy!\x03\xc8\xf0\xb2\x02\xf3\xc7\xdf\xafXm\xf5\xaf\xdd\xc8\xeb\xb3n7\xe34\xa7R\x8c\xaf\xa3\xb7y\xe7\x12\x0f\x0c\xc2\xa8v\x12E\xc3u\xc8Y\x1fh.\xcf\x01\xae\x8c\x00"v\x99V\xad>\xaf\x08)\x83V*\x9b\xad\xc0\x9c\x94\xa5D[\x08s\x88\xd1\xcb\xf6\xf8j\xa1c\xc1yb\xda\x12\xa1~\xf6\xd1"\x14\x11a\x02\xc1\xd3\xf5'
|
||||
# tmp_aes_key = b'\x82\xeb\x12\x0e\xbeT\x80>!\xaa\x01\xac\xc8\xe1u#d\x1b\x08\xf5G\xc7\xe5g\xa9\xc3\x1d*BC;6'
|
||||
# tmp_aes_iv = b'r\xbb/\xe8\x0bb,T\x19\x17\xf20WsTf\x1d_C\x83|2h\xd3s\x82\xaeVW\x10v\xff'
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
# # try all modes
|
||||
# aes_modes = [AES.MODE_CBC, AES.MODE_CFB, AES.MODE_CTR, AES.MODE_ECB, AES.MODE_OFB, AES.MODE_OPENPGP, AES.MODE_PGP]
|
||||
# aes_modes_names = ["AES.MODE_CBC", "AES.MODE_CFB", "AES.MODE_CTR", "AES.MODE_ECB", "AES.MODE_OFB", "AES.MODE_OPENPGP", "AES.MODE_PGP"]
|
||||
#
|
||||
# working_modes = []
|
||||
# working_modenames = []
|
||||
# for mode, modename in zip(aes_modes, aes_modes_names):
|
||||
# print("\n\nTrying mode: " + modename + "(" + str(mode) + ")")
|
||||
# try:
|
||||
# crypting_object = AES.new(tmp_aes_key, mode, tmp_aes_iv) # encrypter thing
|
||||
# decrypted_answer = crypting_object.decrypt(encrypted_answer)
|
||||
# print("decrypted_answer: ")
|
||||
# print(decrypted_answer.__repr__())
|
||||
# vis(decrypted_answer)
|
||||
# working_modes.append(mode)
|
||||
# working_modenames.append(modename)
|
||||
# print("Which should look the same than: ")
|
||||
# print(answer.__repr__())
|
||||
# vis(answer)
|
||||
# if answer == decrypted_answer:
|
||||
# print("THEY ARE THE SAME!!")
|
||||
# else:
|
||||
# print("THEY ARE DIFFERENT :(((((")
|
||||
# except Exception as e:
|
||||
# print("Exception: " + str(e))
|
||||
#
|
||||
# print("\n\nModes " + str(working_modenames) + " (" + str(working_modes) + ") succesfully unencrypted the answer!")
|
||||
|
||||
|
||||
# From http://stackoverflow.com/questions/17797582/java-aes-256-decrypt-with-ige
|
||||
# public static final byte[] ige(final byte[] key, final byte[] IV,
|
||||
# final byte[] Message) throws Exception {
|
||||
def ige(key, iv, message, blocksize=16):#32):
|
||||
"""given a key, ive and message, decrypt it. blocksize is the default one used in the javascript implementation"""
|
||||
# print("len(key): " + str(len(key)))
|
||||
# print("len(iv): " + str(len(iv)))
|
||||
# print("len(message): " + str(len(message)))
|
||||
# key = bytearray(key)
|
||||
# iv = bytearray(iv)
|
||||
# message = bytearray(message)
|
||||
#
|
||||
# final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher = AES.new(key, AES.MODE_ECB, iv)
|
||||
blocksize = cipher.block_size
|
||||
|
||||
# cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
|
||||
#
|
||||
# final int blocksize = cipher.getBlockSize();
|
||||
#
|
||||
# byte[] xPrev = Arrays.copyOfRange(IV, 0, blocksize);
|
||||
xPrev = iv[0:blocksize]
|
||||
# byte[] yPrev = Arrays.copyOfRange(IV, blocksize, IV.length);
|
||||
yPrev = iv[blocksize:]
|
||||
#
|
||||
# byte[] decrypted = new byte[0];
|
||||
decrypted = None
|
||||
#
|
||||
# byte[] y, x;
|
||||
# y = bytearray()
|
||||
# x = bytearray()
|
||||
# for (int i = 0; i < Message.length; i += blocksize) {
|
||||
|
||||
|
||||
def xor_strings(a, b): # xor two strings of different lengths
|
||||
if len(a) > len(b):
|
||||
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])
|
||||
else:
|
||||
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])
|
||||
def add_strings(a, b):
|
||||
if len(a) > len(b):
|
||||
return "".join([chr(ord(x) + ord(y)) for (x, y) in zip(a[:len(b)], b)])
|
||||
else:
|
||||
return "".join([chr(ord(x) + ord(y)) for (x, y) in zip(a, b[:len(a)])])
|
||||
#return sum([a, b]) & 0xFFFFFFFF
|
||||
|
||||
for i in range(0, len(message), blocksize):
|
||||
#print("i: " + str(i))
|
||||
# x = java.util.Arrays.copyOfRange(Message, i, i + blocksize);
|
||||
x = message[i:i+blocksize]
|
||||
#print(" x: " + x.__repr__())
|
||||
# y = xor(cipher.doFinal(xor(x, yPrev)), xPrev);
|
||||
|
||||
y = xor_strings(cipher.decrypt(xor_strings(x, yPrev)), xPrev)
|
||||
#print(" y: " + y.__repr__())
|
||||
#y = xor(cipher.decrypt(xor(x, yPrev)), xPrev)
|
||||
# xPrev = x;
|
||||
xPrev = x
|
||||
# yPrev = y;
|
||||
yPrev = y
|
||||
#
|
||||
# decrypted = sumBytes(decrypted, y);
|
||||
if decrypted is None:
|
||||
decrypted = y
|
||||
else:
|
||||
decrypted_ba = bytearray(decrypted)
|
||||
decrypted_ba.extend(y)
|
||||
decrypted = str(decrypted_ba)
|
||||
# all this did not work
|
||||
#decrypted = int(decrypted, 16) + int(y, 16)
|
||||
#decrypted.append(y)
|
||||
#add_strings(decrypted, y)
|
||||
#decrypted = decrypted + y # this is wrong
|
||||
#print(" decrypted: " + decrypted.__repr__())
|
||||
# }
|
||||
#
|
||||
# return decrypted;
|
||||
print("len(key): " + str(len(key)))
|
||||
print("len(iv): " + str(len(iv)))
|
||||
print("len(message): " + str(len(message)))
|
||||
print("!!!!!!!!!!!!!!!!!!!!!! ---> cipher.block_size: " + str(cipher.block_size))
|
||||
return decrypted
|
||||
# }
|
||||
|
||||
#result = ige(tmp_aes_key, tmp_aes_iv, encrypted_answer)
|
||||
result = ige(tmp_aes_key_hex, tmp_aes_iv_hex, encrypted_answer_hex)
|
||||
print("result:")
|
||||
print(result)
|
||||
vis(result)
|
||||
if answer == result:
|
||||
print("THEY ARE THE SAME!!")
|
||||
else:
|
||||
print("THEY ARE DIFFERENT :(((((")
|
||||
print("len(result): " + str(len(result)))
|
||||
print("len(answer):" + str(len(answer)))
|
||||
|
||||
vis(encrypted_answer)
|
||||
|
||||
# Inspiration: http://passingcuriosity.com/2009/aes-encryption-in-python-with-m2crypto/
|
||||
# sudo apt-get install swig
|
||||
# sudo pip install M2Crypto
|
||||
# This DOES NOT work. openssl wrapper m2crypto does not have IGE available
|
||||
# from base64 import b64encode, b64decode
|
||||
# from M2Crypto.EVP import Cipher
|
||||
# ENC=1
|
||||
# DEC=0
|
||||
#
|
||||
# def build_cipher(key, iv, op=ENC):
|
||||
# """"""""
|
||||
# #return Cipher(alg='aes_256_ecb', key=key, iv=iv, op=op)
|
||||
# return Cipher(alg='aes_256 ige', key=key, iv=iv, op=op)
|
||||
#
|
||||
# def encryptor(key, iv=None):
|
||||
# """"""
|
||||
# # Decode the key and iv
|
||||
# key = b64decode(key)
|
||||
# if iv is None:
|
||||
# iv = '\0' * 16
|
||||
# else:
|
||||
# iv = b64decode(iv)
|
||||
#
|
||||
# # Return the encryption function
|
||||
# def encrypt(data):
|
||||
# cipher = build_cipher(key, iv, ENC)
|
||||
# v = cipher.update(data)
|
||||
# v = v + cipher.final()
|
||||
# del cipher
|
||||
# v = b64encode(v)
|
||||
# return v
|
||||
# return encrypt
|
||||
#
|
||||
# def decryptor(key, iv=None):
|
||||
# """"""
|
||||
# # Decode the key and iv
|
||||
# #key = b64decode(key)
|
||||
# if iv is None:
|
||||
# iv = '\0' * 16
|
||||
# else:
|
||||
# #iv = b64decode(iv)
|
||||
# pass
|
||||
#
|
||||
# # Return the decryption function
|
||||
# def decrypt(data):
|
||||
# #data = b64decode(data)
|
||||
# cipher = build_cipher(key, iv, DEC)
|
||||
# v = cipher.update(data)
|
||||
# v = v + cipher.final()
|
||||
# del cipher
|
||||
# return v
|
||||
# return decrypt
|
||||
#
|
||||
# print("Decrypting with m2...")
|
||||
# decryptor_m2= decryptor(tmp_aes_key, tmp_aes_iv)
|
||||
# m2_decrypt_ans =decryptor_m2(encrypted_answer)
|
||||
# vis(m2_decrypt_ans)
|
||||
|
||||
# Output was:
|
||||
# Trying mode: AES.MODE_CBC(2)
|
||||
# Exception: IV must be 16 bytes long
|
||||
#
|
||||
#
|
||||
# Trying mode: AES.MODE_CFB(3)
|
||||
# Exception: IV must be 16 bytes long
|
||||
#
|
||||
#
|
||||
# Trying mode: AES.MODE_CTR(6)
|
||||
# Exception: 'counter' keyword parameter is required with CTR mode
|
||||
#
|
||||
#
|
||||
# Trying mode: AES.MODE_ECB(1)
|
||||
# decrypted_answer:
|
||||
# '\xb4\x87<\xc5&\xe2J\x0e\x96\x1a\x08\xeaSR\xc4!.\x17\x0b]ZI\xd6\xdc\xbd5\x87i\x11\x1b\x9d\x04\x93;\xc5\xd7C\xe1[\xb9\xaa\x95a)\x14\xfd\x8f\xe8\xf8\xc7\xb1~\xdf\xd3\xf7\xf8\xdc\xc4v\xae \xd7\xff\x91\x0c}:\xf45T~\xad\x00w\xde\x98\xe7\xd5b\x7f\xafZg\xd0\x01\x1e\xcaF\xc7\xe4,@kS\xc8|\xe4\xedv\xa3g\x121.\x00\x10X$\x00\xd0\xea\x12\xe0\xc0n\xdd\x9c&\xb3\xd9\x15\x97\xc67\xaeH\xef\xfb\xd9\x8eC\x8b\x99\xb6P\x9f&\x9d\x95a\x00\xc3\xac\xd7@\x95\\\x127\x97\xca:\x9b\xfbA\xad\xc5K \xc7\xe1erK\x16\xd3m\xd4\x1b\xd3\xbf\x9e\xc2\x15\xd8\xd6^\xa7r9\xffC\xb0(\xb5W\xa0\n=\x8a\x0b"\x18&\x95l\xc4oF\xd9\xff\x98\xb5\xaf\xbd\xb89\x80p\xffC\xa3\xcb\xff\x8a1p\xd0t0\x13\x89\x1dG\x1e+l\xab\xd9AN$L\xd8~\xedE\xf7\x8c\x93\x0c\xc1\xfd\xdb\xc9\x9d\xe9\x0c\x1d\xbb}8\xf6j\x95>\xae\xee-\x9e?k\xaf\xaer\xe4`\xf9\t\x11\xb4\xd8\x03q*\x83\x13\x02\x0e\xe8\x9d?\xef\x0f\xd3^\xd82o4U\xc4l\xde\x17iJ\xfe\xd4\x8c`\xc6{H\x97\x16\xb7g\xb9\xddeUc\xd4\xc8\xa782IeK\x81\xc3r=!\x92\x97\x0f\x92\xd4\xf6\x01\x93\xa5\xbacL\x8b\x1a\xd5\x1d4\x9a2\x87m6\xc9\xf1\xb0&K\xdfo\xee\xeaQ\xf9\xc3\xa6\xfd?\n\xad\xfc\x9e\xaf\x8c\xfdJd\x84\xa5\x8bBl\xb1\xa0g\x8c\xb47\xf4\xc0`\x88\xe8\x88\n\x85\x81r\xf4J\xe3}\x89]\x8b\xfb|\x10\x05-)\xe2\xba\x96BT:\x16F\xaf\xd8\xa9\xcfew>\xc4QE\x91M\xffc\x07d\x1c\xf2\xb0G\xe5\x04\x03\x9bZ\xa0w\xa4\xd42\x0ex\xc3@\xdd\x9c\x15X\x0ey\x0e+\x12\x13ro\xda\xc2a\xfbH\xd0\x7f\x96\xad\xa7b&\xe7\xca+h\x1b\x13!\xf2\xf0cUw\xd7\x0f\xcd\x10>\x91\xcb\x0e\xba\xc1\xdec\xe6\x11\xdb\xba=y\x97\xe9\xc5\xfcW\x9b\x91)\xf1\x19\x12\xc4L\x83\xee"\xc2S\x9at\xd4\x01({\x01\xdc2e\xe7K\x10C\xa8J\xa3a\x1c=#\x03\x9b\xb2\x8e\xe0\x95\x9a\xf4R\x8d\xcf\xef\x88\xef\xce|\xe7\x9a5\xfe>\x13\x9d\x13\xe9 \xfc[\x02\xe2QP\xd4\x93\xe3\x15uJ3\xe6\xe1B\x12\xbdy\x81G\x9a*\x93K'
|
||||
#
|
||||
#
|
||||
# Trying mode: AES.MODE_OFB(5)
|
||||
# Exception: IV must be 16 bytes long
|
||||
#
|
||||
#
|
||||
# Trying mode: AES.MODE_OPENPGP(7)
|
||||
# Exception: Length of IV must be 16 or 18 bytes for MODE_OPENPGP
|
||||
#
|
||||
#
|
||||
# Trying mode: AES.MODE_PGP(4)
|
||||
# Exception: MODE_PGP is not supported anymore
|
||||
#
|
||||
# Modes ['AES.MODE_ECB'] ([1]) succesfully unencrypted the answer!
|
||||
|
@ -83,4 +83,8 @@ class Connection
|
||||
break;
|
||||
}
|
||||
}
|
||||
public function read_message()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -53,17 +53,17 @@ class PrimeModule
|
||||
$d = ($n - 1);
|
||||
$s = 0;
|
||||
while (($d % 2) == 0) {
|
||||
$d = floor($d / 2);
|
||||
$d = intval($d / 2);
|
||||
$s++;
|
||||
}
|
||||
$break = false;
|
||||
foreach (pyjslib_range($precision) as $repeat) {
|
||||
foreach (Tools::range($precision) as $repeat) {
|
||||
$a = rand(2, ($n - 2));
|
||||
$x = posmod(pow($a, $d), $n);
|
||||
if (($x == 1) || ($x == ($n - 1))) {
|
||||
continue;
|
||||
}
|
||||
foreach (pyjslib_range($s - 1) as $r) {
|
||||
foreach (Tools::range($s - 1) as $r) {
|
||||
$x = posmod(pow($x, 2), $n);
|
||||
if (($x == 1)) {
|
||||
return false;
|
||||
@ -221,8 +221,7 @@ class PrimeModule
|
||||
if (count($res) == 2) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
$n = (int)$n->toString();
|
||||
$factors = [];
|
||||
$limit = sqrt($n) + 1;
|
||||
foreach ($this->smallprimes as $checker) {
|
||||
@ -242,13 +241,13 @@ class PrimeModule
|
||||
return $factors;
|
||||
}
|
||||
while ($n > 1) {
|
||||
if ($n->isprime()) {
|
||||
if ($this->isprime($n)) {
|
||||
$factors[] = $n;
|
||||
break;
|
||||
}
|
||||
$factor = $this->pollard_brent($n);
|
||||
$factors[] = $this->primefactors($factor);
|
||||
$n = floor($n / $factor);
|
||||
$n = intval($n / $factor);
|
||||
}
|
||||
if ($sort) {
|
||||
$factors = sort($factors);
|
||||
@ -303,6 +302,6 @@ class PrimeModule
|
||||
|
||||
public function lcm($a, $b)
|
||||
{
|
||||
return floor(abs(($a * $b)) / $this->gcd($a, $b));
|
||||
return intval(abs(($a * $b)) / $this->gcd($a, $b));
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,10 @@ class Session extends Tools
|
||||
'protocol' => 'tcp',
|
||||
'api_id' => 25628,
|
||||
'api_hash' => '1fe17cda7d355166cdaa71f04122873c',
|
||||
'tl_schema' => 'https://core.telegram.org/schema/mtproto-json',
|
||||
'tl_schema' => [
|
||||
'https://core.telegram.org/schema/mtproto-json',
|
||||
__DIR__.'/telegram_layer55.json',
|
||||
],
|
||||
'rsa_key' => '-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
|
||||
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
|
||||
|
@ -16,7 +16,10 @@ class TL
|
||||
{
|
||||
public function __construct($filename)
|
||||
{
|
||||
$TL_dict = json_decode(file_get_contents($filename), true);
|
||||
$TL_dict = [];
|
||||
foreach ($filename as $file) {
|
||||
$TL_dict = array_replace(json_decode(file_get_contents($file), true), $TL_dict);
|
||||
}
|
||||
$this->constructors = $TL_dict['constructors'];
|
||||
$this->constructor_id = [];
|
||||
$this->constructor_type = [];
|
||||
|
1
src/danog/MadelineProto/telegram_layer55.json
Normal file
1
src/danog/MadelineProto/telegram_layer55.json
Normal file
File diff suppressed because one or more lines are too long
1
testing.php
Normal file → Executable file
1
testing.php
Normal file → Executable file
@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
Loading…
Reference in New Issue
Block a user