2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-08-28 12:57:52 +00:00

Merge branch 'tgcrypto' into docs

# Conflicts:
#	docs/source/index.rst
This commit is contained in:
Dan 2018-02-16 18:44:38 +01:00
commit bf47939142
11 changed files with 143 additions and 114 deletions

View File

@ -63,6 +63,9 @@ Features
`MTProto Mobile Protocol v2.0`_ and the mechanisms needed for establishing
a reliable connection.
- **Fast**: Pyrogram's speed is boosted up by `TgCrypto`_, a high-performance, easy-to-install
crypto library written in C.
- **Updated**: Pyrogram makes use of the latest Telegram API version, currently `Layer 75`_.
- **Documented**: Pyrogram API public methods are documented and resemble the well
@ -105,6 +108,7 @@ To get started, press Next.
resources/ErrorHandling
resources/SOCKS5Proxy
resources/AutoAuthorization
resources/FastCrypto
.. toctree::
:hidden:
@ -121,4 +125,6 @@ To get started, press Next.
.. _`MTProto Mobile Protocol v2.0`: https://core.telegram.org/mtproto
.. _TgCrypto: https://docs.pyrogram.ml/resources/FastCrypto/
.. _`Layer 75`: https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl

View File

@ -0,0 +1,37 @@
Fast Crypto
===========
Pyrogram's speed can be *dramatically* boosted up by installing TgCrypto_, a high-performance, easy-to-install crypto
library specifically written in C for Pyrogram [#f1]_. TgCrypto is a replacement for the painfully slow PyAES and
implements the crypto algorithms MTProto requires, namely AES-IGE and AES-CTR 256 bit.
Installation
------------
.. code-block:: bash
$ pip install --upgrade tgcrypto
.. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto
is not detected on your system, Pyrogram will automatically fall back to PyAES and will show you a warning.
The reason about being an optional package is that TgCrypto requires some extra system tools in order to be compiled.
Usually the errors you receive when trying to install TgCrypto are enough to understand what you should do next.
- **Windows**: Install `Visual C++ 2015 Build Tools <http://landinghub.visualstudio.com/visual-cpp-build-tools>`_.
- **macOS**: A pop-up will automatically ask you to install the command line developer tools as soon as you issue the
installation command.
- **Linux**: Depending on your distro, install a proper C compiler (``gcc``, ``clang``) and the Python header files
(``python3-dev``).
- **Termux (Android)**: Install ``clang`` and ``python-dev`` packages.
More help on the `Pyrogram group chat <https://t.me/PyrogramChat>`_.
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
.. [#f1] Although TgCrypto is intended for Pyrogram, it is shipped as a standalone package and can thus be used for
other projects too.

View File

@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
"e" if sys.getfilesystemencoding() == "ascii" else "\xe8"
)
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__version__ = "0.5.0"
__version__ = "0.6.0"
from .api.errors import Error
from .client import ChatAction

View File

@ -47,7 +47,7 @@ from pyrogram.api.types import (
InputPeerEmpty, InputPeerSelf,
InputPeerUser, InputPeerChat, InputPeerChannel
)
from pyrogram.crypto import CTR
from pyrogram.crypto import AES
from pyrogram.session import Auth, Session
from .input_media import InputMedia
from .style import Markdown, HTML
@ -1806,8 +1806,6 @@ class Client:
)
)
if isinstance(r, types.upload.FileCdnRedirect):
ctr = CTR(r.encryption_key, r.encryption_iv)
cdn_session = Session(
r.dc_id,
self.test_mode,
@ -1846,7 +1844,7 @@ class Client:
break
# https://core.telegram.org/cdn#decrypting-files
decrypted_chunk = ctr.decrypt(chunk, offset)
decrypted_chunk = AES.ctr_decrypt(chunk, r.encryption_key, r.encryption_iv, offset)
# TODO: https://core.telegram.org/cdn#verifying-files
# TODO: Save to temp file, flush each chunk, rename to full if everything is ok

View File

@ -16,8 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .ctr import CTR
from .ige import IGE
from .aes import AES
from .kdf import KDF
from .prime import Prime
from .rsa import RSA

88
pyrogram/crypto/aes.py Normal file
View File

@ -0,0 +1,88 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import logging
log = logging.getLogger(__name__)
try:
import tgcrypto
except ImportError:
logging.warning("Warning: TgCrypto is missing")
is_fast = False
import pyaes
else:
log.info("Using TgCrypto")
is_fast = True
# TODO: Ugly IFs
class AES:
@classmethod
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
if is_fast:
return tgcrypto.ige_encrypt(data, key, iv)
else:
return cls.ige(data, key, iv, True)
@classmethod
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
if is_fast:
return tgcrypto.ige_decrypt(data, key, iv)
else:
return cls.ige(data, key, iv, False)
@staticmethod
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes:
replace = int.to_bytes(offset // 16, byteorder="big", length=4)
iv = iv[:-4] + replace
if is_fast:
return tgcrypto.ctr_decrypt(data, key, iv)
else:
ctr = pyaes.AESModeOfOperationCTR(key)
ctr._counter._counter = list(iv)
return ctr.decrypt(data)
@staticmethod
def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a),
"big",
)
@classmethod
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
cipher = pyaes.AES(key)
iv_1 = iv[:16]
iv_2 = iv[16:]
data = [data[i: i + 16] for i in range(0, len(data), 16)]
if encrypt:
for i, chunk in enumerate(data):
iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
iv_2 = chunk
else:
for i, chunk in enumerate(data):
iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
iv_1 = chunk
return b"".join(data)

