diff --git a/telethon/crypto/aes.py b/telethon/crypto/aes.py index 8f13b5f0..3cfcc1af 100644 --- a/telethon/crypto/aes.py +++ b/telethon/crypto/aes.py @@ -1,13 +1,29 @@ """ -AES IGE implementation in Python. This module may use libssl if available. +AES IGE implementation in Python. + +If available, cryptg will be used instead, otherwise +if available, libssl will be used instead, otherwise +the Python implementation will be used. """ import os import pyaes +import logging +from . import libssl + + +__log__ = logging.getLogger(__name__) + try: import cryptg + __log__.info('cryptg detected, it will be used for encryption') except ImportError: cryptg = None + if libssl.encrypt_ige and libssl.decrypt_ige: + __log__.info('libssl detected, it will be used for encryption') + else: + __log__.info('cryptg module not installed and libssl not found, ' + 'falling back to (slower) Python encryption') class AES: @@ -23,6 +39,8 @@ class AES: """ if cryptg: return cryptg.decrypt_ige(cipher_text, key, iv) + if libssl.decrypt_ige: + return libssl.decrypt_ige(cipher_text, key, iv) iv1 = iv[:len(iv) // 2] iv2 = iv[len(iv) // 2:] @@ -56,13 +74,14 @@ class AES: Encrypts the given text in 16-bytes blocks by using the given key and 32-bytes initialization vector. """ - # Add random padding iff it's not evenly divisible by 16 already - if len(plain_text) % 16 != 0: - padding_count = 16 - len(plain_text) % 16 - plain_text += os.urandom(padding_count) + padding = len(plain_text) % 16 + if padding: + plain_text += os.urandom(16 - padding) if cryptg: return cryptg.encrypt_ige(plain_text, key, iv) + if libssl.encrypt_ige: + return libssl.encrypt_ige(plain_text, key, iv) iv1 = iv[:len(iv) // 2] iv2 = iv[len(iv) // 2:] diff --git a/telethon/crypto/libssl.py b/telethon/crypto/libssl.py new file mode 100644 index 00000000..33e04eb3 --- /dev/null +++ b/telethon/crypto/libssl.py @@ -0,0 +1,69 @@ +""" +Helper module around the system's libssl library if available for IGE mode. +""" +import ctypes +import ctypes.util + + +lib = ctypes.util.find_library('ssl') +if not lib: + decrypt_ige = None + encrypt_ige = None +else: + _libssl = ctypes.cdll.LoadLibrary(lib) + + # https://github.com/openssl/openssl/blob/master/include/openssl/aes.h + AES_ENCRYPT = ctypes.c_int(1) + AES_DECRYPT = ctypes.c_int(0) + AES_MAXNR = 14 + + class AES_KEY(ctypes.Structure): + """Helper class representing an AES key""" + _fields_ = [ + ('rd_key', ctypes.c_uint32 * (4 * (AES_MAXNR + 1))), + ('rounds', ctypes.c_uint), + ] + + def decrypt_ige(cipher_text, key, iv): + aes_key = AES_KEY() + key_len = ctypes.c_int(8 * len(key)) + key = (ctypes.c_ubyte * len(key))(*key) + iv = (ctypes.c_ubyte * len(iv))(*iv) + + in_len = ctypes.c_size_t(len(cipher_text)) + in_ptr = (ctypes.c_ubyte * len(cipher_text))(*cipher_text) + out_ptr = (ctypes.c_ubyte * len(cipher_text))() + + _libssl.AES_set_decrypt_key(key, key_len, ctypes.byref(aes_key)) + _libssl.AES_ige_encrypt( + ctypes.byref(in_ptr), + ctypes.byref(out_ptr), + in_len, + ctypes.byref(aes_key), + ctypes.byref(iv), + AES_DECRYPT + ) + + return bytes(out_ptr) + + def encrypt_ige(plain_text, key, iv): + aes_key = AES_KEY() + key_len = ctypes.c_int(8 * len(key)) + key = (ctypes.c_ubyte * len(key))(*key) + iv = (ctypes.c_ubyte * len(iv))(*iv) + + in_len = ctypes.c_size_t(len(plain_text)) + in_ptr = (ctypes.c_ubyte * len(plain_text))(*plain_text) + out_ptr = (ctypes.c_ubyte * len(plain_text))() + + _libssl.AES_set_encrypt_key(key, key_len, ctypes.byref(aes_key)) + _libssl.AES_ige_encrypt( + ctypes.byref(in_ptr), + ctypes.byref(out_ptr), + in_len, + ctypes.byref(aes_key), + ctypes.byref(iv), + AES_ENCRYPT + ) + + return bytes(out_ptr)