Skip to content

Commit 2b74250

Browse files
committed
Merge IGE and CTR into a single class (AES)
1 parent 0dd5843 commit 2b74250

File tree

7 files changed

+98
-112
lines changed

7 files changed

+98
-112
lines changed

pyrogram/client/client.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
InputPeerEmpty, InputPeerSelf,
4747
InputPeerUser, InputPeerChat, InputPeerChannel
4848
)
49-
from pyrogram.crypto import CTR
49+
from pyrogram.crypto import AES
5050
from pyrogram.session import Auth, Session
5151
from .style import Markdown, HTML
5252

@@ -1633,8 +1633,6 @@ def get_file(self,
16331633
)
16341634
)
16351635
if isinstance(r, types.upload.FileCdnRedirect):
1636-
ctr = CTR(r.encryption_key, r.encryption_iv)
1637-
16381636
cdn_session = Session(
16391637
r.dc_id,
16401638
self.test_mode,
@@ -1673,7 +1671,7 @@ def get_file(self,
16731671
break
16741672

16751673
# https://core.telegram.org/cdn#decrypting-files
1676-
decrypted_chunk = ctr.decrypt(chunk, offset)
1674+
decrypted_chunk = AES.ctr_decrypt(chunk, r.encryption_key, r.encryption_iv, offset)
16771675

16781676
# TODO: https://core.telegram.org/cdn#verifying-files
16791677
# TODO: Save to temp file, flush each chunk, rename to full if everything is ok

pyrogram/crypto/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
# You should have received a copy of the GNU Lesser General Public License
1717
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
1818

19-
from .ctr import CTR
20-
from .ige import IGE
19+
from .aes import AES
2120
from .kdf import KDF
2221
from .prime import Prime
2322
from .rsa import RSA

pyrogram/crypto/aes.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Pyrogram - Telegram MTProto API Client Library for Python
2+
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
3+
#
4+
# This file is part of Pyrogram.
5+
#
6+
# Pyrogram is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published
8+
# by the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# Pyrogram is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
18+
19+
import logging
20+
21+
log = logging.getLogger(__name__)
22+
23+
try:
24+
import tgcrypto
25+
except ImportError:
26+
logging.warning("Warning: TgCrypto is missing")
27+
is_fast = False
28+
import pyaes
29+
else:
30+
log.info("Using TgCrypto")
31+
is_fast = True
32+
33+
34+
# TODO: Ugly IFs
35+
class AES:
36+
@classmethod
37+
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
38+
if is_fast:
39+
return tgcrypto.ige_encrypt(data, key, iv)
40+
else:
41+
return cls.ige(data, key, iv, True)
42+
43+
@classmethod
44+
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
45+
if is_fast:
46+
return tgcrypto.ige_decrypt(data, key, iv)
47+
else:
48+
return cls.ige(data, key, iv, False)
49+
50+
@staticmethod
51+
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes:
52+
replace = int.to_bytes(offset // 16, byteorder="big", length=4)
53+
iv = iv[:-4] + replace
54+
55+
if is_fast:
56+
return tgcrypto.ctr_decrypt(data, key, iv)
57+
else:
58+
ctr = pyaes.AESModeOfOperationCTR(key)
59+
ctr._counter._counter = list(iv)
60+
return ctr.decrypt(data)
61+
62+
@staticmethod
63+
def xor(a: bytes, b: bytes) -> bytes:
64+
return int.to_bytes(
65+
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
66+
len(a),
67+
"big",
68+
)
69+
70+
@classmethod
71+
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
72+
cipher = pyaes.AES(key)
73+
74+
iv_1 = iv[:16]
75+
iv_2 = iv[16:]
76+
77+
data = [data[i: i + 16] for i in range(0, len(data), 16)]
78+
79+
if encrypt:
80+
for i, chunk in enumerate(data):
81+
iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
82+
iv_2 = chunk
83+
else:
84+
for i, chunk in enumerate(data):
85+
iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
86+
iv_1 = chunk
87+
88+
return b"".join(data)

pyrogram/crypto/ctr.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

pyrogram/crypto/ige.py

Lines changed: 0 additions & 64 deletions
This file was deleted.

pyrogram/session/auth.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from pyrogram.api import functions, types
2626
from pyrogram.api.core import Object, Long, Int
2727
from pyrogram.connection import Connection
28-
from pyrogram.crypto import IGE, RSA, Prime
28+
from pyrogram.crypto import AES, RSA, Prime
2929
from .internals import MsgId, DataCenter
3030

3131
log = logging.getLogger(__name__)
@@ -152,7 +152,7 @@ def create(self):
152152

153153
server_nonce = int.from_bytes(server_nonce, "little", signed=True)
154154

155-
answer_with_hash = IGE.decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
155+
answer_with_hash = AES.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
156156
answer = answer_with_hash[20:]
157157

158158
server_dh_inner_data = Object.read(BytesIO(answer))
@@ -181,7 +181,7 @@ def create(self):
181181
sha = sha1(data).digest()
182182
padding = urandom(- (len(data) + len(sha)) % 16)
183183
data_with_hash = sha + data + padding
184-
encrypted_data = IGE.encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
184+
encrypted_data = AES.ige_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
185185

186186
log.debug("Send set_client_DH_params")
187187
set_client_dh_params_answer = self.send(
@@ -236,7 +236,7 @@ def create(self):
236236
log.debug("Nonce fields check: OK")
237237

238238
# Step 9
239-
server_salt = IGE.xor(new_nonce[:8], server_nonce[:8])
239+
server_salt = AES.xor(new_nonce[:8], server_nonce[:8])
240240

241241
log.debug("Server salt: {}".format(int.from_bytes(server_salt, "little")))
242242

pyrogram/session/session.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int
3333
from pyrogram.api.errors import Error
3434
from pyrogram.connection import Connection
35-
from pyrogram.crypto import IGE, KDF
35+
from pyrogram.crypto import AES, KDF
3636
from .internals import MsgId, MsgFactory, DataCenter
3737

3838
log = logging.getLogger(__name__)
@@ -204,14 +204,14 @@ def pack(self, message: Message):
204204
msg_key = msg_key_large[8:24]
205205
aes_key, aes_iv = KDF(self.auth_key, msg_key, True)
206206

207-
return self.auth_key_id + msg_key + IGE.encrypt(data + padding, aes_key, aes_iv)
207+
return self.auth_key_id + msg_key + AES.ige_encrypt(data + padding, aes_key, aes_iv)
208208

209209
def unpack(self, b: BytesIO) -> Message:
210210
assert b.read(8) == self.auth_key_id, b.getvalue()
211211

212212
msg_key = b.read(16)
213213
aes_key, aes_iv = KDF(self.auth_key, msg_key, False)
214-
data = BytesIO(IGE.decrypt(b.read(), aes_key, aes_iv))
214+
data = BytesIO(AES.ige_decrypt(b.read(), aes_key, aes_iv))
215215
data.read(8)
216216

217217
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id

0 commit comments

Comments
 (0)