View File

@ -1,35 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
try:
from pyaes import AESModeOfOperationCTR
except ImportError:
pass
class CTR:
def __init__(self, key: bytes, iv: bytes):
self.ctr = AESModeOfOperationCTR(key)
self.iv = iv
def decrypt(self, data: bytes, offset: int) -> bytes:
replace = int.to_bytes(offset // 16, byteorder="big", length=4)
iv = self.iv[:-4] + replace
self.ctr._counter._counter = list(iv)
return self.ctr.decrypt(data)

View File

@ -1,64 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
# from pyaes import AES
import tgcrypto
BLOCK_SIZE = 16
# TODO: Performance optimization
class IGE:
@classmethod
def encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige_encrypt(data, key, iv)
# return cls.ige(data, key, iv, True)
@classmethod
def decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige_decrypt(data, key, iv)
# return cls.ige(data, key, iv, False)
@staticmethod
def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a),
"big",
)
# @classmethod
# def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
# cipher = AES(key)
#
# iv_1 = iv[:BLOCK_SIZE]
# iv_2 = iv[BLOCK_SIZE:]
#
# data = [data[i: i + BLOCK_SIZE] for i in range(0, len(data), BLOCK_SIZE)]
#
# if encrypt:
# for i, chunk in enumerate(data):
# iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
# iv_2 = chunk
# else:
# for i, chunk in enumerate(data):
# iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
# iv_1 = chunk
#
# return b"".join(data)

View File

@ -25,7 +25,7 @@ from os import urandom
from pyrogram.api import functions, types
from pyrogram.api.core import Object, Long, Int
from pyrogram.connection import Connection
from pyrogram.crypto import IGE, RSA, Prime
from pyrogram.crypto import AES, RSA, Prime
from .internals import MsgId, DataCenter
log = logging.getLogger(__name__)
@ -152,7 +152,7 @@ class Auth:
server_nonce = int.from_bytes(server_nonce, "little", signed=True)
answer_with_hash = IGE.decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
answer_with_hash = AES.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
answer = answer_with_hash[20:]
server_dh_inner_data = Object.read(BytesIO(answer))
@ -181,7 +181,7 @@ class Auth:
sha = sha1(data).digest()
padding = urandom(- (len(data) + len(sha)) % 16)
data_with_hash = sha + data + padding
encrypted_data = IGE.encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
encrypted_data = AES.ige_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
log.debug("Send set_client_DH_params")
set_client_dh_params_answer = self.send(
@ -236,7 +236,7 @@ class Auth:
log.debug("Nonce fields check: OK")
# Step 9
server_salt = IGE.xor(new_nonce[:8], server_nonce[:8])
server_salt = AES.xor(new_nonce[:8], server_nonce[:8])
log.debug("Server salt: {}".format(int.from_bytes(server_salt, "little")))

View File

@ -33,7 +33,7 @@ from pyrogram.api.all import layer
from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int
from pyrogram.api.errors import Error
from pyrogram.connection import Connection
from pyrogram.crypto import IGE, KDF
from pyrogram.crypto import AES, KDF
from .internals import MsgId, MsgFactory, DataCenter
log = logging.getLogger(__name__)
@ -192,14 +192,14 @@ class Session:
msg_key = msg_key_large[8:24]
aes_key, aes_iv = KDF(self.auth_key, msg_key, True)
return self.auth_key_id + msg_key + IGE.encrypt(data + padding, aes_key, aes_iv)
return self.auth_key_id + msg_key + AES.ige_encrypt(data + padding, aes_key, aes_iv)
def unpack(self, b: BytesIO) -> Message:
assert b.read(8) == self.auth_key_id, b.getvalue()
msg_key = b.read(16)
aes_key, aes_iv = KDF(self.auth_key, msg_key, False)
data = BytesIO(IGE.decrypt(b.read(), aes_key, aes_iv))
data = BytesIO(AES.ige_decrypt(b.read(), aes_key, aes_iv))
data.read(8)
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id

View File

@ -66,6 +66,6 @@ setup(
],
packages=find_packages(),
zip_safe=False,
install_requires=["pyaes", "pysocks", "tgcrypto"],
install_requires=["pyaes", "pysocks"],
include_package_data=True,
)