2020import json
2121from base64 import b64decode
2222
23+ from cryptography .hazmat .backends import default_backend
24+ from cryptography .hazmat .primitives .asymmetric .padding import OAEP , MGF1
25+ from cryptography .hazmat .primitives .ciphers import Cipher
26+ from cryptography .hazmat .primitives .ciphers .algorithms import AES
27+ from cryptography .hazmat .primitives .ciphers .modes import CBC
28+ from cryptography .hazmat .primitives .hashes import SHA512 , SHA256 , Hash , SHA1
2329from future .utils import bord
2430
25- try :
26- from cryptography .hazmat .backends import default_backend
27- from cryptography .hazmat .primitives .asymmetric .padding import OAEP , MGF1
28- from cryptography .hazmat .primitives .ciphers import Cipher
29- from cryptography .hazmat .primitives .ciphers .algorithms import AES
30- from cryptography .hazmat .primitives .ciphers .modes import CBC
31- from cryptography .hazmat .primitives .hashes import SHA512 , SHA256 , Hash , SHA1
32-
33- CRYPTO = True
34- except ImportError :
35- CRYPTO = False
36-
3731from telegram import TelegramObject
3832
3933
4034class _TelegramDecryptionError (Exception ):
35+ """
36+ Something went wrong with decryption. Never exposed to the user, gets turned into a
37+ warning inside PassportData.
38+ This is because if we raise an error during a update fetch, it might hang the Updater.
39+ """
4140 pass
4241
4342
@@ -61,6 +60,7 @@ def decrypt(secret, hash, data):
6160 :obj:`bytes`: The decrypted data as bytes
6261
6362 """
63+ # First make sure that if secret, hash, or data was base64 encoded, to decode it into bytes
6464 try :
6565 secret = b64decode (secret )
6666 except (binascii .Error , TypeError ):
@@ -73,18 +73,25 @@ def decrypt(secret, hash, data):
7373 data = b64decode (data )
7474 except (binascii .Error , TypeError ):
7575 pass
76+ # Make a SHA512 hash of secret + update
7677 digest = Hash (SHA512 (), backend = default_backend ())
7778 digest .update (secret + hash )
7879 secret_hash_hash = digest .finalize ()
80+ # First 32 chars is our key, next 16 is the initialisation vector
7981 key , iv = secret_hash_hash [:32 ], secret_hash_hash [32 :32 + 16 ]
82+ # Init a AES-CBC cipher and decrypt the data
8083 cipher = Cipher (AES (key ), CBC (iv ), backend = default_backend ())
8184 decryptor = cipher .decryptor ()
8285 data = decryptor .update (data ) + decryptor .finalize ()
86+ # Calculate SHA256 hash of the decrypted data
8387 digest = Hash (SHA256 (), backend = default_backend ())
8488 digest .update (data )
8589 data_hash = digest .finalize ()
90+ # If the newly calculated hash did not match the one telegram gave us
8691 if data_hash != hash :
92+ # Raise a error that is caught inside telegram.PassportData and transformed into a warning
8793 raise _TelegramDecryptionError ("Hashes are not equal! {} != {}" .format (data_hash , hash ))
94+ # Return data without padding
8895 return data [bord (data [0 ]):]
8996
9097
@@ -140,19 +147,28 @@ def de_json(cls, data, bot):
140147 if not data :
141148 return None
142149
143- # If already decrypted
150+ # If already decrypted just create the data object directly
144151 if isinstance (data ['data' ], dict ):
145152 data ['data' ] = Credentials .de_json (data ['data' ], bot = bot )
146153 else :
147154 try :
155+ # Try decrypting according to step 1 at
156+ # https://core.telegram.org/passport#decrypting-data
157+ # We make sure to base64 decode the secret first.
158+ # Telegram says to use OAEP padding so we do that. The Mask Generation Function
159+ # is the default for OAEP, the algorithm is the default for PHP which is what
160+ # Telegram's backend servers run.
148161 data ['secret' ] = bot .private_key .decrypt (b64decode (data .get ('secret' )), OAEP (
149162 mgf = MGF1 (algorithm = SHA1 ()),
150163 algorithm = SHA1 (),
151164 label = None
152165 ))
153166 except ValueError as e :
167+ # If decryption fails raise exception that is then caught inside PassportData
168+ # and turned into a warning
154169 raise _TelegramDecryptionError (e )
155170
171+ # Now that secret is decrypted, we can decrypt the data
156172 data ['data' ] = Credentials .de_json (decrypt_json (data .get ('secret' ),
157173 data .get ('hash' ),
158174 data .get ('data' )),
0 commit comments