commit a442e720cfe576f64d98750b61f9d7c0766af00c Author: Sazonov Andrey Date: Fri Mar 20 16:19:19 2026 +0300 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b694934 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.venv \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c3235a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.12-slim + +RUN apt-get update && apt-get install --no-install-recommends -y \ + libcap2-bin \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache-dir uvloop pycryptodome +RUN setcap cap_net_bind_service=+ep /usr/local/bin/python3.12 +RUN useradd -u 10000 tgproxy + +USER tgproxy +WORKDIR /home/tgproxy/ + +COPY --chown=tgproxy mtprotoproxy.py config.py . + +CMD ["python3", "mtprotoproxy.py"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e66411a --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +The MIT License + +Copyright (c) 2018, Alexander Bersenev + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cde3dc --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Async MTProto Proxy # + +Fast and simple to setup MTProto proxy written in Python. + +## Starting Up ## + +1. `git clone -b stable https://github.com/alexbers/mtprotoproxy.git; cd mtprotoproxy` +2. *(optional, recommended)* edit *config.py*, set **PORT**, **USERS** and **AD_TAG** +3. `docker-compose up -d` (or just `python3 mtprotoproxy.py` if you don't like Docker) +4. *(optional, get a link to share the proxy)* `docker-compose logs` + +![Demo](https://alexbers.com/mtprotoproxy/install_demo_v2.gif) + +## Channel Advertising ## + +To advertise a channel get a tag from **@MTProxybot** and put it to *config.py*. + +## Performance ## + +The proxy performance should be enough to comfortably serve about 4 000 simultaneous users on +the VDS instance with 1 CPU core and 1024MB RAM. + +## More Instructions ## + +- [Running without Docker](https://github.com/alexbers/mtprotoproxy/wiki/Running-Without-Docker) +- [Optimization and fine tuning](https://github.com/alexbers/mtprotoproxy/wiki/Optimization-and-Fine-Tuning) + +## Advanced Usage ## + +The proxy can be launched: +- with a custom config: `python3 mtprotoproxy.py [configfile]` +- several times, clients will be automaticaly balanced between instances +- with uvloop module to get an extra speed boost +- with runtime statistics exported to [Prometheus](https://prometheus.io/) diff --git a/config.py b/config.py new file mode 100644 index 0000000..54b614e --- /dev/null +++ b/config.py @@ -0,0 +1,27 @@ +PORT = 443 + +# name -> secret (32 hex chars) +USERS = { + "tg": "00000000000000000000000000000001", + # "tg2": "0123456789abcdef0123456789abcdef", +} + +MODES = { + # Classic mode, easy to detect + "classic": False, + + # Makes the proxy harder to detect + # Can be incompatible with very old clients + "secure": False, + + # Makes the proxy even more hard to detect + # Can be incompatible with old clients + "tls": True +} + +# The domain for TLS mode, bad clients are proxied there +# Use random existing domain, proxy checks it on start +# TLS_DOMAIN = "www.google.com" + +# Tag for advertising, obtainable from @MTProxybot +# AD_TAG = "3c09c680b76ee91a4c25ad51f742267d" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b723d09 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + mtprotoproxy: + build: . + restart: unless-stopped + network_mode: "host" + volumes: + - ./config.py:/home/tgproxy/config.py + - ./mtprotoproxy.py:/home/tgproxy/mtprotoproxy.py + - /etc/localtime:/etc/localtime:ro + logging: + driver: "json-file" + options: + max-file: "10" + max-size: "10m" \ No newline at end of file diff --git a/mtprotoproxy.py b/mtprotoproxy.py new file mode 100755 index 0000000..10de4aa --- /dev/null +++ b/mtprotoproxy.py @@ -0,0 +1,2406 @@ +#!/usr/bin/env python3 + +import asyncio +import socket +import urllib.parse +import urllib.request +import collections +import time +import datetime +import hmac +import base64 +import hashlib +import random +import binascii +import sys +import re +import runpy +import signal +import os +import stat +import traceback + + +TG_DATACENTER_PORT = 443 + +TG_DATACENTERS_V4 = [ + "149.154.175.50", "149.154.167.51", "149.154.175.100", + "149.154.167.91", "149.154.171.5" +] + +TG_DATACENTERS_V6 = [ + "2001:b28:f23d:f001::a", "2001:67c:04e8:f002::a", "2001:b28:f23d:f003::a", + "2001:67c:04e8:f004::a", "2001:b28:f23f:f005::a" +] + +# This list will be updated in the runtime +TG_MIDDLE_PROXIES_V4 = { + 1: [("149.154.175.50", 8888)], -1: [("149.154.175.50", 8888)], + 2: [("149.154.161.144", 8888)], -2: [("149.154.161.144", 8888)], + 3: [("149.154.175.100", 8888)], -3: [("149.154.175.100", 8888)], + 4: [("91.108.4.136", 8888)], -4: [("149.154.165.109", 8888)], + 5: [("91.108.56.183", 8888)], -5: [("91.108.56.183", 8888)] +} + +TG_MIDDLE_PROXIES_V6 = { + 1: [("2001:b28:f23d:f001::d", 8888)], -1: [("2001:b28:f23d:f001::d", 8888)], + 2: [("2001:67c:04e8:f002::d", 80)], -2: [("2001:67c:04e8:f002::d", 80)], + 3: [("2001:b28:f23d:f003::d", 8888)], -3: [("2001:b28:f23d:f003::d", 8888)], + 4: [("2001:67c:04e8:f004::d", 8888)], -4: [("2001:67c:04e8:f004::d", 8888)], + 5: [("2001:b28:f23f:f005::d", 8888)], -5: [("2001:b28:f23f:f005::d", 8888)] +} + +PROXY_SECRET = bytes.fromhex( + "c4f9faca9678e6bb48ad6c7e2ce5c0d24430645d554addeb55419e034da62721" + + "d046eaab6e52ab14a95a443ecfb3463e79a05a66612adf9caeda8be9a80da698" + + "6fb0a6ff387af84d88ef3a6413713e5c3377f6e1a3d47d99f5e0c56eece8f05c" + + "54c490b079e31bef82ff0ee8f2b0a32756d249c5f21269816cb7061b265db212" +) + +SKIP_LEN = 8 +PREKEY_LEN = 32 +KEY_LEN = 32 +IV_LEN = 16 +HANDSHAKE_LEN = 64 +PROTO_TAG_POS = 56 +DC_IDX_POS = 60 + +MIN_CERT_LEN = 1024 + +PROTO_TAG_ABRIDGED = b"\xef\xef\xef\xef" +PROTO_TAG_INTERMEDIATE = b"\xee\xee\xee\xee" +PROTO_TAG_SECURE = b"\xdd\xdd\xdd\xdd" + +CBC_PADDING = 16 +PADDING_FILLER = b"\x04\x00\x00\x00" + +MIN_MSG_LEN = 12 +MAX_MSG_LEN = 2 ** 24 + +STAT_DURATION_BUCKETS = [0.1, 0.5, 1, 2, 5, 15, 60, 300, 600, 1800, 2**31 - 1] + +my_ip_info = {"ipv4": None, "ipv6": None} +used_handshakes = collections.OrderedDict() +client_ips = collections.OrderedDict() +last_client_ips = {} +disable_middle_proxy = False +is_time_skewed = False +fake_cert_len = random.randrange(1024, 4096) +mask_host_cached_ip = None +last_clients_with_time_skew = {} +last_clients_with_same_handshake = collections.Counter() +proxy_start_time = 0 +proxy_links = [] + +stats = collections.Counter() +user_stats = collections.defaultdict(collections.Counter) + +config = {} + + +def init_config(): + global config + # we use conf_dict to protect the original config from exceptions when reloading + if len(sys.argv) < 2: + conf_dict = runpy.run_module("config") + elif len(sys.argv) == 2: + # launch with own config + conf_dict = runpy.run_path(sys.argv[1]) + else: + # undocumented way of launching + conf_dict = {} + conf_dict["PORT"] = int(sys.argv[1]) + secrets = sys.argv[2].split(",") + conf_dict["USERS"] = {"user%d" % i: secrets[i].zfill(32) for i in range(len(secrets))} + conf_dict["MODES"] = {"classic": False, "secure": True, "tls": True} + if len(sys.argv) > 3: + conf_dict["AD_TAG"] = sys.argv[3] + if len(sys.argv) > 4: + conf_dict["TLS_DOMAIN"] = sys.argv[4] + conf_dict["MODES"] = {"classic": False, "secure": False, "tls": True} + + conf_dict = {k: v for k, v in conf_dict.items() if k.isupper()} + + conf_dict.setdefault("PORT", 3256) + conf_dict.setdefault("USERS", {"tg": "00000000000000000000000000000000"}) + conf_dict["AD_TAG"] = bytes.fromhex(conf_dict.get("AD_TAG", "")) + + for user, secret in conf_dict["USERS"].items(): + if not re.fullmatch("[0-9a-fA-F]{32}", secret): + fixed_secret = re.sub(r"[^0-9a-fA-F]", "", secret).zfill(32)[:32] + + print_err("Bad secret for user %s, should be 32 hex chars, got %s. " % (user, secret)) + print_err("Changing it to %s" % fixed_secret) + + conf_dict["USERS"][user] = fixed_secret + + # load advanced settings + + # use middle proxy, necessary to show ad + conf_dict.setdefault("USE_MIDDLE_PROXY", len(conf_dict["AD_TAG"]) == 16) + + # if IPv6 available, use it by default, IPv6 with middle proxies is unstable now + conf_dict.setdefault("PREFER_IPV6", socket.has_ipv6 and not conf_dict["USE_MIDDLE_PROXY"]) + + # disables tg->client traffic reencryption, faster but less secure + conf_dict.setdefault("FAST_MODE", True) + + # enables some working modes + modes = conf_dict.get("MODES", {}) + + if "MODES" not in conf_dict: + modes.setdefault("classic", True) + modes.setdefault("secure", True) + modes.setdefault("tls", True) + else: + modes.setdefault("classic", False) + modes.setdefault("secure", False) + modes.setdefault("tls", False) + + legacy_warning = False + if "SECURE_ONLY" in conf_dict: + legacy_warning = True + modes["classic"] = not bool(conf_dict["SECURE_ONLY"]) + + if "TLS_ONLY" in conf_dict: + legacy_warning = True + if conf_dict["TLS_ONLY"]: + modes["classic"] = False + modes["secure"] = False + + if not modes["classic"] and not modes["secure"] and not modes["tls"]: + print_err("No known modes enabled, enabling tls-only mode") + modes["tls"] = True + + if legacy_warning: + print_err("Legacy options SECURE_ONLY or TLS_ONLY detected") + print_err("Please use MODES in your config instead:") + print_err("MODES = {") + print_err(' "classic": %s,' % modes["classic"]) + print_err(' "secure": %s,' % modes["secure"]) + print_err(' "tls": %s' % modes["tls"]) + print_err("}") + + conf_dict["MODES"] = modes + + # accept incoming connections only with proxy protocol v1/v2, useful for nginx and haproxy + conf_dict.setdefault("PROXY_PROTOCOL", False) + + # set the tls domain for the proxy, has an influence only on starting message + conf_dict.setdefault("TLS_DOMAIN", "www.google.com") + + # enable proxying bad clients to some host + conf_dict.setdefault("MASK", True) + + # the next host to forward bad clients + conf_dict.setdefault("MASK_HOST", conf_dict["TLS_DOMAIN"]) + + # set the home domain for the proxy, has an influence only on the log message + conf_dict.setdefault("MY_DOMAIN", False) + + # the next host's port to forward bad clients + conf_dict.setdefault("MASK_PORT", 443) + + # use upstream SOCKS5 proxy + conf_dict.setdefault("SOCKS5_HOST", None) + conf_dict.setdefault("SOCKS5_PORT", None) + conf_dict.setdefault("SOCKS5_USER", None) + conf_dict.setdefault("SOCKS5_PASS", None) + + if conf_dict["SOCKS5_HOST"] and conf_dict["SOCKS5_PORT"]: + # Disable the middle proxy if using socks, they are not compatible + conf_dict["USE_MIDDLE_PROXY"] = False + + # user tcp connection limits, the mapping from name to the integer limit + # one client can create many tcp connections, up to 8 + conf_dict.setdefault("USER_MAX_TCP_CONNS", {}) + + # expiration date for users in format of day/month/year + conf_dict.setdefault("USER_EXPIRATIONS", {}) + for user in conf_dict["USER_EXPIRATIONS"]: + expiration = datetime.datetime.strptime(conf_dict["USER_EXPIRATIONS"][user], "%d/%m/%Y") + conf_dict["USER_EXPIRATIONS"][user] = expiration + + # the data quota for user + conf_dict.setdefault("USER_DATA_QUOTA", {}) + + # length of used handshake randoms for active fingerprinting protection, zero to disable + conf_dict.setdefault("REPLAY_CHECK_LEN", 65536) + + # accept clients with bad clocks. This reduces the protection against replay attacks + conf_dict.setdefault("IGNORE_TIME_SKEW", False) + + # length of last client ip addresses for logging + conf_dict.setdefault("CLIENT_IPS_LEN", 131072) + + # delay in seconds between stats printing + conf_dict.setdefault("STATS_PRINT_PERIOD", 600) + + # delay in seconds between middle proxy info updates + conf_dict.setdefault("PROXY_INFO_UPDATE_PERIOD", 24*60*60) + + # delay in seconds between time getting, zero means disabled + conf_dict.setdefault("GET_TIME_PERIOD", 10*60) + + # delay in seconds between getting the length of certificate on the mask host + conf_dict.setdefault("GET_CERT_LEN_PERIOD", random.randrange(4*60*60, 6*60*60)) + + # max socket buffer size to the client direction, the more the faster, but more RAM hungry + # can be the tuple (low, users_margin, high) for the adaptive case. If no much users, use high + conf_dict.setdefault("TO_CLT_BUFSIZE", (16384, 100, 131072)) + + # max socket buffer size to the telegram servers direction, also can be the tuple + conf_dict.setdefault("TO_TG_BUFSIZE", 65536) + + # keepalive period for clients in secs + conf_dict.setdefault("CLIENT_KEEPALIVE", 10*60) + + # drop client after this timeout if the handshake fail + conf_dict.setdefault("CLIENT_HANDSHAKE_TIMEOUT", random.randrange(5, 15)) + + # if client doesn't confirm data for this number of seconds, it is dropped + conf_dict.setdefault("CLIENT_ACK_TIMEOUT", 5*60) + + # telegram servers connect timeout in seconds + conf_dict.setdefault("TG_CONNECT_TIMEOUT", 10) + + # drop connection if no data from telegram server for this many seconds + conf_dict.setdefault("TG_READ_TIMEOUT", 60) + + # listen address for IPv4 + conf_dict.setdefault("LISTEN_ADDR_IPV4", "0.0.0.0") + + # listen address for IPv6 + conf_dict.setdefault("LISTEN_ADDR_IPV6", "::") + + # listen unix socket + conf_dict.setdefault("LISTEN_UNIX_SOCK", "") + + # prometheus exporter listen port, use some random port here + conf_dict.setdefault("METRICS_PORT", None) + + # prometheus listen addr ipv4 + conf_dict.setdefault("METRICS_LISTEN_ADDR_IPV4", "0.0.0.0") + + # prometheus listen addr ipv6 + conf_dict.setdefault("METRICS_LISTEN_ADDR_IPV6", None) + + # prometheus scrapers whitelist + conf_dict.setdefault("METRICS_WHITELIST", ["127.0.0.1", "::1"]) + + # export proxy link to prometheus + conf_dict.setdefault("METRICS_EXPORT_LINKS", False) + + # default prefix for metrics + conf_dict.setdefault("METRICS_PREFIX", "mtprotoproxy_") + + # allow access to config by attributes + config = type("config", (dict,), conf_dict)(conf_dict) + + +def apply_upstream_proxy_settings(): + # apply socks settings in place + if config.SOCKS5_HOST and config.SOCKS5_PORT: + import socks + print_err("Socket-proxy mode activated, it is incompatible with advertising and uvloop") + socks.set_default_proxy(socks.PROXY_TYPE_SOCKS5, config.SOCKS5_HOST, config.SOCKS5_PORT, + username=config.SOCKS5_USER, password=config.SOCKS5_PASS) + if not hasattr(socket, "origsocket"): + socket.origsocket = socket.socket + socket.socket = socks.socksocket + elif hasattr(socket, "origsocket"): + socket.socket = socket.origsocket + del socket.origsocket + + +def try_use_cryptography_module(): + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + + class CryptographyEncryptorAdapter: + __slots__ = ('encryptor', 'decryptor') + + def __init__(self, cipher): + self.encryptor = cipher.encryptor() + self.decryptor = cipher.decryptor() + + def encrypt(self, data): + return self.encryptor.update(data) + + def decrypt(self, data): + return self.decryptor.update(data) + + def create_aes_ctr(key, iv): + iv_bytes = int.to_bytes(iv, 16, "big") + cipher = Cipher(algorithms.AES(key), modes.CTR(iv_bytes), default_backend()) + return CryptographyEncryptorAdapter(cipher) + + def create_aes_cbc(key, iv): + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), default_backend()) + return CryptographyEncryptorAdapter(cipher) + + return create_aes_ctr, create_aes_cbc + + +def try_use_pycrypto_or_pycryptodome_module(): + from Crypto.Cipher import AES + from Crypto.Util import Counter + + def create_aes_ctr(key, iv): + ctr = Counter.new(128, initial_value=iv) + return AES.new(key, AES.MODE_CTR, counter=ctr) + + def create_aes_cbc(key, iv): + return AES.new(key, AES.MODE_CBC, iv) + + return create_aes_ctr, create_aes_cbc + + +def use_slow_bundled_cryptography_module(): + import pyaes + + msg = "To make the program a *lot* faster, please install cryptography module: " + msg += "pip install cryptography\n" + print(msg, flush=True, file=sys.stderr) + + class BundledEncryptorAdapter: + __slots__ = ('mode', ) + + def __init__(self, mode): + self.mode = mode + + def encrypt(self, data): + encrypter = pyaes.Encrypter(self.mode, pyaes.PADDING_NONE) + return encrypter.feed(data) + encrypter.feed() + + def decrypt(self, data): + decrypter = pyaes.Decrypter(self.mode, pyaes.PADDING_NONE) + return decrypter.feed(data) + decrypter.feed() + + def create_aes_ctr(key, iv): + ctr = pyaes.Counter(iv) + return pyaes.AESModeOfOperationCTR(key, ctr) + + def create_aes_cbc(key, iv): + mode = pyaes.AESModeOfOperationCBC(key, iv) + return BundledEncryptorAdapter(mode) + return create_aes_ctr, create_aes_cbc + + +try: + create_aes_ctr, create_aes_cbc = try_use_cryptography_module() +except ImportError: + try: + create_aes_ctr, create_aes_cbc = try_use_pycrypto_or_pycryptodome_module() + except ImportError: + create_aes_ctr, create_aes_cbc = use_slow_bundled_cryptography_module() + + +def print_err(*params): + print(*params, file=sys.stderr, flush=True) + + +def ensure_users_in_user_stats(): + global user_stats + + for user in config.USERS: + user_stats[user].update() + + +def init_proxy_start_time(): + global proxy_start_time + proxy_start_time = time.time() + + +def update_stats(**kw_stats): + global stats + stats.update(**kw_stats) + + +def update_user_stats(user, **kw_stats): + global user_stats + user_stats[user].update(**kw_stats) + + +def update_durations(duration): + global stats + + for bucket in STAT_DURATION_BUCKETS: + if duration <= bucket: + break + + update_stats(**{"connects_with_duration_le_%s" % str(bucket): 1}) + + +def get_curr_connects_count(): + global user_stats + + all_connects = 0 + for user, stat in user_stats.items(): + all_connects += stat["curr_connects"] + return all_connects + + +def get_to_tg_bufsize(): + if isinstance(config.TO_TG_BUFSIZE, int): + return config.TO_TG_BUFSIZE + + low, margin, high = config.TO_TG_BUFSIZE + return high if get_curr_connects_count() < margin else low + + +def get_to_clt_bufsize(): + if isinstance(config.TO_CLT_BUFSIZE, int): + return config.TO_CLT_BUFSIZE + + low, margin, high = config.TO_CLT_BUFSIZE + return high if get_curr_connects_count() < margin else low + + +class MyRandom(random.Random): + def __init__(self): + super().__init__() + key = bytes([random.randrange(256) for i in range(32)]) + iv = random.randrange(256**16) + + self.encryptor = create_aes_ctr(key, iv) + self.buffer = bytearray() + + def getrandbits(self, k): + numbytes = (k + 7) // 8 + return int.from_bytes(self.getrandbytes(numbytes), 'big') >> (numbytes * 8 - k) + + def getrandbytes(self, n): + CHUNK_SIZE = 512 + + while n > len(self.buffer): + data = int.to_bytes(super().getrandbits(CHUNK_SIZE*8), CHUNK_SIZE, "big") + self.buffer += self.encryptor.encrypt(data) + + result = self.buffer[:n] + self.buffer = self.buffer[n:] + return bytes(result) + + +myrandom = MyRandom() + + +class TgConnectionPool: + MAX_CONNS_IN_POOL = 16 + + def __init__(self): + self.pools = {} + + async def open_tg_connection(self, host, port, init_func=None): + task = asyncio.open_connection(host, port, limit=get_to_clt_bufsize()) + reader_tgt, writer_tgt = await asyncio.wait_for(task, timeout=config.TG_CONNECT_TIMEOUT) + + set_keepalive(writer_tgt.get_extra_info("socket")) + set_bufsizes(writer_tgt.get_extra_info("socket"), get_to_clt_bufsize(), get_to_tg_bufsize()) + + if init_func: + return await asyncio.wait_for(init_func(host, port, reader_tgt, writer_tgt), + timeout=config.TG_CONNECT_TIMEOUT) + return reader_tgt, writer_tgt + + def is_conn_dead(self, reader, writer): + if writer.transport.is_closing(): + return True + raw_reader = reader + while hasattr(raw_reader, 'upstream'): + raw_reader = raw_reader.upstream + if raw_reader.at_eof(): + return True + return False + + def register_host_port(self, host, port, init_func): + if (host, port, init_func) not in self.pools: + self.pools[(host, port, init_func)] = [] + + while len(self.pools[(host, port, init_func)]) < TgConnectionPool.MAX_CONNS_IN_POOL: + connect_task = asyncio.ensure_future(self.open_tg_connection(host, port, init_func)) + self.pools[(host, port, init_func)].append(connect_task) + + async def get_connection(self, host, port, init_func=None): + self.register_host_port(host, port, init_func) + + ret = None + for task in self.pools[(host, port, init_func)][:]: + if task.done(): + if task.exception(): + self.pools[(host, port, init_func)].remove(task) + continue + + reader, writer, *other = task.result() + if self.is_conn_dead(reader, writer): + self.pools[(host, port, init_func)].remove(task) + writer.transport.abort() + continue + + if not ret: + self.pools[(host, port, init_func)].remove(task) + ret = (reader, writer, *other) + + self.register_host_port(host, port, init_func) + if ret: + return ret + return await self.open_tg_connection(host, port, init_func) + + +tg_connection_pool = TgConnectionPool() + + +class LayeredStreamReaderBase: + __slots__ = ("upstream", ) + + def __init__(self, upstream): + self.upstream = upstream + + async def read(self, n): + return await self.upstream.read(n) + + async def readexactly(self, n): + return await self.upstream.readexactly(n) + + +class LayeredStreamWriterBase: + __slots__ = ("upstream", ) + + def __init__(self, upstream): + self.upstream = upstream + + def write(self, data, extra={}): + return self.upstream.write(data) + + def write_eof(self): + return self.upstream.write_eof() + + async def drain(self): + return await self.upstream.drain() + + def close(self): + return self.upstream.close() + + def abort(self): + return self.upstream.transport.abort() + + def get_extra_info(self, name): + return self.upstream.get_extra_info(name) + + @property + def transport(self): + return self.upstream.transport + + +class FakeTLSStreamReader(LayeredStreamReaderBase): + __slots__ = ('buf', ) + + def __init__(self, upstream): + self.upstream = upstream + self.buf = bytearray() + + async def read(self, n, ignore_buf=False): + if self.buf and not ignore_buf: + data = self.buf + self.buf = bytearray() + return bytes(data) + + while True: + tls_rec_type = await self.upstream.readexactly(1) + if not tls_rec_type: + return b"" + + if tls_rec_type not in [b"\x14", b"\x17"]: + print_err("BUG: bad tls type %s in FakeTLSStreamReader" % tls_rec_type) + return b"" + + version = await self.upstream.readexactly(2) + if version != b"\x03\x03": + print_err("BUG: unknown version %s in FakeTLSStreamReader" % version) + return b"" + + data_len = int.from_bytes(await self.upstream.readexactly(2), "big") + data = await self.upstream.readexactly(data_len) + if tls_rec_type == b"\x14": + continue + return data + + async def readexactly(self, n): + while len(self.buf) < n: + tls_data = await self.read(1, ignore_buf=True) + if not tls_data: + return b"" + self.buf += tls_data + data, self.buf = self.buf[:n], self.buf[n:] + return bytes(data) + + +class FakeTLSStreamWriter(LayeredStreamWriterBase): + __slots__ = () + + def __init__(self, upstream): + self.upstream = upstream + + def write(self, data, extra={}): + MAX_CHUNK_SIZE = 16384 + 24 + for start in range(0, len(data), MAX_CHUNK_SIZE): + end = min(start+MAX_CHUNK_SIZE, len(data)) + self.upstream.write(b"\x17\x03\x03" + int.to_bytes(end-start, 2, "big")) + self.upstream.write(data[start: end]) + return len(data) + + +class CryptoWrappedStreamReader(LayeredStreamReaderBase): + __slots__ = ('decryptor', 'block_size', 'buf') + + def __init__(self, upstream, decryptor, block_size=1): + self.upstream = upstream + self.decryptor = decryptor + self.block_size = block_size + self.buf = bytearray() + + async def read(self, n): + if self.buf: + ret = bytes(self.buf) + self.buf.clear() + return ret + else: + data = await self.upstream.read(n) + if not data: + return b"" + + needed_till_full_block = -len(data) % self.block_size + if needed_till_full_block > 0: + data += await self.upstream.readexactly(needed_till_full_block) + return self.decryptor.decrypt(data) + + async def readexactly(self, n): + if n > len(self.buf): + to_read = n - len(self.buf) + needed_till_full_block = -to_read % self.block_size + + to_read_block_aligned = to_read + needed_till_full_block + data = await self.upstream.readexactly(to_read_block_aligned) + self.buf += self.decryptor.decrypt(data) + + ret = bytes(self.buf[:n]) + self.buf = self.buf[n:] + return ret + + +class CryptoWrappedStreamWriter(LayeredStreamWriterBase): + __slots__ = ('encryptor', 'block_size') + + def __init__(self, upstream, encryptor, block_size=1): + self.upstream = upstream + self.encryptor = encryptor + self.block_size = block_size + + def write(self, data, extra={}): + if len(data) % self.block_size != 0: + print_err("BUG: writing %d bytes not aligned to block size %d" % ( + len(data), self.block_size)) + return 0 + q = self.encryptor.encrypt(data) + return self.upstream.write(q) + + +class MTProtoFrameStreamReader(LayeredStreamReaderBase): + __slots__ = ('seq_no', ) + + def __init__(self, upstream, seq_no=0): + self.upstream = upstream + self.seq_no = seq_no + + async def read(self, buf_size): + msg_len_bytes = await self.upstream.readexactly(4) + msg_len = int.from_bytes(msg_len_bytes, "little") + # skip paddings + while msg_len == 4: + msg_len_bytes = await self.upstream.readexactly(4) + msg_len = int.from_bytes(msg_len_bytes, "little") + + len_is_bad = (msg_len % len(PADDING_FILLER) != 0) + if not MIN_MSG_LEN <= msg_len <= MAX_MSG_LEN or len_is_bad: + print_err("msg_len is bad, closing connection", msg_len) + return b"" + + msg_seq_bytes = await self.upstream.readexactly(4) + msg_seq = int.from_bytes(msg_seq_bytes, "little", signed=True) + if msg_seq != self.seq_no: + print_err("unexpected seq_no") + return b"" + + self.seq_no += 1 + + data = await self.upstream.readexactly(msg_len - 4 - 4 - 4) + checksum_bytes = await self.upstream.readexactly(4) + checksum = int.from_bytes(checksum_bytes, "little") + + computed_checksum = binascii.crc32(msg_len_bytes + msg_seq_bytes + data) + if computed_checksum != checksum: + return b"" + return data + + +class MTProtoFrameStreamWriter(LayeredStreamWriterBase): + __slots__ = ('seq_no', ) + + def __init__(self, upstream, seq_no=0): + self.upstream = upstream + self.seq_no = seq_no + + def write(self, msg, extra={}): + len_bytes = int.to_bytes(len(msg) + 4 + 4 + 4, 4, "little") + seq_bytes = int.to_bytes(self.seq_no, 4, "little", signed=True) + self.seq_no += 1 + + msg_without_checksum = len_bytes + seq_bytes + msg + checksum = int.to_bytes(binascii.crc32(msg_without_checksum), 4, "little") + + full_msg = msg_without_checksum + checksum + padding = PADDING_FILLER * ((-len(full_msg) % CBC_PADDING) // len(PADDING_FILLER)) + + return self.upstream.write(full_msg + padding) + + +class MTProtoCompactFrameStreamReader(LayeredStreamReaderBase): + __slots__ = () + + async def read(self, buf_size): + msg_len_bytes = await self.upstream.readexactly(1) + msg_len = int.from_bytes(msg_len_bytes, "little") + + extra = {"QUICKACK_FLAG": False} + if msg_len >= 0x80: + extra["QUICKACK_FLAG"] = True + msg_len -= 0x80 + + if msg_len == 0x7f: + msg_len_bytes = await self.upstream.readexactly(3) + msg_len = int.from_bytes(msg_len_bytes, "little") + + msg_len *= 4 + + data = await self.upstream.readexactly(msg_len) + + return data, extra + + +class MTProtoCompactFrameStreamWriter(LayeredStreamWriterBase): + __slots__ = () + + def write(self, data, extra={}): + SMALL_PKT_BORDER = 0x7f + LARGE_PKT_BORDER = 256 ** 3 + + if len(data) % 4 != 0: + print_err("BUG: MTProtoFrameStreamWriter attempted to send msg with len", len(data)) + return 0 + + if extra.get("SIMPLE_ACK"): + return self.upstream.write(data[::-1]) + + len_div_four = len(data) // 4 + + if len_div_four < SMALL_PKT_BORDER: + return self.upstream.write(bytes([len_div_four]) + data) + elif len_div_four < LARGE_PKT_BORDER: + return self.upstream.write(b'\x7f' + int.to_bytes(len_div_four, 3, 'little') + data) + else: + print_err("Attempted to send too large pkt len =", len(data)) + return 0 + + +class MTProtoIntermediateFrameStreamReader(LayeredStreamReaderBase): + __slots__ = () + + async def read(self, buf_size): + msg_len_bytes = await self.upstream.readexactly(4) + msg_len = int.from_bytes(msg_len_bytes, "little") + + extra = {} + if msg_len > 0x80000000: + extra["QUICKACK_FLAG"] = True + msg_len -= 0x80000000 + + data = await self.upstream.readexactly(msg_len) + return data, extra + + +class MTProtoIntermediateFrameStreamWriter(LayeredStreamWriterBase): + __slots__ = () + + def write(self, data, extra={}): + if extra.get("SIMPLE_ACK"): + return self.upstream.write(data) + else: + return self.upstream.write(int.to_bytes(len(data), 4, 'little') + data) + + +class MTProtoSecureIntermediateFrameStreamReader(LayeredStreamReaderBase): + __slots__ = () + + async def read(self, buf_size): + msg_len_bytes = await self.upstream.readexactly(4) + msg_len = int.from_bytes(msg_len_bytes, "little") + + extra = {} + if msg_len > 0x80000000: + extra["QUICKACK_FLAG"] = True + msg_len -= 0x80000000 + + data = await self.upstream.readexactly(msg_len) + + if msg_len % 4 != 0: + cut_border = msg_len - (msg_len % 4) + data = data[:cut_border] + + return data, extra + + +class MTProtoSecureIntermediateFrameStreamWriter(LayeredStreamWriterBase): + __slots__ = () + + def write(self, data, extra={}): + MAX_PADDING_LEN = 4 + if extra.get("SIMPLE_ACK"): + # TODO: make this unpredictable + return self.upstream.write(data) + else: + padding_len = myrandom.randrange(MAX_PADDING_LEN) + padding = myrandom.getrandbytes(padding_len) + padded_data_len_bytes = int.to_bytes(len(data) + padding_len, 4, 'little') + return self.upstream.write(padded_data_len_bytes + data + padding) + + +class ProxyReqStreamReader(LayeredStreamReaderBase): + __slots__ = () + + async def read(self, msg): + RPC_PROXY_ANS = b"\x0d\xda\x03\x44" + RPC_CLOSE_EXT = b"\xa2\x34\xb6\x5e" + RPC_SIMPLE_ACK = b"\x9b\x40\xac\x3b" + RPC_UNKNOWN = b'\xdf\xa2\x30\x57' + + data = await self.upstream.read(1) + + if len(data) < 4: + return b"" + + ans_type = data[:4] + if ans_type == RPC_CLOSE_EXT: + return b"" + + if ans_type == RPC_PROXY_ANS: + ans_flags, conn_id, conn_data = data[4:8], data[8:16], data[16:] + return conn_data + + if ans_type == RPC_SIMPLE_ACK: + conn_id, confirm = data[4:12], data[12:16] + return confirm, {"SIMPLE_ACK": True} + + if ans_type == RPC_UNKNOWN: + return b"", {"SKIP_SEND": True} + + print_err("unknown rpc ans type:", ans_type) + return b"", {"SKIP_SEND": True} + + +class ProxyReqStreamWriter(LayeredStreamWriterBase): + __slots__ = ('remote_ip_port', 'our_ip_port', 'out_conn_id', 'proto_tag') + + def __init__(self, upstream, cl_ip, cl_port, my_ip, my_port, proto_tag): + self.upstream = upstream + + if ":" not in cl_ip: + self.remote_ip_port = b"\x00" * 10 + b"\xff\xff" + self.remote_ip_port += socket.inet_pton(socket.AF_INET, cl_ip) + else: + self.remote_ip_port = socket.inet_pton(socket.AF_INET6, cl_ip) + self.remote_ip_port += int.to_bytes(cl_port, 4, "little") + + if ":" not in my_ip: + self.our_ip_port = b"\x00" * 10 + b"\xff\xff" + self.our_ip_port += socket.inet_pton(socket.AF_INET, my_ip) + else: + self.our_ip_port = socket.inet_pton(socket.AF_INET6, my_ip) + self.our_ip_port += int.to_bytes(my_port, 4, "little") + self.out_conn_id = myrandom.getrandbytes(8) + + self.proto_tag = proto_tag + + def write(self, msg, extra={}): + RPC_PROXY_REQ = b"\xee\xf1\xce\x36" + EXTRA_SIZE = b"\x18\x00\x00\x00" + PROXY_TAG = b"\xae\x26\x1e\xdb" + FOUR_BYTES_ALIGNER = b"\x00\x00\x00" + + FLAG_NOT_ENCRYPTED = 0x2 + FLAG_HAS_AD_TAG = 0x8 + FLAG_MAGIC = 0x1000 + FLAG_EXTMODE2 = 0x20000 + FLAG_PAD = 0x8000000 + FLAG_INTERMEDIATE = 0x20000000 + FLAG_ABRIDGED = 0x40000000 + FLAG_QUICKACK = 0x80000000 + + if len(msg) % 4 != 0: + print_err("BUG: attempted to send msg with len %d" % len(msg)) + return 0 + + flags = FLAG_HAS_AD_TAG | FLAG_MAGIC | FLAG_EXTMODE2 + + if self.proto_tag == PROTO_TAG_ABRIDGED: + flags |= FLAG_ABRIDGED + elif self.proto_tag == PROTO_TAG_INTERMEDIATE: + flags |= FLAG_INTERMEDIATE + elif self.proto_tag == PROTO_TAG_SECURE: + flags |= FLAG_INTERMEDIATE | FLAG_PAD + + if extra.get("QUICKACK_FLAG"): + flags |= FLAG_QUICKACK + + if msg.startswith(b"\x00" * 8): + flags |= FLAG_NOT_ENCRYPTED + + full_msg = bytearray() + full_msg += RPC_PROXY_REQ + int.to_bytes(flags, 4, "little") + self.out_conn_id + full_msg += self.remote_ip_port + self.our_ip_port + EXTRA_SIZE + PROXY_TAG + full_msg += bytes([len(config.AD_TAG)]) + config.AD_TAG + FOUR_BYTES_ALIGNER + full_msg += msg + + return self.upstream.write(full_msg) + + +def try_setsockopt(sock, level, option, value): + try: + sock.setsockopt(level, option, value) + except OSError as E: + pass + + +def set_keepalive(sock, interval=40, attempts=5): + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + if hasattr(socket, "TCP_KEEPIDLE"): + try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, interval) + if hasattr(socket, "TCP_KEEPINTVL"): + try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval) + if hasattr(socket, "TCP_KEEPCNT"): + try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_KEEPCNT, attempts) + + +def set_ack_timeout(sock, timeout): + if hasattr(socket, "TCP_USER_TIMEOUT"): + try_setsockopt(sock, socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, timeout*1000) + + +def set_bufsizes(sock, recv_buf, send_buf): + try_setsockopt(sock, socket.SOL_SOCKET, socket.SO_RCVBUF, recv_buf) + try_setsockopt(sock, socket.SOL_SOCKET, socket.SO_SNDBUF, send_buf) + + +def set_instant_rst(sock): + INSTANT_RST = b"\x01\x00\x00\x00\x00\x00\x00\x00" + if hasattr(socket, "SO_LINGER"): + try_setsockopt(sock, socket.SOL_SOCKET, socket.SO_LINGER, INSTANT_RST) + + +def gen_x25519_public_key(): + # generates some number which has square root by modulo P + P = 2**255 - 19 + n = myrandom.randrange(P) + return int.to_bytes((n*n) % P, length=32, byteorder="little") + + +async def connect_reader_to_writer(reader, writer): + BUF_SIZE = 8192 + try: + while True: + data = await reader.read(BUF_SIZE) + + if not data: + if not writer.transport.is_closing(): + writer.write_eof() + await writer.drain() + return + + writer.write(data) + await writer.drain() + except (OSError, asyncio.IncompleteReadError) as e: + pass + + +async def handle_bad_client(reader_clt, writer_clt, handshake): + BUF_SIZE = 8192 + CONNECT_TIMEOUT = 5 + + global mask_host_cached_ip + + update_stats(connects_bad=1) + + if writer_clt.transport.is_closing(): + return + + set_bufsizes(writer_clt.get_extra_info("socket"), BUF_SIZE, BUF_SIZE) + + if not config.MASK or handshake is None: + while await reader_clt.read(BUF_SIZE): + # just consume all the data + pass + return + + writer_srv = None + try: + host = mask_host_cached_ip or config.MASK_HOST + task = asyncio.open_connection(host, config.MASK_PORT, limit=BUF_SIZE) + reader_srv, writer_srv = await asyncio.wait_for(task, timeout=CONNECT_TIMEOUT) + if not mask_host_cached_ip: + mask_host_cached_ip = writer_srv.get_extra_info("peername")[0] + writer_srv.write(handshake) + await writer_srv.drain() + + srv_to_clt = connect_reader_to_writer(reader_srv, writer_clt) + clt_to_srv = connect_reader_to_writer(reader_clt, writer_srv) + task_srv_to_clt = asyncio.ensure_future(srv_to_clt) + task_clt_to_srv = asyncio.ensure_future(clt_to_srv) + + await asyncio.wait([task_srv_to_clt, task_clt_to_srv], return_when=asyncio.FIRST_COMPLETED) + + task_srv_to_clt.cancel() + task_clt_to_srv.cancel() + + if writer_clt.transport.is_closing(): + return + + # if the server closed the connection with RST or FIN-RST, copy them to the client + if not writer_srv.transport.is_closing(): + # workaround for uvloop, it doesn't fire exceptions on write_eof + sock = writer_srv.get_extra_info('socket') + raw_sock = socket.socket(sock.family, sock.type, sock.proto, sock.fileno()) + try: + raw_sock.shutdown(socket.SHUT_WR) + except OSError as E: + set_instant_rst(writer_clt.get_extra_info("socket")) + finally: + raw_sock.detach() + else: + set_instant_rst(writer_clt.get_extra_info("socket")) + except ConnectionRefusedError as E: + return + except (OSError, asyncio.TimeoutError) as E: + return + finally: + if writer_srv is not None: + writer_srv.transport.abort() + + +async def handle_fake_tls_handshake(handshake, reader, writer, peer): + global used_handshakes + global client_ips + global last_client_ips + global last_clients_with_time_skew + global last_clients_with_same_handshake + global fake_cert_len + + TIME_SKEW_MIN = -20 * 60 + TIME_SKEW_MAX = 10 * 60 + + TLS_VERS = b"\x03\x03" + TLS_CIPHERSUITE = b"\x13\x01" + TLS_CHANGE_CIPHER = b"\x14" + TLS_VERS + b"\x00\x01\x01" + TLS_APP_HTTP2_HDR = b"\x17" + TLS_VERS + + DIGEST_LEN = 32 + DIGEST_HALFLEN = 16 + DIGEST_POS = 11 + + SESSION_ID_LEN_POS = DIGEST_POS + DIGEST_LEN + SESSION_ID_POS = SESSION_ID_LEN_POS + 1 + + tls_extensions = b"\x00\x2e" + b"\x00\x33\x00\x24" + b"\x00\x1d\x00\x20" + tls_extensions += gen_x25519_public_key() + b"\x00\x2b\x00\x02\x03\x04" + + digest = handshake[DIGEST_POS:DIGEST_POS+DIGEST_LEN] + + if digest[:DIGEST_HALFLEN] in used_handshakes: + last_clients_with_same_handshake[peer[0]] += 1 + return False + + sess_id_len = handshake[SESSION_ID_LEN_POS] + sess_id = handshake[SESSION_ID_POS:SESSION_ID_POS+sess_id_len] + + for user in config.USERS: + secret = bytes.fromhex(config.USERS[user]) + + msg = handshake[:DIGEST_POS] + b"\x00"*DIGEST_LEN + handshake[DIGEST_POS+DIGEST_LEN:] + computed_digest = hmac.new(secret, msg, digestmod=hashlib.sha256).digest() + + xored_digest = bytes(digest[i] ^ computed_digest[i] for i in range(DIGEST_LEN)) + digest_good = xored_digest.startswith(b"\x00" * (DIGEST_LEN-4)) + + if not digest_good: + continue + + timestamp = int.from_bytes(xored_digest[-4:], "little") + client_time_is_ok = TIME_SKEW_MIN < time.time() - timestamp < TIME_SKEW_MAX + + # some clients fail to read unix time and send the time since boot instead + client_time_is_small = timestamp < 60*60*24*1000 + accept_bad_time = config.IGNORE_TIME_SKEW or is_time_skewed or client_time_is_small + + if not client_time_is_ok and not accept_bad_time: + last_clients_with_time_skew[peer[0]] = (time.time() - timestamp) // 60 + continue + + http_data = myrandom.getrandbytes(fake_cert_len) + + srv_hello = TLS_VERS + b"\x00"*DIGEST_LEN + bytes([sess_id_len]) + sess_id + srv_hello += TLS_CIPHERSUITE + b"\x00" + tls_extensions + + hello_pkt = b"\x16" + TLS_VERS + int.to_bytes(len(srv_hello) + 4, 2, "big") + hello_pkt += b"\x02" + int.to_bytes(len(srv_hello), 3, "big") + srv_hello + hello_pkt += TLS_CHANGE_CIPHER + TLS_APP_HTTP2_HDR + hello_pkt += int.to_bytes(len(http_data), 2, "big") + http_data + + computed_digest = hmac.new(secret, msg=digest+hello_pkt, digestmod=hashlib.sha256).digest() + hello_pkt = hello_pkt[:DIGEST_POS] + computed_digest + hello_pkt[DIGEST_POS+DIGEST_LEN:] + + writer.write(hello_pkt) + await writer.drain() + + if config.REPLAY_CHECK_LEN > 0: + while len(used_handshakes) >= config.REPLAY_CHECK_LEN: + used_handshakes.popitem(last=False) + used_handshakes[digest[:DIGEST_HALFLEN]] = True + + if config.CLIENT_IPS_LEN > 0: + while len(client_ips) >= config.CLIENT_IPS_LEN: + client_ips.popitem(last=False) + if peer[0] not in client_ips: + client_ips[peer[0]] = True + last_client_ips[peer[0]] = True + + reader = FakeTLSStreamReader(reader) + writer = FakeTLSStreamWriter(writer) + return reader, writer + + return False + + +async def handle_proxy_protocol(reader, peer=None): + PROXY_SIGNATURE = b"PROXY " + PROXY_MIN_LEN = 6 + PROXY_TCP4 = b"TCP4" + PROXY_TCP6 = b"TCP6" + PROXY_UNKNOWN = b"UNKNOWN" + + PROXY2_SIGNATURE = b"\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a" + PROXY2_MIN_LEN = 16 + PROXY2_AF_UNSPEC = 0x0 + PROXY2_AF_INET = 0x1 + PROXY2_AF_INET6 = 0x2 + + header = await reader.readexactly(PROXY_MIN_LEN) + if header.startswith(PROXY_SIGNATURE): + # proxy header v1 + header += await reader.readuntil(b"\r\n") + _, proxy_fam, *proxy_addr = header[:-2].split(b" ") + if proxy_fam in (PROXY_TCP4, PROXY_TCP6): + if len(proxy_addr) == 4: + src_addr = proxy_addr[0].decode('ascii') + src_port = int(proxy_addr[2].decode('ascii')) + return (src_addr, src_port) + elif proxy_fam == PROXY_UNKNOWN: + return peer + return False + + header += await reader.readexactly(PROXY2_MIN_LEN - PROXY_MIN_LEN) + if header.startswith(PROXY2_SIGNATURE): + # proxy header v2 + proxy_ver = header[12] + if proxy_ver & 0xf0 != 0x20: + return False + proxy_len = int.from_bytes(header[14:16], "big") + proxy_addr = await reader.readexactly(proxy_len) + if proxy_ver == 0x21: + proxy_fam = header[13] >> 4 + if proxy_fam == PROXY2_AF_INET: + if proxy_len >= (4 + 2)*2: + src_addr = socket.inet_ntop(socket.AF_INET, proxy_addr[:4]) + src_port = int.from_bytes(proxy_addr[8:10], "big") + return (src_addr, src_port) + elif proxy_fam == PROXY2_AF_INET6: + if proxy_len >= (16 + 2)*2: + src_addr = socket.inet_ntop(socket.AF_INET6, proxy_addr[:16]) + src_port = int.from_bytes(proxy_addr[32:34], "big") + return (src_addr, src_port) + elif proxy_fam == PROXY2_AF_UNSPEC: + return peer + elif proxy_ver == 0x20: + return peer + + return False + + +async def handle_handshake(reader, writer): + global used_handshakes + global client_ips + global last_client_ips + global last_clients_with_same_handshake + + TLS_START_BYTES = b"\x16\x03\x01" + + if writer.transport.is_closing() or writer.get_extra_info("peername") is None: + return False + + peer = writer.get_extra_info("peername")[:2] + if not peer: + peer = ("unknown ip", 0) + + if config.PROXY_PROTOCOL: + ip = peer[0] if peer else "unknown ip" + peer = await handle_proxy_protocol(reader, peer) + if not peer: + print_err("Client from %s sent bad proxy protocol headers" % ip) + await handle_bad_client(reader, writer, None) + return False + + is_tls_handshake = True + handshake = b"" + for expected_byte in TLS_START_BYTES: + handshake += await reader.readexactly(1) + if handshake[-1] != expected_byte: + is_tls_handshake = False + break + + if is_tls_handshake: + handshake += await reader.readexactly(2) + tls_handshake_len = int.from_bytes(handshake[-2:], "big") + if tls_handshake_len < 512: + is_tls_handshake = False + + if is_tls_handshake: + handshake += await reader.readexactly(tls_handshake_len) + tls_handshake_result = await handle_fake_tls_handshake(handshake, reader, writer, peer) + + if not tls_handshake_result: + await handle_bad_client(reader, writer, handshake) + return False + reader, writer = tls_handshake_result + handshake = await reader.readexactly(HANDSHAKE_LEN) + else: + if not config.MODES["classic"] and not config.MODES["secure"]: + await handle_bad_client(reader, writer, handshake) + return False + handshake += await reader.readexactly(HANDSHAKE_LEN - len(handshake)) + + dec_prekey_and_iv = handshake[SKIP_LEN:SKIP_LEN+PREKEY_LEN+IV_LEN] + dec_prekey, dec_iv = dec_prekey_and_iv[:PREKEY_LEN], dec_prekey_and_iv[PREKEY_LEN:] + enc_prekey_and_iv = handshake[SKIP_LEN:SKIP_LEN+PREKEY_LEN+IV_LEN][::-1] + enc_prekey, enc_iv = enc_prekey_and_iv[:PREKEY_LEN], enc_prekey_and_iv[PREKEY_LEN:] + + if dec_prekey_and_iv in used_handshakes: + last_clients_with_same_handshake[peer[0]] += 1 + await handle_bad_client(reader, writer, handshake) + return False + + for user in config.USERS: + secret = bytes.fromhex(config.USERS[user]) + + dec_key = hashlib.sha256(dec_prekey + secret).digest() + decryptor = create_aes_ctr(key=dec_key, iv=int.from_bytes(dec_iv, "big")) + + enc_key = hashlib.sha256(enc_prekey + secret).digest() + encryptor = create_aes_ctr(key=enc_key, iv=int.from_bytes(enc_iv, "big")) + + decrypted = decryptor.decrypt(handshake) + + proto_tag = decrypted[PROTO_TAG_POS:PROTO_TAG_POS+4] + if proto_tag not in (PROTO_TAG_ABRIDGED, PROTO_TAG_INTERMEDIATE, PROTO_TAG_SECURE): + continue + + if proto_tag == PROTO_TAG_SECURE: + if is_tls_handshake and not config.MODES["tls"]: + continue + if not is_tls_handshake and not config.MODES["secure"]: + continue + else: + if not config.MODES["classic"]: + continue + + dc_idx = int.from_bytes(decrypted[DC_IDX_POS:DC_IDX_POS+2], "little", signed=True) + + if config.REPLAY_CHECK_LEN > 0: + while len(used_handshakes) >= config.REPLAY_CHECK_LEN: + used_handshakes.popitem(last=False) + used_handshakes[dec_prekey_and_iv] = True + + if config.CLIENT_IPS_LEN > 0: + while len(client_ips) >= config.CLIENT_IPS_LEN: + client_ips.popitem(last=False) + if peer[0] not in client_ips: + client_ips[peer[0]] = True + last_client_ips[peer[0]] = True + + reader = CryptoWrappedStreamReader(reader, decryptor) + writer = CryptoWrappedStreamWriter(writer, encryptor) + return reader, writer, proto_tag, user, dc_idx, enc_key + enc_iv, peer + + await handle_bad_client(reader, writer, handshake) + return False + + +async def do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=None): + RESERVED_NONCE_FIRST_CHARS = [b"\xef"] + RESERVED_NONCE_BEGININGS = [b"\x48\x45\x41\x44", b"\x50\x4F\x53\x54", + b"\x47\x45\x54\x20", b"\xee\xee\xee\xee", + b"\xdd\xdd\xdd\xdd", b"\x16\x03\x01\x02"] + RESERVED_NONCE_CONTINUES = [b"\x00\x00\x00\x00"] + + global my_ip_info + global tg_connection_pool + + dc_idx = abs(dc_idx) - 1 + + if my_ip_info["ipv6"] and (config.PREFER_IPV6 or not my_ip_info["ipv4"]): + if not 0 <= dc_idx < len(TG_DATACENTERS_V6): + return False + dc = TG_DATACENTERS_V6[dc_idx] + else: + if not 0 <= dc_idx < len(TG_DATACENTERS_V4): + return False + dc = TG_DATACENTERS_V4[dc_idx] + + try: + reader_tgt, writer_tgt = await tg_connection_pool.get_connection(dc, TG_DATACENTER_PORT) + except ConnectionRefusedError as E: + print_err("Got connection refused while trying to connect to", dc, TG_DATACENTER_PORT) + return False + except ConnectionAbortedError as E: + print_err("The Telegram server connection is bad: %d (%s %s) %s" % (dc_idx, dc, TG_DATACENTER_PORT, E)) + return False + except (OSError, asyncio.TimeoutError) as E: + print_err("Unable to connect to", dc, TG_DATACENTER_PORT) + return False + + while True: + rnd = bytearray(myrandom.getrandbytes(HANDSHAKE_LEN)) + if rnd[:1] in RESERVED_NONCE_FIRST_CHARS: + continue + if rnd[:4] in RESERVED_NONCE_BEGININGS: + continue + if rnd[4:8] in RESERVED_NONCE_CONTINUES: + continue + break + + rnd[PROTO_TAG_POS:PROTO_TAG_POS+4] = proto_tag + + if dec_key_and_iv: + rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN] = dec_key_and_iv[::-1] + + rnd = bytes(rnd) + + dec_key_and_iv = rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN][::-1] + dec_key, dec_iv = dec_key_and_iv[:KEY_LEN], dec_key_and_iv[KEY_LEN:] + decryptor = create_aes_ctr(key=dec_key, iv=int.from_bytes(dec_iv, "big")) + + enc_key_and_iv = rnd[SKIP_LEN:SKIP_LEN+KEY_LEN+IV_LEN] + enc_key, enc_iv = enc_key_and_iv[:KEY_LEN], enc_key_and_iv[KEY_LEN:] + encryptor = create_aes_ctr(key=enc_key, iv=int.from_bytes(enc_iv, "big")) + + rnd_enc = rnd[:PROTO_TAG_POS] + encryptor.encrypt(rnd)[PROTO_TAG_POS:] + + writer_tgt.write(rnd_enc) + await writer_tgt.drain() + + reader_tgt = CryptoWrappedStreamReader(reader_tgt, decryptor) + writer_tgt = CryptoWrappedStreamWriter(writer_tgt, encryptor) + + return reader_tgt, writer_tgt + + +def get_middleproxy_aes_key_and_iv(nonce_srv, nonce_clt, clt_ts, srv_ip, clt_port, purpose, + clt_ip, srv_port, middleproxy_secret, clt_ipv6=None, + srv_ipv6=None): + EMPTY_IP = b"\x00\x00\x00\x00" + + if not clt_ip or not srv_ip: + clt_ip = EMPTY_IP + srv_ip = EMPTY_IP + + s = bytearray() + s += nonce_srv + nonce_clt + clt_ts + srv_ip + clt_port + purpose + clt_ip + srv_port + s += middleproxy_secret + nonce_srv + + if clt_ipv6 and srv_ipv6: + s += clt_ipv6 + srv_ipv6 + + s += nonce_clt + + md5_sum = hashlib.md5(s[1:]).digest() + sha1_sum = hashlib.sha1(s).digest() + + key = md5_sum[:12] + sha1_sum + iv = hashlib.md5(s[2:]).digest() + return key, iv + + +async def middleproxy_handshake(host, port, reader_tgt, writer_tgt): + """ The most logic of middleproxy handshake, launched in pool """ + START_SEQ_NO = -2 + NONCE_LEN = 16 + + RPC_HANDSHAKE = b"\xf5\xee\x82\x76" + RPC_NONCE = b"\xaa\x87\xcb\x7a" + # pass as consts to simplify code + RPC_FLAGS = b"\x00\x00\x00\x00" + CRYPTO_AES = b"\x01\x00\x00\x00" + + RPC_NONCE_ANS_LEN = 32 + RPC_HANDSHAKE_ANS_LEN = 32 + + writer_tgt = MTProtoFrameStreamWriter(writer_tgt, START_SEQ_NO) + key_selector = PROXY_SECRET[:4] + crypto_ts = int.to_bytes(int(time.time()) % (256**4), 4, "little") + + nonce = myrandom.getrandbytes(NONCE_LEN) + + msg = RPC_NONCE + key_selector + CRYPTO_AES + crypto_ts + nonce + + writer_tgt.write(msg) + await writer_tgt.drain() + + reader_tgt = MTProtoFrameStreamReader(reader_tgt, START_SEQ_NO) + ans = await reader_tgt.read(get_to_clt_bufsize()) + + if len(ans) != RPC_NONCE_ANS_LEN: + raise ConnectionAbortedError("bad rpc answer length") + + rpc_type, rpc_key_selector, rpc_schema, rpc_crypto_ts, rpc_nonce = ( + ans[:4], ans[4:8], ans[8:12], ans[12:16], ans[16:32] + ) + + if rpc_type != RPC_NONCE or rpc_key_selector != key_selector or rpc_schema != CRYPTO_AES: + raise ConnectionAbortedError("bad rpc answer") + + # get keys + tg_ip, tg_port = writer_tgt.upstream.get_extra_info('peername')[:2] + my_ip, my_port = writer_tgt.upstream.get_extra_info('sockname')[:2] + + use_ipv6_tg = (":" in tg_ip) + + if not use_ipv6_tg: + if my_ip_info["ipv4"]: + # prefer global ip settings to work behind NAT + my_ip = my_ip_info["ipv4"] + + tg_ip_bytes = socket.inet_pton(socket.AF_INET, tg_ip)[::-1] + my_ip_bytes = socket.inet_pton(socket.AF_INET, my_ip)[::-1] + + tg_ipv6_bytes = None + my_ipv6_bytes = None + else: + if my_ip_info["ipv6"]: + my_ip = my_ip_info["ipv6"] + + tg_ip_bytes = None + my_ip_bytes = None + + tg_ipv6_bytes = socket.inet_pton(socket.AF_INET6, tg_ip) + my_ipv6_bytes = socket.inet_pton(socket.AF_INET6, my_ip) + + tg_port_bytes = int.to_bytes(tg_port, 2, "little") + my_port_bytes = int.to_bytes(my_port, 2, "little") + + enc_key, enc_iv = get_middleproxy_aes_key_and_iv( + nonce_srv=rpc_nonce, nonce_clt=nonce, clt_ts=crypto_ts, srv_ip=tg_ip_bytes, + clt_port=my_port_bytes, purpose=b"CLIENT", clt_ip=my_ip_bytes, srv_port=tg_port_bytes, + middleproxy_secret=PROXY_SECRET, clt_ipv6=my_ipv6_bytes, srv_ipv6=tg_ipv6_bytes) + + dec_key, dec_iv = get_middleproxy_aes_key_and_iv( + nonce_srv=rpc_nonce, nonce_clt=nonce, clt_ts=crypto_ts, srv_ip=tg_ip_bytes, + clt_port=my_port_bytes, purpose=b"SERVER", clt_ip=my_ip_bytes, srv_port=tg_port_bytes, + middleproxy_secret=PROXY_SECRET, clt_ipv6=my_ipv6_bytes, srv_ipv6=tg_ipv6_bytes) + + encryptor = create_aes_cbc(key=enc_key, iv=enc_iv) + decryptor = create_aes_cbc(key=dec_key, iv=dec_iv) + + SENDER_PID = b"IPIPPRPDTIME" + PEER_PID = b"IPIPPRPDTIME" + + # TODO: pass client ip and port here for statistics + handshake = RPC_HANDSHAKE + RPC_FLAGS + SENDER_PID + PEER_PID + + writer_tgt.upstream = CryptoWrappedStreamWriter(writer_tgt.upstream, encryptor, block_size=16) + writer_tgt.write(handshake) + await writer_tgt.drain() + + reader_tgt.upstream = CryptoWrappedStreamReader(reader_tgt.upstream, decryptor, block_size=16) + + handshake_ans = await reader_tgt.read(1) + if len(handshake_ans) != RPC_HANDSHAKE_ANS_LEN: + raise ConnectionAbortedError("bad rpc handshake answer length") + + handshake_type, handshake_flags, handshake_sender_pid, handshake_peer_pid = ( + handshake_ans[:4], handshake_ans[4:8], handshake_ans[8:20], handshake_ans[20:32]) + if handshake_type != RPC_HANDSHAKE or handshake_peer_pid != SENDER_PID: + raise ConnectionAbortedError("bad rpc handshake answer") + + return reader_tgt, writer_tgt, my_ip, my_port + + +async def do_middleproxy_handshake(proto_tag, dc_idx, cl_ip, cl_port): + global my_ip_info + global tg_connection_pool + + use_ipv6_tg = (my_ip_info["ipv6"] and (config.PREFER_IPV6 or not my_ip_info["ipv4"])) + + if use_ipv6_tg: + if dc_idx not in TG_MIDDLE_PROXIES_V6: + return False + addr, port = myrandom.choice(TG_MIDDLE_PROXIES_V6[dc_idx]) + else: + if dc_idx not in TG_MIDDLE_PROXIES_V4: + return False + addr, port = myrandom.choice(TG_MIDDLE_PROXIES_V4[dc_idx]) + + try: + ret = await tg_connection_pool.get_connection(addr, port, middleproxy_handshake) + reader_tgt, writer_tgt, my_ip, my_port = ret + except ConnectionRefusedError as E: + print_err("The Telegram server %d (%s %s) is refusing connections" % (dc_idx, addr, port)) + return False + except ConnectionAbortedError as E: + print_err("The Telegram server connection is bad: %d (%s %s) %s" % (dc_idx, addr, port, E)) + return False + except (OSError, asyncio.TimeoutError) as E: + print_err("Unable to connect to the Telegram server %d (%s %s)" % (dc_idx, addr, port)) + return False + + writer_tgt = ProxyReqStreamWriter(writer_tgt, cl_ip, cl_port, my_ip, my_port, proto_tag) + reader_tgt = ProxyReqStreamReader(reader_tgt) + + return reader_tgt, writer_tgt + + +async def tg_connect_reader_to_writer(rd, wr, user, rd_buf_size, is_upstream): + try: + while True: + if not is_upstream: + data = await asyncio.wait_for(rd.read(rd_buf_size), + timeout=config.TG_READ_TIMEOUT) + else: + data = await rd.read(rd_buf_size) + if isinstance(data, tuple): + data, extra = data + else: + extra = {} + + if extra.get("SKIP_SEND"): + continue + + if not data: + wr.write_eof() + await wr.drain() + return + else: + if is_upstream: + update_user_stats(user, octets_from_client=len(data), msgs_from_client=1) + else: + update_user_stats(user, octets_to_client=len(data), msgs_to_client=1) + + wr.write(data, extra) + await wr.drain() + except (OSError, asyncio.IncompleteReadError, asyncio.TimeoutError) as e: + # print_err(e) + pass + + +async def handle_client(reader_clt, writer_clt): + set_keepalive(writer_clt.get_extra_info("socket"), config.CLIENT_KEEPALIVE, attempts=3) + set_ack_timeout(writer_clt.get_extra_info("socket"), config.CLIENT_ACK_TIMEOUT) + set_bufsizes(writer_clt.get_extra_info("socket"), get_to_tg_bufsize(), get_to_clt_bufsize()) + + update_stats(connects_all=1) + + try: + clt_data = await asyncio.wait_for(handle_handshake(reader_clt, writer_clt), + timeout=config.CLIENT_HANDSHAKE_TIMEOUT) + except asyncio.TimeoutError: + update_stats(handshake_timeouts=1) + return + + if not clt_data: + return + + reader_clt, writer_clt, proto_tag, user, dc_idx, enc_key_and_iv, peer = clt_data + cl_ip, cl_port = peer + + update_user_stats(user, connects=1) + + connect_directly = (not config.USE_MIDDLE_PROXY or disable_middle_proxy) + + if connect_directly: + if config.FAST_MODE: + tg_data = await do_direct_handshake(proto_tag, dc_idx, dec_key_and_iv=enc_key_and_iv) + else: + tg_data = await do_direct_handshake(proto_tag, dc_idx) + else: + tg_data = await do_middleproxy_handshake(proto_tag, dc_idx, cl_ip, cl_port) + + if not tg_data: + return + + reader_tg, writer_tg = tg_data + + if connect_directly and config.FAST_MODE: + class FakeEncryptor: + def encrypt(self, data): + return data + + class FakeDecryptor: + def decrypt(self, data): + return data + + reader_tg.decryptor = FakeDecryptor() + writer_clt.encryptor = FakeEncryptor() + + if not connect_directly: + if proto_tag == PROTO_TAG_ABRIDGED: + reader_clt = MTProtoCompactFrameStreamReader(reader_clt) + writer_clt = MTProtoCompactFrameStreamWriter(writer_clt) + elif proto_tag == PROTO_TAG_INTERMEDIATE: + reader_clt = MTProtoIntermediateFrameStreamReader(reader_clt) + writer_clt = MTProtoIntermediateFrameStreamWriter(writer_clt) + elif proto_tag == PROTO_TAG_SECURE: + reader_clt = MTProtoSecureIntermediateFrameStreamReader(reader_clt) + writer_clt = MTProtoSecureIntermediateFrameStreamWriter(writer_clt) + else: + return + + tg_to_clt = tg_connect_reader_to_writer(reader_tg, writer_clt, user, + get_to_clt_bufsize(), False) + clt_to_tg = tg_connect_reader_to_writer(reader_clt, writer_tg, + user, get_to_tg_bufsize(), True) + task_tg_to_clt = asyncio.ensure_future(tg_to_clt) + task_clt_to_tg = asyncio.ensure_future(clt_to_tg) + + update_user_stats(user, curr_connects=1) + + tcp_limit_hit = ( + user in config.USER_MAX_TCP_CONNS and + user_stats[user]["curr_connects"] > config.USER_MAX_TCP_CONNS[user] + ) + + user_expired = ( + user in config.USER_EXPIRATIONS and + datetime.datetime.now() > config.USER_EXPIRATIONS[user] + ) + + user_data_quota_hit = ( + user in config.USER_DATA_QUOTA and + (user_stats[user]["octets_to_client"] + + user_stats[user]["octets_from_client"] > config.USER_DATA_QUOTA[user]) + ) + + if (not tcp_limit_hit) and (not user_expired) and (not user_data_quota_hit): + start = time.time() + await asyncio.wait([task_tg_to_clt, task_clt_to_tg], return_when=asyncio.FIRST_COMPLETED) + update_durations(time.time() - start) + + update_user_stats(user, curr_connects=-1) + + task_tg_to_clt.cancel() + task_clt_to_tg.cancel() + + writer_tg.transport.abort() + + +async def handle_client_wrapper(reader, writer): + try: + await handle_client(reader, writer) + except (asyncio.IncompleteReadError, asyncio.CancelledError): + pass + except (ConnectionResetError, TimeoutError, BrokenPipeError): + pass + except Exception: + traceback.print_exc() + finally: + writer.transport.abort() + + +def make_metrics_pkt(metrics): + pkt_body_list = [] + used_names = set() + + for name, m_type, desc, val in metrics: + name = config.METRICS_PREFIX + name + if name not in used_names: + pkt_body_list.append("# HELP %s %s" % (name, desc)) + pkt_body_list.append("# TYPE %s %s" % (name, m_type)) + used_names.add(name) + + if isinstance(val, dict): + tags = [] + for tag, tag_val in val.items(): + if tag == "val": + continue + tag_val = tag_val.replace('"', r'\"') + tags.append('%s="%s"' % (tag, tag_val)) + pkt_body_list.append("%s{%s} %s" % (name, ",".join(tags), val["val"])) + else: + pkt_body_list.append("%s %s" % (name, val)) + pkt_body = "\n".join(pkt_body_list) + "\n" + + pkt_header_list = [] + pkt_header_list.append("HTTP/1.1 200 OK") + pkt_header_list.append("Connection: close") + pkt_header_list.append("Content-Length: %d" % len(pkt_body)) + pkt_header_list.append("Content-Type: text/plain; version=0.0.4; charset=utf-8") + pkt_header_list.append("Date: %s" % time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())) + + pkt_header = "\r\n".join(pkt_header_list) + + pkt = pkt_header + "\r\n\r\n" + pkt_body + return pkt + + +async def handle_metrics(reader, writer): + global stats + global user_stats + global my_ip_info + global proxy_start_time + global proxy_links + global last_clients_with_time_skew + global last_clients_with_same_handshake + + client_ip = writer.get_extra_info("peername")[0] + if client_ip not in config.METRICS_WHITELIST: + writer.close() + return + + try: + metrics = [] + metrics.append(["uptime", "counter", "proxy uptime", time.time() - proxy_start_time]) + metrics.append(["connects_bad", "counter", "connects with bad secret", + stats["connects_bad"]]) + metrics.append(["connects_all", "counter", "incoming connects", stats["connects_all"]]) + metrics.append(["handshake_timeouts", "counter", "number of timed out handshakes", + stats["handshake_timeouts"]]) + + if config.METRICS_EXPORT_LINKS: + for link in proxy_links: + link_as_metric = link.copy() + link_as_metric["val"] = 1 + metrics.append(["proxy_link_info", "counter", + "the proxy link info", link_as_metric]) + + bucket_start = 0 + for bucket in STAT_DURATION_BUCKETS: + bucket_end = bucket if bucket != STAT_DURATION_BUCKETS[-1] else "+Inf" + metric = { + "bucket": "%s-%s" % (bucket_start, bucket_end), + "val": stats["connects_with_duration_le_%s" % str(bucket)] + } + metrics.append(["connects_by_duration", "counter", "connects by duration", metric]) + bucket_start = bucket_end + + user_metrics_desc = [ + ["user_connects", "counter", "user connects", "connects"], + ["user_connects_curr", "gauge", "current user connects", "curr_connects"], + ["user_octets", "counter", "octets proxied for user", + "octets_from_client+octets_to_client"], + ["user_msgs", "counter", "msgs proxied for user", + "msgs_from_client+msgs_to_client"], + ["user_octets_from", "counter", "octets proxied from user", "octets_from_client"], + ["user_octets_to", "counter", "octets proxied to user", "octets_to_client"], + ["user_msgs_from", "counter", "msgs proxied from user", "msgs_from_client"], + ["user_msgs_to", "counter", "msgs proxied to user", "msgs_to_client"], + ] + + for m_name, m_type, m_desc, stat_key in user_metrics_desc: + for user, stat in user_stats.items(): + if "+" in stat_key: + val = 0 + for key_part in stat_key.split("+"): + val += stat[key_part] + else: + val = stat[stat_key] + metric = {"user": user, "val": val} + metrics.append([m_name, m_type, m_desc, metric]) + + pkt = make_metrics_pkt(metrics) + writer.write(pkt.encode()) + await writer.drain() + + except Exception: + traceback.print_exc() + finally: + writer.close() + + +async def stats_printer(): + global user_stats + global last_client_ips + global last_clients_with_time_skew + global last_clients_with_same_handshake + + while True: + await asyncio.sleep(config.STATS_PRINT_PERIOD) + + print("Stats for", time.strftime("%d.%m.%Y %H:%M:%S")) + for user, stat in user_stats.items(): + print("%s: %d connects (%d current), %.2f MB, %d msgs" % ( + user, stat["connects"], stat["curr_connects"], + (stat["octets_from_client"] + stat["octets_to_client"]) / 1000000, + stat["msgs_from_client"] + stat["msgs_to_client"])) + print(flush=True) + + if last_client_ips: + print("New IPs:") + for ip in last_client_ips: + print(ip) + print(flush=True) + last_client_ips.clear() + + if last_clients_with_time_skew: + print("Clients with time skew (possible replay-attackers):") + for ip, skew_minutes in last_clients_with_time_skew.items(): + print("%s, clocks were %d minutes behind" % (ip, skew_minutes)) + print(flush=True) + last_clients_with_time_skew.clear() + if last_clients_with_same_handshake: + print("Clients with duplicate handshake (likely replay-attackers):") + for ip, times in last_clients_with_same_handshake.items(): + print("%s, %d times" % (ip, times)) + print(flush=True) + last_clients_with_same_handshake.clear() + + +async def make_https_req(url, host="core.telegram.org"): + """ Make request, return resp body and headers. """ + SSL_PORT = 443 + url_data = urllib.parse.urlparse(url) + + HTTP_REQ_TEMPLATE = "\r\n".join(["GET %s HTTP/1.1", "Host: %s", + "Connection: close"]) + "\r\n\r\n" + reader, writer = await asyncio.open_connection(url_data.netloc, SSL_PORT, ssl=True) + req = HTTP_REQ_TEMPLATE % (urllib.parse.quote(url_data.path), host) + writer.write(req.encode("utf8")) + data = await reader.read() + writer.close() + + headers, body = data.split(b"\r\n\r\n", 1) + return headers, body + + +def gen_tls_client_hello_msg(server_name): + msg = bytearray() + msg += b"\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03" + myrandom.getrandbytes(32) + msg += b"\x20" + myrandom.getrandbytes(32) + msg += b"\x00\x22\x4a\x4a\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9" + msg += b"\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x0a\x01\x00\x01\x91" + msg += b"\xda\xda\x00\x00\x00\x00" + msg += int.to_bytes(len(server_name) + 5, 2, "big") + msg += int.to_bytes(len(server_name) + 3, 2, "big") + b"\x00" + msg += int.to_bytes(len(server_name), 2, "big") + server_name.encode("ascii") + msg += b"\x00\x17\x00\x00\xff\x01\x00\x01\x00\x00\x0a\x00\x0a\x00\x08\xaa\xaa\x00\x1d\x00" + msg += b"\x17\x00\x18\x00\x0b\x00\x02\x01\x00\x00\x23\x00\x00\x00\x10\x00\x0e\x00\x0c\x02" + msg += b"\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\x00\x05\x00\x05\x01\x00\x00\x00\x00" + msg += b"\x00\x0d\x00\x14\x00\x12\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06" + msg += b"\x06\x01\x02\x01\x00\x12\x00\x00\x00\x33\x00\x2b\x00\x29\xaa\xaa\x00\x01\x00\x00" + msg += b"\x1d\x00\x20" + gen_x25519_public_key() + msg += b"\x00\x2d\x00\x02\x01\x01\x00\x2b\x00\x0b\x0a\xba\xba\x03\x04\x03\x03\x03\x02\x03" + msg += b"\x01\x00\x1b\x00\x03\x02\x00\x02\x3a\x3a\x00\x01\x00\x00\x15" + msg += int.to_bytes(517 - len(msg) - 2, 2, "big") + msg += b"\x00" * (517 - len(msg)) + return bytes(msg) + + +async def get_encrypted_cert(host, port, server_name): + async def get_tls_record(reader): + try: + record_type = (await reader.readexactly(1))[0] + tls_version = await reader.readexactly(2) + if tls_version != b"\x03\x03": + return 0, b"" + record_len = int.from_bytes(await reader.readexactly(2), "big") + record = await reader.readexactly(record_len) + + return record_type, record + except asyncio.IncompleteReadError: + return 0, b"" + + reader, writer = await asyncio.open_connection(host, port) + writer.write(gen_tls_client_hello_msg(server_name)) + await writer.drain() + + record1_type, record1 = await get_tls_record(reader) + if record1_type != 22: + return b"" + + record2_type, record2 = await get_tls_record(reader) + if record2_type != 20: + return b"" + + record3_type, record3 = await get_tls_record(reader) + if record3_type != 23: + return b"" + + if len(record3) < MIN_CERT_LEN: + record4_type, record4 = await get_tls_record(reader) + if record4_type != 23: + return b"" + msg = ("The MASK_HOST %s sent some TLS record before certificate record, this makes the " + + "proxy more detectable") % config.MASK_HOST + print_err(msg) + + return record4 + + return record3 + + +async def get_mask_host_cert_len(): + global fake_cert_len + + GET_CERT_TIMEOUT = 10 + MASK_ENABLING_CHECK_PERIOD = 60 + + while True: + try: + if not config.MASK: + # do nothing + await asyncio.sleep(MASK_ENABLING_CHECK_PERIOD) + continue + + task = get_encrypted_cert(config.MASK_HOST, config.MASK_PORT, config.TLS_DOMAIN) + cert = await asyncio.wait_for(task, timeout=GET_CERT_TIMEOUT) + if cert: + if len(cert) < MIN_CERT_LEN: + msg = ("The MASK_HOST %s returned several TLS records, this is not supported" % + config.MASK_HOST) + print_err(msg) + elif len(cert) != fake_cert_len: + fake_cert_len = len(cert) + print_err("Got cert from the MASK_HOST %s, its length is %d" % + (config.MASK_HOST, fake_cert_len)) + else: + print_err("The MASK_HOST %s is not TLS 1.3 host, this is not recommended" % + config.MASK_HOST) + except ConnectionRefusedError: + print_err("The MASK_HOST %s is refusing connections, this is not recommended" % + config.MASK_HOST) + except (TimeoutError, asyncio.TimeoutError): + print_err("Got timeout while getting TLS handshake from MASK_HOST %s" % + config.MASK_HOST) + except Exception as E: + print_err("Failed to connect to MASK_HOST %s: %s" % ( + config.MASK_HOST, E)) + + await asyncio.sleep(config.GET_CERT_LEN_PERIOD) + + +async def get_srv_time(): + TIME_SYNC_ADDR = "https://core.telegram.org/getProxySecret" + MAX_TIME_SKEW = 30 + + global disable_middle_proxy + global is_time_skewed + + want_to_reenable_advertising = False + while True: + try: + headers, secret = await make_https_req(TIME_SYNC_ADDR) + + for line in headers.split(b"\r\n"): + if not line.startswith(b"Date: "): + continue + line = line[len("Date: "):].decode() + srv_time = datetime.datetime.strptime(line, "%a, %d %b %Y %H:%M:%S %Z") + srv_time = srv_time.replace(tzinfo=datetime.timezone.utc) + now_time = datetime.datetime.now(datetime.timezone.utc) + is_time_skewed = (now_time-srv_time).total_seconds() > MAX_TIME_SKEW + if is_time_skewed and config.USE_MIDDLE_PROXY and not disable_middle_proxy: + print_err("Time skew detected, please set the clock") + print_err("Server time:", srv_time, "your time:", now_time) + print_err("Disabling advertising to continue serving") + print_err("Putting down the shields against replay attacks") + + disable_middle_proxy = True + want_to_reenable_advertising = True + elif not is_time_skewed and want_to_reenable_advertising: + print_err("Time is ok, reenabling advertising") + disable_middle_proxy = False + want_to_reenable_advertising = False + except Exception as E: + print_err("Error getting server time", E) + + await asyncio.sleep(config.GET_TIME_PERIOD) + + +async def clear_ip_resolving_cache(): + global mask_host_cached_ip + min_sleep = myrandom.randrange(60 - 10, 60 + 10) + max_sleep = myrandom.randrange(120 - 10, 120 + 10) + while True: + mask_host_cached_ip = None + await asyncio.sleep(myrandom.randrange(min_sleep, max_sleep)) + + +async def update_middle_proxy_info(): + async def get_new_proxies(url): + PROXY_REGEXP = re.compile(r"proxy_for\s+(-?\d+)\s+(.+):(\d+)\s*;") + ans = {} + headers, body = await make_https_req(url) + + fields = PROXY_REGEXP.findall(body.decode("utf8")) + if fields: + for dc_idx, host, port in fields: + if host.startswith("[") and host.endswith("]"): + host = host[1:-1] + dc_idx, port = int(dc_idx), int(port) + if dc_idx not in ans: + ans[dc_idx] = [(host, port)] + else: + ans[dc_idx].append((host, port)) + return ans + + PROXY_INFO_ADDR = "https://core.telegram.org/getProxyConfig" + PROXY_INFO_ADDR_V6 = "https://core.telegram.org/getProxyConfigV6" + PROXY_SECRET_ADDR = "https://core.telegram.org/getProxySecret" + + global TG_MIDDLE_PROXIES_V4 + global TG_MIDDLE_PROXIES_V6 + global PROXY_SECRET + + while True: + try: + v4_proxies = await get_new_proxies(PROXY_INFO_ADDR) + if not v4_proxies: + raise Exception("no proxy data") + TG_MIDDLE_PROXIES_V4 = v4_proxies + except Exception as E: + print_err("Error updating middle proxy list:", E) + + try: + v6_proxies = await get_new_proxies(PROXY_INFO_ADDR_V6) + if not v6_proxies: + raise Exception("no proxy data (ipv6)") + TG_MIDDLE_PROXIES_V6 = v6_proxies + except Exception as E: + print_err("Error updating middle proxy list for IPv6:", E) + + try: + headers, secret = await make_https_req(PROXY_SECRET_ADDR) + if not secret: + raise Exception("no secret") + if secret != PROXY_SECRET: + PROXY_SECRET = secret + print_err("Middle proxy secret updated") + except Exception as E: + print_err("Error updating middle proxy secret, using old", E) + + await asyncio.sleep(config.PROXY_INFO_UPDATE_PERIOD) + + +def init_ip_info(): + global my_ip_info + global disable_middle_proxy + + def get_ip_from_url(url): + TIMEOUT = 5 + try: + with urllib.request.urlopen(url, timeout=TIMEOUT) as f: + if f.status != 200: + raise Exception("Invalid status code") + return f.read().decode().strip() + except Exception: + return None + + IPV4_URL1 = "http://v4.ident.me/" + IPV4_URL2 = "http://ipv4.icanhazip.com/" + + IPV6_URL1 = "http://v6.ident.me/" + IPV6_URL2 = "http://ipv6.icanhazip.com/" + + my_ip_info["ipv4"] = get_ip_from_url(IPV4_URL1) or get_ip_from_url(IPV4_URL2) + my_ip_info["ipv6"] = get_ip_from_url(IPV6_URL1) or get_ip_from_url(IPV6_URL2) + + # the server can return ipv4 address instead of ipv6 + if my_ip_info["ipv6"] and ":" not in my_ip_info["ipv6"]: + my_ip_info["ipv6"] = None + + if my_ip_info["ipv6"] and (config.PREFER_IPV6 or not my_ip_info["ipv4"]): + print_err("IPv6 found, using it for external communication") + + if config.USE_MIDDLE_PROXY: + if not my_ip_info["ipv4"] and not my_ip_info["ipv6"]: + print_err("Failed to determine your ip, advertising disabled") + disable_middle_proxy = True + + +def print_tg_info(): + global my_ip_info + global proxy_links + + print_default_warning = False + + if config.PORT == 3256: + print("The default port 3256 is used, this is not recommended", flush=True) + if not config.MODES["classic"] and not config.MODES["secure"]: + print("Since you have TLS only mode enabled the best port is 443", flush=True) + print_default_warning = True + + if not config.MY_DOMAIN: + ip_addrs = [ip for ip in my_ip_info.values() if ip] + if not ip_addrs: + ip_addrs = ["YOUR_IP"] + else: + ip_addrs = [config.MY_DOMAIN] + + proxy_links = [] + + for user, secret in sorted(config.USERS.items(), key=lambda x: x[0]): + for ip in ip_addrs: + if config.MODES["classic"]: + params = {"server": ip, "port": config.PORT, "secret": secret} + params_encoded = urllib.parse.urlencode(params, safe=':') + classic_link = "tg://proxy?{}".format(params_encoded) + proxy_links.append({"user": user, "link": classic_link}) + print("{}: {}".format(user, classic_link), flush=True) + + if config.MODES["secure"]: + params = {"server": ip, "port": config.PORT, "secret": "dd" + secret} + params_encoded = urllib.parse.urlencode(params, safe=':') + dd_link = "tg://proxy?{}".format(params_encoded) + proxy_links.append({"user": user, "link": dd_link}) + print("{}: {}".format(user, dd_link), flush=True) + + if config.MODES["tls"]: + tls_secret = "ee" + secret + config.TLS_DOMAIN.encode().hex() + # the base64 links is buggy on ios + # tls_secret = bytes.fromhex("ee" + secret) + config.TLS_DOMAIN.encode() + # tls_secret_base64 = base64.urlsafe_b64encode(tls_secret) + params = {"server": ip, "port": config.PORT, "secret": tls_secret} + params_encoded = urllib.parse.urlencode(params, safe=':') + tls_link = "tg://proxy?{}".format(params_encoded) + proxy_links.append({"user": user, "link": tls_link}) + print("{}: {}".format(user, tls_link), flush=True) + + if secret in ["00000000000000000000000000000000", "0123456789abcdef0123456789abcdef", + "00000000000000000000000000000001"]: + msg = "The default secret {} is used, this is not recommended".format(secret) + print(msg, flush=True) + random_secret = "".join(myrandom.choice("0123456789abcdef") for i in range(32)) + print("You can change it to this random secret:", random_secret, flush=True) + print_default_warning = True + + if config.TLS_DOMAIN == "www.google.com": + print("The default TLS_DOMAIN www.google.com is used, this is not recommended", flush=True) + msg = "You should use random existing domain instead, bad clients are proxied there" + print(msg, flush=True) + print_default_warning = True + + if print_default_warning: + print_err("Warning: one or more default settings detected") + + +def setup_files_limit(): + try: + import resource + soft_fd_limit, hard_fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE) + resource.setrlimit(resource.RLIMIT_NOFILE, (hard_fd_limit, hard_fd_limit)) + except (ValueError, OSError): + print("Failed to increase the limit of opened files", flush=True, file=sys.stderr) + except ImportError: + pass + + +def setup_asyncio(): + # get rid of annoying "socket.send() raised exception" log messages + asyncio.constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES = 100 + + +def setup_signals(): + if hasattr(signal, 'SIGUSR1'): + def debug_signal(signum, frame): + import pdb + pdb.set_trace() + + signal.signal(signal.SIGUSR1, debug_signal) + + if hasattr(signal, 'SIGUSR2'): + def reload_signal(signum, frame): + init_config() + ensure_users_in_user_stats() + apply_upstream_proxy_settings() + print("Config reloaded", flush=True, file=sys.stderr) + print_tg_info() + + signal.signal(signal.SIGUSR2, reload_signal) + + +def try_setup_uvloop(): + if config.SOCKS5_HOST and config.SOCKS5_PORT: + # socks mode is not compatible with uvloop + return + try: + import uvloop + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + print_err("Found uvloop, using it for optimal performance") + except ImportError: + pass + + +def remove_unix_socket(path): + try: + if stat.S_ISSOCK(os.stat(path).st_mode): + os.unlink(path) + except (FileNotFoundError, NotADirectoryError): + pass + + +def loop_exception_handler(loop, context): + exception = context.get("exception") + transport = context.get("transport") + if exception: + if isinstance(exception, TimeoutError): + if transport: + transport.abort() + return + if isinstance(exception, OSError): + IGNORE_ERRNO = { + 10038, # operation on non-socket on Windows, likely because fd == -1 + 121, # the semaphore timeout period has expired on Windows + } + + FORCE_CLOSE_ERRNO = { + 113, # no route to host + + } + if exception.errno in IGNORE_ERRNO: + return + elif exception.errno in FORCE_CLOSE_ERRNO: + if transport: + transport.abort() + return + + loop.default_exception_handler(context) + + +def create_servers(loop): + servers = [] + + reuse_port = hasattr(socket, "SO_REUSEPORT") + has_unix = hasattr(socket, "AF_UNIX") + + if config.LISTEN_ADDR_IPV4: + task = asyncio.start_server(handle_client_wrapper, config.LISTEN_ADDR_IPV4, config.PORT, + limit=get_to_tg_bufsize(), reuse_port=reuse_port) + servers.append(loop.run_until_complete(task)) + + if config.LISTEN_ADDR_IPV6 and socket.has_ipv6: + task = asyncio.start_server(handle_client_wrapper, config.LISTEN_ADDR_IPV6, config.PORT, + limit=get_to_tg_bufsize(), reuse_port=reuse_port) + servers.append(loop.run_until_complete(task)) + + if config.LISTEN_UNIX_SOCK and has_unix: + remove_unix_socket(config.LISTEN_UNIX_SOCK) + task = asyncio.start_unix_server(handle_client_wrapper, config.LISTEN_UNIX_SOCK, + limit=get_to_tg_bufsize()) + servers.append(loop.run_until_complete(task)) + os.chmod(config.LISTEN_UNIX_SOCK, 0o666) + + if config.METRICS_PORT is not None: + if config.METRICS_LISTEN_ADDR_IPV4: + task = asyncio.start_server(handle_metrics, config.METRICS_LISTEN_ADDR_IPV4, + config.METRICS_PORT) + servers.append(loop.run_until_complete(task)) + if config.METRICS_LISTEN_ADDR_IPV6 and socket.has_ipv6: + task = asyncio.start_server(handle_metrics, config.METRICS_LISTEN_ADDR_IPV6, + config.METRICS_PORT) + servers.append(loop.run_until_complete(task)) + + return servers + + +def create_utilitary_tasks(loop): + tasks = [] + + stats_printer_task = asyncio.Task(stats_printer(), loop=loop) + tasks.append(stats_printer_task) + + if config.USE_MIDDLE_PROXY: + middle_proxy_updater_task = asyncio.Task(update_middle_proxy_info(), loop=loop) + tasks.append(middle_proxy_updater_task) + + if config.GET_TIME_PERIOD: + time_get_task = asyncio.Task(get_srv_time(), loop=loop) + tasks.append(time_get_task) + + get_cert_len_task = asyncio.Task(get_mask_host_cert_len(), loop=loop) + tasks.append(get_cert_len_task) + + clear_resolving_cache_task = asyncio.Task(clear_ip_resolving_cache(), loop=loop) + tasks.append(clear_resolving_cache_task) + + return tasks + + +def main(): + init_config() + ensure_users_in_user_stats() + apply_upstream_proxy_settings() + init_ip_info() + print_tg_info() + + setup_asyncio() + setup_files_limit() + setup_signals() + try_setup_uvloop() + + init_proxy_start_time() + + if sys.platform == "win32": + loop = asyncio.ProactorEventLoop() + else: + loop = asyncio.new_event_loop() + + asyncio.set_event_loop(loop) + loop.set_exception_handler(loop_exception_handler) + + utilitary_tasks = create_utilitary_tasks(loop) + for task in utilitary_tasks: + asyncio.ensure_future(task) + + servers = create_servers(loop) + + try: + loop.run_forever() + except KeyboardInterrupt: + pass + + if hasattr(asyncio, "all_tasks"): + tasks = asyncio.all_tasks(loop) + else: + # for compatibility with Python 3.6 + tasks = asyncio.Task.all_tasks(loop) + + for task in tasks: + task.cancel() + + for server in servers: + server.close() + loop.run_until_complete(server.wait_closed()) + + has_unix = hasattr(socket, "AF_UNIX") + + if config.LISTEN_UNIX_SOCK and has_unix: + remove_unix_socket(config.LISTEN_UNIX_SOCK) + + loop.close() + + +if __name__ == "__main__": + main() diff --git a/pyaes/__init__.py b/pyaes/__init__.py new file mode 100644 index 0000000..587acd3 --- /dev/null +++ b/pyaes/__init__.py @@ -0,0 +1,6 @@ +VERSION = [1, 3, 0] + +from .aes import AES, AESModeOfOperationCTR, AESModeOfOperationCBC, AESModeOfOperationCFB, AESModeOfOperationECB, \ + AESModeOfOperationOFB, AESModesOfOperation, Counter +from .blockfeeder import decrypt_stream, Decrypter, encrypt_stream, Encrypter +from .blockfeeder import PADDING_NONE, PADDING_DEFAULT diff --git a/pyaes/aes.py b/pyaes/aes.py new file mode 100644 index 0000000..c6e8bc0 --- /dev/null +++ b/pyaes/aes.py @@ -0,0 +1,589 @@ +# The MIT License (MIT) +# +# Copyright (c) 2014 Richard Moore +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# This is a pure-Python implementation of the AES algorithm and AES common +# modes of operation. + +# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard + +# Honestly, the best description of the modes of operations are the wonderful +# diagrams on Wikipedia. They explain in moments what my words could never +# achieve. Hence the inline documentation here is sparer than I'd prefer. +# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation + +# Also useful, PyCrypto, a crypto library implemented in C with Python bindings: +# https://www.dlitz.net/software/pycrypto/ + + +# Supported key sizes: +# 128-bit +# 192-bit +# 256-bit + + +# Supported modes of operation: +# ECB - Electronic Codebook +# CBC - Cipher-Block Chaining +# CFB - Cipher Feedback +# OFB - Output Feedback +# CTR - Counter + + +# See the README.md for API details and general information. + + +import copy +import struct + +__all__ = ["AES", "AESModeOfOperationCTR", "AESModeOfOperationCBC", "AESModeOfOperationCFB", + "AESModeOfOperationECB", "AESModeOfOperationOFB", "AESModesOfOperation", "Counter"] + + +def _compact_word(word): + return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] + +def _string_to_bytes(text): + return list(ord(c) for c in text) + +def _bytes_to_string(binary): + return "".join(chr(b) for b in binary) + +def _concat_list(a, b): + return a + b + + +# Python 3 compatibility +try: + xrange +except Exception: + xrange = range + + # Python 3 supports bytes, which is already an array of integers + def _string_to_bytes(text): + if isinstance(text, bytes): + return text + return [ord(c) for c in text] + + # In Python 3, we return bytes + def _bytes_to_string(binary): + return bytes(binary) + + # Python 3 cannot concatenate a list onto a bytes, so we bytes-ify it first + def _concat_list(a, b): + return a + bytes(b) + + +# Based *largely* on the Rijndael implementation +# See: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf +class AES(object): + '''Encapsulates the AES block cipher. + + You generally should not need this. Use the AESModeOfOperation classes + below instead.''' + + # Number of rounds by keysize + number_of_rounds = {16: 10, 24: 12, 32: 14} + + # Round constant words + rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ] + + # S-box and Inverse S-box (S is for Substitution) + S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ] + Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ] + + # Transformations for encryption + T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ] + T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ] + T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ] + T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ] + + # Transformations for decryption + T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ] + T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ] + T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ] + T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ] + + # Transformations for decryption key expansion + U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ] + U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ] + U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ] + U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ] + + def __init__(self, key): + + if len(key) not in (16, 24, 32): + raise ValueError('Invalid key size') + + rounds = self.number_of_rounds[len(key)] + + # Encryption round keys + self._Ke = [[0] * 4 for i in xrange(rounds + 1)] + + # Decryption round keys + self._Kd = [[0] * 4 for i in xrange(rounds + 1)] + + round_key_count = (rounds + 1) * 4 + KC = len(key) // 4 + + # Convert the key into ints + tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ] + + # Copy values into round key arrays + for i in xrange(0, KC): + self._Ke[i // 4][i % 4] = tk[i] + self._Kd[rounds - (i // 4)][i % 4] = tk[i] + + # Key expansion (fips-197 section 5.2) + rconpointer = 0 + t = KC + while t < round_key_count: + + tt = tk[KC - 1] + tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^ + (self.S[(tt >> 8) & 0xFF] << 16) ^ + (self.S[ tt & 0xFF] << 8) ^ + self.S[(tt >> 24) & 0xFF] ^ + (self.rcon[rconpointer] << 24)) + rconpointer += 1 + + if KC != 8: + for i in xrange(1, KC): + tk[i] ^= tk[i - 1] + + # Key expansion for 256-bit keys is "slightly different" (fips-197) + else: + for i in xrange(1, KC // 2): + tk[i] ^= tk[i - 1] + tt = tk[KC // 2 - 1] + + tk[KC // 2] ^= (self.S[ tt & 0xFF] ^ + (self.S[(tt >> 8) & 0xFF] << 8) ^ + (self.S[(tt >> 16) & 0xFF] << 16) ^ + (self.S[(tt >> 24) & 0xFF] << 24)) + + for i in xrange(KC // 2 + 1, KC): + tk[i] ^= tk[i - 1] + + # Copy values into round key arrays + j = 0 + while j < KC and t < round_key_count: + self._Ke[t // 4][t % 4] = tk[j] + self._Kd[rounds - (t // 4)][t % 4] = tk[j] + j += 1 + t += 1 + + # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3) + for r in xrange(1, rounds): + for j in xrange(0, 4): + tt = self._Kd[r][j] + self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^ + self.U2[(tt >> 16) & 0xFF] ^ + self.U3[(tt >> 8) & 0xFF] ^ + self.U4[ tt & 0xFF]) + + def encrypt(self, plaintext): + 'Encrypt a block of plain text using the AES block cipher.' + + if len(plaintext) != 16: + raise ValueError('wrong block length') + + rounds = len(self._Ke) - 1 + (s1, s2, s3) = [1, 2, 3] + a = [0, 0, 0, 0] + + # Convert plaintext to (ints ^ key) + t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(0, 4): + a[i] = (self.T1[(t[ i ] >> 24) & 0xFF] ^ + self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^ + self.T3[(t[(i + s2) % 4] >> 8) & 0xFF] ^ + self.T4[ t[(i + s3) % 4] & 0xFF] ^ + self._Ke[r][i]) + t = copy.copy(a) + + # The last round is special + result = [ ] + for i in xrange(0, 4): + tt = self._Ke[rounds][i] + result.append((self.S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((self.S[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((self.S[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF) + + return result + + def decrypt(self, ciphertext): + 'Decrypt a block of cipher text using the AES block cipher.' + + if len(ciphertext) != 16: + raise ValueError('wrong block length') + + rounds = len(self._Kd) - 1 + (s1, s2, s3) = [3, 2, 1] + a = [0, 0, 0, 0] + + # Convert ciphertext to (ints ^ key) + t = [(_compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in xrange(0, 4)] + + # Apply round transforms + for r in xrange(1, rounds): + for i in xrange(0, 4): + a[i] = (self.T5[(t[ i ] >> 24) & 0xFF] ^ + self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^ + self.T7[(t[(i + s2) % 4] >> 8) & 0xFF] ^ + self.T8[ t[(i + s3) % 4] & 0xFF] ^ + self._Kd[r][i]) + t = copy.copy(a) + + # The last round is special + result = [ ] + for i in xrange(0, 4): + tt = self._Kd[rounds][i] + result.append((self.Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((self.Si[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((self.Si[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF) + + return result + + +class Counter(object): + '''A counter object for the Counter (CTR) mode of operation. + + To create a custom counter, you can usually just override the + increment method.''' + + def __init__(self, initial_value = 1): + + # Convert the value into an array of bytes long + self._counter = [ ((initial_value >> i) % 256) for i in xrange(128 - 8, -1, -8) ] + + value = property(lambda s: s._counter) + + def increment(self): + '''Increment the counter (overflow rolls back to 0).''' + + for i in xrange(len(self._counter) - 1, -1, -1): + self._counter[i] += 1 + + if self._counter[i] < 256: break + + # Carry the one + self._counter[i] = 0 + + # Overflow + else: + self._counter = [ 0 ] * len(self._counter) + + +class AESBlockModeOfOperation(object): + '''Super-class for AES modes of operation that require blocks.''' + def __init__(self, key): + self._aes = AES(key) + + def decrypt(self, ciphertext): + raise Exception('not implemented') + + def encrypt(self, plaintext): + raise Exception('not implemented') + + +class AESStreamModeOfOperation(AESBlockModeOfOperation): + '''Super-class for AES modes of operation that are stream-ciphers.''' + +class AESSegmentModeOfOperation(AESStreamModeOfOperation): + '''Super-class for AES modes of operation that segment data.''' + + segment_bytes = 16 + + + +class AESModeOfOperationECB(AESBlockModeOfOperation): + '''AES Electronic Codebook Mode of Operation. + + o Block-cipher, so data must be padded to 16 byte boundaries + + Security Notes: + o This mode is not recommended + o Any two identical blocks produce identical encrypted values, + exposing data patterns. (See the image of Tux on wikipedia) + + Also see: + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.1''' + + + name = "Electronic Codebook (ECB)" + + def encrypt(self, plaintext): + if len(plaintext) != 16: + raise ValueError('plaintext block must be 16 bytes') + + plaintext = _string_to_bytes(plaintext) + return _bytes_to_string(self._aes.encrypt(plaintext)) + + def decrypt(self, ciphertext): + if len(ciphertext) != 16: + raise ValueError('ciphertext block must be 16 bytes') + + ciphertext = _string_to_bytes(ciphertext) + return _bytes_to_string(self._aes.decrypt(ciphertext)) + + + +class AESModeOfOperationCBC(AESBlockModeOfOperation): + '''AES Cipher-Block Chaining Mode of Operation. + + o The Initialization Vector (IV) + o Block-cipher, so data must be padded to 16 byte boundaries + o An incorrect initialization vector will only cause the first + block to be corrupt; all other blocks will be intact + o A corrupt bit in the cipher text will cause a block to be + corrupted, and the next block to be inverted, but all other + blocks will be intact. + + Security Notes: + o This method (and CTR) ARE recommended. + + Also see: + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.2''' + + + name = "Cipher-Block Chaining (CBC)" + + def __init__(self, key, iv = None): + if iv is None: + self._last_cipherblock = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._last_cipherblock = _string_to_bytes(iv) + + AESBlockModeOfOperation.__init__(self, key) + + def encrypt(self, plaintext): + if len(plaintext) != 16: + raise ValueError('plaintext block must be 16 bytes') + + plaintext = _string_to_bytes(plaintext) + precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ] + self._last_cipherblock = self._aes.encrypt(precipherblock) + + return _bytes_to_string(self._last_cipherblock) + + def decrypt(self, ciphertext): + if len(ciphertext) != 16: + raise ValueError('ciphertext block must be 16 bytes') + + cipherblock = _string_to_bytes(ciphertext) + plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ] + self._last_cipherblock = cipherblock + + return _bytes_to_string(plaintext) + + + +class AESModeOfOperationCFB(AESSegmentModeOfOperation): + '''AES Cipher Feedback Mode of Operation. + + o A stream-cipher, so input does not need to be padded to blocks, + but does need to be padded to segment_size + + Also see: + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_.28CFB.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.3''' + + + name = "Cipher Feedback (CFB)" + + def __init__(self, key, iv, segment_size = 1): + if segment_size == 0: segment_size = 1 + + if iv is None: + self._shift_register = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._shift_register = _string_to_bytes(iv) + + self._segment_bytes = segment_size + + AESBlockModeOfOperation.__init__(self, key) + + segment_bytes = property(lambda s: s._segment_bytes) + + def encrypt(self, plaintext): + if len(plaintext) % self._segment_bytes != 0: + raise ValueError('plaintext block must be a multiple of segment_size') + + plaintext = _string_to_bytes(plaintext) + + # Break block into segments + encrypted = [ ] + for i in xrange(0, len(plaintext), self._segment_bytes): + plaintext_segment = plaintext[i: i + self._segment_bytes] + xor_segment = self._aes.encrypt(self._shift_register)[:len(plaintext_segment)] + cipher_segment = [ (p ^ x) for (p, x) in zip(plaintext_segment, xor_segment) ] + + # Shift the top bits out and the ciphertext in + self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment) + + encrypted.extend(cipher_segment) + + return _bytes_to_string(encrypted) + + def decrypt(self, ciphertext): + if len(ciphertext) % self._segment_bytes != 0: + raise ValueError('ciphertext block must be a multiple of segment_size') + + ciphertext = _string_to_bytes(ciphertext) + + # Break block into segments + decrypted = [ ] + for i in xrange(0, len(ciphertext), self._segment_bytes): + cipher_segment = ciphertext[i: i + self._segment_bytes] + xor_segment = self._aes.encrypt(self._shift_register)[:len(cipher_segment)] + plaintext_segment = [ (p ^ x) for (p, x) in zip(cipher_segment, xor_segment) ] + + # Shift the top bits out and the ciphertext in + self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment) + + decrypted.extend(plaintext_segment) + + return _bytes_to_string(decrypted) + + + +class AESModeOfOperationOFB(AESStreamModeOfOperation): + '''AES Output Feedback Mode of Operation. + + o A stream-cipher, so input does not need to be padded to blocks, + allowing arbitrary length data. + o A bit twiddled in the cipher text, twiddles the same bit in the + same bit in the plain text, which can be useful for error + correction techniques. + + Also see: + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_.28OFB.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.4''' + + + name = "Output Feedback (OFB)" + + def __init__(self, key, iv = None): + if iv is None: + self._last_precipherblock = [ 0 ] * 16 + elif len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._last_precipherblock = _string_to_bytes(iv) + + self._remaining_block = [ ] + + AESBlockModeOfOperation.__init__(self, key) + + def encrypt(self, plaintext): + encrypted = [ ] + for p in _string_to_bytes(plaintext): + if len(self._remaining_block) == 0: + self._remaining_block = self._aes.encrypt(self._last_precipherblock) + self._last_precipherblock = [ ] + precipherbyte = self._remaining_block.pop(0) + self._last_precipherblock.append(precipherbyte) + cipherbyte = p ^ precipherbyte + encrypted.append(cipherbyte) + + return _bytes_to_string(encrypted) + + def decrypt(self, ciphertext): + # AES-OFB is symetric + return self.encrypt(ciphertext) + + + +class AESModeOfOperationCTR(AESStreamModeOfOperation): + '''AES Counter Mode of Operation. + + o A stream-cipher, so input does not need to be padded to blocks, + allowing arbitrary length data. + o The counter must be the same size as the key size (ie. len(key)) + o Each block independant of the other, so a corrupt byte will not + damage future blocks. + o Each block has a uniue counter value associated with it, which + contributes to the encrypted value, so no data patterns are + leaked. + o Also known as: Counter Mode (CM), Integer Counter Mode (ICM) and + Segmented Integer Counter (SIC + + Security Notes: + o This method (and CBC) ARE recommended. + o Each message block is associated with a counter value which must be + unique for ALL messages with the same key. Otherwise security may be + compromised. + + Also see: + + o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29 + o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.5 + and Appendix B for managing the initial counter''' + + + name = "Counter (CTR)" + + def __init__(self, key, counter = None): + AESBlockModeOfOperation.__init__(self, key) + + if counter is None: + counter = Counter() + + self._counter = counter + self._remaining_counter = [ ] + + def encrypt(self, plaintext): + while len(self._remaining_counter) < len(plaintext): + self._remaining_counter += self._aes.encrypt(self._counter.value) + self._counter.increment() + + plaintext = _string_to_bytes(plaintext) + + encrypted = [ (p ^ c) for (p, c) in zip(plaintext, self._remaining_counter) ] + self._remaining_counter = self._remaining_counter[len(encrypted):] + + return _bytes_to_string(encrypted) + + def decrypt(self, crypttext): + # AES-CTR is symetric + return self.encrypt(crypttext) + + +# Simple lookup table for each mode +AESModesOfOperation = dict( + ctr = AESModeOfOperationCTR, + cbc = AESModeOfOperationCBC, + cfb = AESModeOfOperationCFB, + ecb = AESModeOfOperationECB, + ofb = AESModeOfOperationOFB, +) diff --git a/pyaes/blockfeeder.py b/pyaes/blockfeeder.py new file mode 100644 index 0000000..b9a904d --- /dev/null +++ b/pyaes/blockfeeder.py @@ -0,0 +1,227 @@ +# The MIT License (MIT) +# +# Copyright (c) 2014 Richard Moore +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + +from .aes import AESBlockModeOfOperation, AESSegmentModeOfOperation, AESStreamModeOfOperation +from .util import append_PKCS7_padding, strip_PKCS7_padding, to_bufferable + + +# First we inject three functions to each of the modes of operations +# +# _can_consume(size) +# - Given a size, determine how many bytes could be consumed in +# a single call to either the decrypt or encrypt method +# +# _final_encrypt(data, padding = PADDING_DEFAULT) +# - call and return encrypt on this (last) chunk of data, +# padding as necessary; this will always be at least 16 +# bytes unless the total incoming input was less than 16 +# bytes +# +# _final_decrypt(data, padding = PADDING_DEFAULT) +# - same as _final_encrypt except for decrypt, for +# stripping off padding +# + +PADDING_NONE = 'none' +PADDING_DEFAULT = 'default' + +# @TODO: Ciphertext stealing and explicit PKCS#7 +# PADDING_CIPHERTEXT_STEALING +# PADDING_PKCS7 + +# ECB and CBC are block-only ciphers + +def _block_can_consume(self, size): + if size >= 16: return 16 + return 0 + +# After padding, we may have more than one block +def _block_final_encrypt(self, data, padding = PADDING_DEFAULT): + if padding == PADDING_DEFAULT: + data = append_PKCS7_padding(data) + + elif padding == PADDING_NONE: + if len(data) != 16: + raise Exception('invalid data length for final block') + else: + raise Exception('invalid padding option') + + if len(data) == 32: + return self.encrypt(data[:16]) + self.encrypt(data[16:]) + + return self.encrypt(data) + + +def _block_final_decrypt(self, data, padding = PADDING_DEFAULT): + if padding == PADDING_DEFAULT: + return strip_PKCS7_padding(self.decrypt(data)) + + if padding == PADDING_NONE: + if len(data) != 16: + raise Exception('invalid data length for final block') + return self.decrypt(data) + + raise Exception('invalid padding option') + +AESBlockModeOfOperation._can_consume = _block_can_consume +AESBlockModeOfOperation._final_encrypt = _block_final_encrypt +AESBlockModeOfOperation._final_decrypt = _block_final_decrypt + + + +# CFB is a segment cipher + +def _segment_can_consume(self, size): + return self.segment_bytes * int(size // self.segment_bytes) + +# CFB can handle a non-segment-sized block at the end using the remaining cipherblock +def _segment_final_encrypt(self, data, padding = PADDING_DEFAULT): + if padding != PADDING_DEFAULT: + raise Exception('invalid padding option') + + faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes))) + padded = data + to_bufferable(faux_padding) + return self.encrypt(padded)[:len(data)] + +# CFB can handle a non-segment-sized block at the end using the remaining cipherblock +def _segment_final_decrypt(self, data, padding = PADDING_DEFAULT): + if padding != PADDING_DEFAULT: + raise Exception('invalid padding option') + + faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes))) + padded = data + to_bufferable(faux_padding) + return self.decrypt(padded)[:len(data)] + +AESSegmentModeOfOperation._can_consume = _segment_can_consume +AESSegmentModeOfOperation._final_encrypt = _segment_final_encrypt +AESSegmentModeOfOperation._final_decrypt = _segment_final_decrypt + + + +# OFB and CTR are stream ciphers + +def _stream_can_consume(self, size): + return size + +def _stream_final_encrypt(self, data, padding = PADDING_DEFAULT): + if padding not in [PADDING_NONE, PADDING_DEFAULT]: + raise Exception('invalid padding option') + + return self.encrypt(data) + +def _stream_final_decrypt(self, data, padding = PADDING_DEFAULT): + if padding not in [PADDING_NONE, PADDING_DEFAULT]: + raise Exception('invalid padding option') + + return self.decrypt(data) + +AESStreamModeOfOperation._can_consume = _stream_can_consume +AESStreamModeOfOperation._final_encrypt = _stream_final_encrypt +AESStreamModeOfOperation._final_decrypt = _stream_final_decrypt + + + +class BlockFeeder(object): + '''The super-class for objects to handle chunking a stream of bytes + into the appropriate block size for the underlying mode of operation + and applying (or stripping) padding, as necessary.''' + + def __init__(self, mode, feed, final, padding = PADDING_DEFAULT): + self._mode = mode + self._feed = feed + self._final = final + self._buffer = to_bufferable("") + self._padding = padding + + def feed(self, data = None): + '''Provide bytes to encrypt (or decrypt), returning any bytes + possible from this or any previous calls to feed. + + Call with None or an empty string to flush the mode of + operation and return any final bytes; no further calls to + feed may be made.''' + + if self._buffer is None: + raise ValueError('already finished feeder') + + # Finalize; process the spare bytes we were keeping + if data is None: + result = self._final(self._buffer, self._padding) + self._buffer = None + return result + + self._buffer += to_bufferable(data) + + # We keep 16 bytes around so we can determine padding + result = to_bufferable('') + while len(self._buffer) > 16: + can_consume = self._mode._can_consume(len(self._buffer) - 16) + if can_consume == 0: break + result += self._feed(self._buffer[:can_consume]) + self._buffer = self._buffer[can_consume:] + + return result + + +class Encrypter(BlockFeeder): + 'Accepts bytes of plaintext and returns encrypted ciphertext.' + + def __init__(self, mode, padding = PADDING_DEFAULT): + BlockFeeder.__init__(self, mode, mode.encrypt, mode._final_encrypt, padding) + + +class Decrypter(BlockFeeder): + 'Accepts bytes of ciphertext and returns decrypted plaintext.' + + def __init__(self, mode, padding = PADDING_DEFAULT): + BlockFeeder.__init__(self, mode, mode.decrypt, mode._final_decrypt, padding) + + +# 8kb blocks +BLOCK_SIZE = (1 << 13) + +def _feed_stream(feeder, in_stream, out_stream, block_size = BLOCK_SIZE): + 'Uses feeder to read and convert from in_stream and write to out_stream.' + + while True: + chunk = in_stream.read(block_size) + if not chunk: + break + converted = feeder.feed(chunk) + out_stream.write(converted) + converted = feeder.feed() + out_stream.write(converted) + + +def encrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT): + 'Encrypts a stream of bytes from in_stream to out_stream using mode.' + + encrypter = Encrypter(mode, padding = padding) + _feed_stream(encrypter, in_stream, out_stream, block_size) + + +def decrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT): + 'Decrypts a stream of bytes from in_stream to out_stream using mode.' + + decrypter = Decrypter(mode, padding = padding) + _feed_stream(decrypter, in_stream, out_stream, block_size) diff --git a/pyaes/util.py b/pyaes/util.py new file mode 100644 index 0000000..081a375 --- /dev/null +++ b/pyaes/util.py @@ -0,0 +1,60 @@ +# The MIT License (MIT) +# +# Copyright (c) 2014 Richard Moore +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Why to_bufferable? +# Python 3 is very different from Python 2.x when it comes to strings of text +# and strings of bytes; in Python 3, strings of bytes do not exist, instead to +# represent arbitrary binary data, we must use the "bytes" object. This method +# ensures the object behaves as we need it to. + +def to_bufferable(binary): + return binary + +def _get_byte(c): + return ord(c) + +try: + xrange +except: + + def to_bufferable(binary): + if isinstance(binary, bytes): + return binary + return bytes(ord(b) for b in binary) + + def _get_byte(c): + return c + +def append_PKCS7_padding(data): + pad = 16 - (len(data) % 16) + return data + to_bufferable(chr(pad) * pad) + +def strip_PKCS7_padding(data): + if len(data) % 16 != 0: + raise ValueError("invalid length") + + pad = _get_byte(data[-1]) + + if pad > 16: + raise ValueError("invalid padding byte") + + return data[:-pad]