diff --git a/pyrogram/crypto/__init__.py b/pyrogram/crypto/__init__.py new file mode 100644 index 00000000..71f177ed --- /dev/null +++ b/pyrogram/crypto/__init__.py @@ -0,0 +1,22 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017 Dan Tès +# +# 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 . + +from .ige import IGE +from .kdf import KDF +from .prime import Prime +from .rsa import RSA diff --git a/pyrogram/crypto/ige.py b/pyrogram/crypto/ige.py new file mode 100644 index 00000000..ac061188 --- /dev/null +++ b/pyrogram/crypto/ige.py @@ -0,0 +1,61 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017 Dan Tès +# +# 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 . + +from pyaes import AES + +BLOCK_SIZE = 16 + + +# TODO: Performance optimization + +class IGE: + @classmethod + def encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: + return cls.ige(data, key, iv, True) + + @classmethod + def decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: + 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) diff --git a/pyrogram/crypto/kdf.py b/pyrogram/crypto/kdf.py new file mode 100644 index 00000000..1b941ddd --- /dev/null +++ b/pyrogram/crypto/kdf.py @@ -0,0 +1,35 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017 Dan Tès +# +# 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 . + +from hashlib import sha1 + + +class KDF: + def __new__(cls, auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple: + # https://core.telegram.org/mtproto/description#defining-aes-key-and-initialization-vector + x = 0 if outgoing else 8 + + sha1_a = sha1(msg_key + auth_key[x:x + 32]).digest() + sha1_b = sha1(auth_key[x + 32:x + 48] + msg_key + auth_key[x + 48:x + 64]).digest() + sha1_c = sha1(auth_key[x + 64:x + 96] + msg_key).digest() + sha1_d = sha1(msg_key + auth_key[x + 96:x + 128]).digest() + + aes_key = sha1_a[:8] + sha1_b[8:20] + sha1_c[4:16] + aes_iv = sha1_a[8:20] + sha1_b[:8] + sha1_c[16:20] + sha1_d[:8] + + return aes_key, aes_iv diff --git a/pyrogram/crypto/prime.py b/pyrogram/crypto/prime.py new file mode 100644 index 00000000..706ab87c --- /dev/null +++ b/pyrogram/crypto/prime.py @@ -0,0 +1,73 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017 Dan Tès +# +# 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 . + +from random import randint + + +class Prime: + # Recursive variant + # @classmethod + # def gcd(cls, a: int, b: int) -> int: + # return cls.gcd(b, a % b) if b else a + + @staticmethod + def gcd(a: int, b: int) -> int: + while b: + a, b = b, a % b + + return a + + @classmethod + def decompose(cls, pq: int) -> int: + # https://comeoncodeon.wordpress.com/2010/09/18/pollard-rho-brent-integer-factorization/ + if pq % 2 == 0: + return 2 + + y, c, m = randint(1, pq - 1), randint(1, pq - 1), randint(1, pq - 1) + g = r = q = 1 + x = ys = 0 + + while g == 1: + x = y + + for i in range(r): + y = (pow(y, 2, pq) + c) % pq + + k = 0 + + while k < r and g == 1: + ys = y + + for i in range(min(m, r - k)): + y = (pow(y, 2, pq) + c) % pq + q = q * (abs(x - y)) % pq + + g = cls.gcd(q, pq) + k += m + + r *= 2 + + if g == pq: + while True: + ys = (pow(ys, 2, pq) + c) % pq + g = cls.gcd(abs(x - ys), pq) + + if g > 1: + break + + return g diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py new file mode 100644 index 00000000..0ff1252c --- /dev/null +++ b/pyrogram/crypto/rsa.py @@ -0,0 +1,64 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017 Dan Tès +# +# 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 . + +from collections import namedtuple + +PublicKey = namedtuple("PublicKey", ["m", "e"]) + + +class RSA: + # To get modulus and exponent: + # grep -v -- - public.key | tr -d \\n | base64 -d | openssl asn1parse -inform DER -i + + # TODO Add CDNs keys + server_public_keys = { + 0xc3b42b026ce86b21 - (1 << 64): PublicKey( + # -----BEGIN RSA PUBLIC KEY----- + # MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6 + # lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS + # an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw + # Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ + # 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n + # Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB + # -----END RSA PUBLIC KEY----- + int( + "C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9" + "1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E" + "580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F" + "9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934" + "EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F" + "81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F" + "6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1" + "5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F", + 16 + ), # Modulus + int("010001", 16) # Exponent + ) + } + + @classmethod + def encrypt(cls, data: bytes, fingerprint: int) -> bytes: + return int.to_bytes( + pow( + int.from_bytes(data, "big"), + cls.server_public_keys[fingerprint].e, + cls.server_public_keys[fingerprint].m + ), + 256, + "big" + )