diff --git a/lab5/.gitignore b/lab5/.gitignore new file mode 100644 index 0000000..d0e6d8b --- /dev/null +++ b/lab5/.gitignore @@ -0,0 +1 @@ +key.txt \ No newline at end of file diff --git a/lab5/.python-version b/lab5/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/lab5/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/lab5/README.md b/lab5/README.md new file mode 100644 index 0000000..8e3ed29 --- /dev/null +++ b/lab5/README.md @@ -0,0 +1,75 @@ +# Практическая работа №5 — Конфиденциальный обмен сообщениями + +Клиент-серверное приложение для обмена сообщениями по TCP с поддержкой +шифрования 3DES-CBC (с затравкой), контроля целостности SHA-256 и +аутентификации отправителя HMAC-SHA256. + +## Установка + +```bash +uv sync +``` + +## Ключ шифрования + +Ключ хранится в текстовом файле (не менее 32 символов). Права на файл — только +чтение владельцем (`600`). Программа проверяет права при запуске и отказывается +работать, если файл доступен группе или другим пользователям. + +```bash +openssl rand -hex 32 > key.txt +chmod 600 key.txt +``` + +## Запуск + +### Сервер + +```bash +uv run main.py server --port 9000 # без шифрования +uv run main.py server --port 9000 --encrypt # 3DES-CBC +uv run main.py server --port 9000 --encrypt --integrity # + SHA-256 +uv run main.py server --port 9000 --encrypt --integrity --test-integrity # отправка с повреждённым хэшем +uv run main.py server --port 9000 --encrypt --hmac # 3DES + HMAC-SHA256 +uv run main.py server --port 9000 --hmac # только HMAC (без шифрования) +uv run main.py server --port 9000 --key /path/to/key.txt --encrypt # другой файл ключа +``` + +### Клиент + +```bash +uv run main.py client 127.0.0.1 9000 +uv run main.py client 127.0.0.1 9000 --encrypt +uv run main.py client 127.0.0.1 9000 --encrypt --integrity +uv run main.py client 127.0.0.1 9000 --encrypt --integrity --test-integrity +uv run main.py client 127.0.0.1 9000 --encrypt --hmac +uv run main.py client 127.0.0.1 9000 --encrypt --hmac --test-integrity +``` + +## Режимы работы + +| Флаг | Описание | +|------|----------| +| *(без флагов)* | Открытый текст, без проверки целостности | +| `--encrypt` | Шифрование 3DES-CBC с затравкой (соль + IV генерируются случайно для каждого сообщения) | +| `--integrity` | Контроль целостности — к сообщению прикладывается SHA-256 хэш открытого текста | +| `--hmac` | HMAC-SHA256 — контроль целостности + аутентификация отправителя (требует ключ, несовместим с `--integrity`) | +| `--test-integrity` | Режим тестирования: отправляется заведомо некорректный хэш/HMAC (требует `--integrity` или `--hmac`) | + +## Анализ трафика с помощью tcpdump + +Для наблюдения за передаваемыми данными удобно использовать `tcpdump`. +При работе на одной машине (loopback): + +```bash +sudo tcpdump -i lo -X -n tcp port 9000 +``` + +При работе по сети (замените `eth0` на имя сетевого интерфейса): + +```bash +sudo tcpdump -i eth0 -X -n tcp port 9000 +``` + +- **Без шифрования** — в дампе видны сообщения открытым текстом (JSON с полем `"data": "текст сообщения"`). +- **С шифрованием** — в дампе видны только шифротекст, соль и IV в hex; читаемый текст отсутствует. diff --git a/lab5/lab5.md b/lab5/lab5.md new file mode 100644 index 0000000..68cf5b6 --- /dev/null +++ b/lab5/lab5.md @@ -0,0 +1,68 @@ +# Практическая работа №5 + +по дисциплине «Защита информации» +**Тема работы:** «Разработка клиент-серверного приложения для конфиденциального обмена сообщениями» +**Преподаватель:** Силиненко А.В. +**Email:** a_silinenko@mail.ru + +## 1. Цель и задачи работы + +### 1.1. Цель работы + +Разработка клиент-серверного приложения для конфиденциального обмена сообщениями с использованием шифрования и контроля целостности. + +### 1.2. Задачи работы + +- получение базовых знаний по использованию криптографических функций библиотеки `crypto` в ОС Linux или другой библиотеки, обеспечивающей реализацию требований задания; +- разработка клиент-серверного приложения для обмена шифрованными сообщениями с контролем целостности; +- проверка работы приложения в различных режимах использования с помощью программы анализа трафика. + +## 2. Требования к приложению + +### 2.1. Общие требования + +Разработать приложение типа «клиент-сервер», позволяющее обмениваться короткими сообщениями между клиентской и серверной частями приложения с обеспечением шифрования и контроля целостности. + +### 2.2. Общие требования + +- протокол взаимодействия: TCP; +- длина сообщения: не более 80 символов; +- полнодуплексный обмен сообщениями: возможность одновременного приема и передачи сообщений; +- IP-адрес и порт серверной части передается клиентской части приложения в качестве параметров запуска в командной строке; +- приложение должно быть способно функционировать как при запуске клиентской и серверной частей приложения на одном компьютере, так и на разных сетевых узлах; +- язык написания программы: любой. + +### 2.3. Требования к функции шифрования + +- приложение должно поддерживать функцию шифрования сообщений на основе симметричного шифрования с использованием библиотеки `crypto`, входящей в API `openssl`, или другой аналогичной библиотеки; +- алгоритм симметричного шифрования: согласно индивидуальному заданию; +В моём варианте это 3DES +- ключ шифрования длиной не менее 32 символов задается в файле; +- права доступа к файлу: чтение только владельцем; +- использование опции шифрования задается ключом командной строки при запуске программы. + +### 2.4. Требования к функции контроля целостности + +- приложение должно поддерживать функцию контроля целостности на основе подсчета хэш-значения передаваемого сообщения; +- алгоритм вычисления хэш-значения - в соответствии с индивидуальным заданием (см. практическую работу №2); +- использование опции контроля целостности задается ключом командной строки при запуске программы; +- предусмотреть режим тестирования, в котором при включенной опции контроля целостности отправляются сообщения с некорректным хэш-значением. + +### 2.5. Необязательные требования + +- реализовать шифрование с «затравкой» («солью»), обеспечивающей разные шифрованные сообщения при одном и том же открытом тексте; +- реализовать для разработанного приложения опцию выработки и использования сессионного ключа на основе алгоритма Диффи-Хеллмана вместо заранее заданного ключа. + +## 3. Требования к отчету + +### 3.1. В разделе отчета, посвященном данной работе, должны быть приведены + +- актуальность темы работы в контексте курса; +- цели и задачи работы; +- краткое описание особенностей используемого алгоритма симметричного шифрования, а также шифрования «с затравкой» и алгоритма Диффи-Хеллмана, если реализованы соответствующие требования; +- команды компиляции и сборки исполняемых модулей приложения; +- описание процедуры проверки работы приложения с примерами, включая выводы программы анализа трафика, подтверждающие реализацию требований передачи сообщений без шифрования, с шифрованием, с шифрованием и контролем целостности; +- описание проверки опции контроля целостности, в которой отправляются сообщения с некорректным хэш-значением; +- описание проверки шифрования «с затравкой» и алгоритма Диффи-Хеллмана, если реализованы соответствующие требования; +- текст разработанного приложения (клиентской и серверной частей) с комментариями - в приложении к отчету; +- выводы по проделанной работе. diff --git a/lab5/main.py b/lab5/main.py new file mode 100644 index 0000000..066562b --- /dev/null +++ b/lab5/main.py @@ -0,0 +1,365 @@ +from __future__ import annotations + +import argparse +import hashlib +import hmac +import json +import os +import socket +import stat +import struct +import sys +import threading + +from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES +from cryptography.hazmat.primitives.ciphers import Cipher, modes +from cryptography.hazmat.primitives import padding as sym_padding + +MAX_MSG_LEN = 80 +DEFAULT_KEY_FILE = "key.txt" + + +def check_key_permissions(path: str) -> None: + st = os.stat(path) + mode = st.st_mode + bad = stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH + if mode & bad: + current = oct(mode & 0o777) + print(f"[!] Key file permissions too open ({current}): {path}") + print(f" Run: chmod 600 {path}") + sys.exit(1) + + +def load_key(path: str) -> str: + if not os.path.isfile(path): + print(f"[!] Key file not found: {path}") + sys.exit(1) + check_key_permissions(path) + with open(path) as f: + key = f.read().strip() + if len(key) < 32: + print(f"[!] Key must be at least 32 characters (got {len(key)})") + sys.exit(1) + return key + + +def derive_3des_key(file_key: str, salt: bytes) -> bytes: + return hashlib.sha256(file_key.encode() + salt).digest()[:24] + + +def encrypt_message(plaintext: str, file_key: str) -> tuple[bytes, bytes, bytes]: + salt = os.urandom(16) + key = derive_3des_key(file_key, salt) + iv = os.urandom(8) + + padder = sym_padding.PKCS7(64).padder() + padded = padder.update(plaintext.encode()) + padder.finalize() + + encryptor = Cipher(TripleDES(key), modes.CBC(iv)).encryptor() + ct = encryptor.update(padded) + encryptor.finalize() + return salt, iv, ct + + +def decrypt_message(salt: bytes, iv: bytes, ct: bytes, file_key: str) -> str: + key = derive_3des_key(file_key, salt) + decryptor = Cipher(TripleDES(key), modes.CBC(iv)).decryptor() + padded = decryptor.update(ct) + decryptor.finalize() + + unpadder = sym_padding.PKCS7(64).unpadder() + plaintext = unpadder.update(padded) + unpadder.finalize() + return plaintext.decode() + + +def compute_hash(text: str) -> str: + return hashlib.sha256(text.encode()).hexdigest() + + +def compute_hmac(file_key: str, text: str, salt: bytes) -> str: + return hmac.new(file_key.encode(), text.encode() + salt, hashlib.sha256).hexdigest() + + +def recv_exactly(sock: socket.socket, n: int) -> bytes | None: + buf = b"" + while len(buf) < n: + chunk = sock.recv(n - len(buf)) + if not chunk: + return None + buf += chunk + return buf + + +def send_packet(sock: socket.socket, data: bytes) -> None: + sock.sendall(struct.pack("!I", len(data)) + data) + + +def recv_packet(sock: socket.socket) -> dict | None: + header = recv_exactly(sock, 4) + if header is None: + return None + length = struct.unpack("!I", header)[0] + payload = recv_exactly(sock, length) + if payload is None: + return None + return json.loads(payload.decode()) + + +def do_send( + sock: socket.socket, + text: str, + file_key: str | None, + use_encrypt: bool, + use_integrity: bool, + use_hmac: bool, + test_integrity: bool, +) -> None: + msg: dict = {} + print(f" [TX] plaintext: {text}") + + enc_salt: bytes | None = None + if use_encrypt: + assert file_key is not None + salt, iv, ct = encrypt_message(text, file_key) + enc_salt = salt + derived_hex = derive_3des_key(file_key, salt).hex() + msg["encrypted"] = True + msg["salt"] = salt.hex() + msg["iv"] = iv.hex() + msg["data"] = ct.hex() + print(f" [TX] salt: {salt.hex()}") + print(f" [TX] 3DES key: {derived_hex}") + print(f" [TX] IV: {iv.hex()}") + print(f" [TX] ciphertext: {ct.hex()}") + else: + msg["encrypted"] = False + msg["data"] = text + + if use_integrity: + h = compute_hash(text) + if test_integrity: + h = hashlib.sha256(b"CORRUPTED_" + os.urandom(4)).hexdigest() + print(f" [TX] SHA-256: {h} (CORRUPTED!)") + else: + print(f" [TX] SHA-256: {h}") + msg["hash"] = h + + if use_hmac: + assert file_key is not None + hmac_salt = enc_salt if enc_salt is not None else os.urandom(16) + h = compute_hmac(file_key, text, hmac_salt) + msg["hmac_salt"] = hmac_salt.hex() + if test_integrity: + h = hmac.new(b"WRONG_KEY", os.urandom(8), hashlib.sha256).hexdigest() + print(f" [TX] HMAC salt: {hmac_salt.hex()}") + print(f" [TX] HMAC-256: {h} (CORRUPTED!)") + else: + print(f" [TX] HMAC salt: {hmac_salt.hex()}") + print(f" [TX] HMAC-256: {h}") + msg["hmac"] = h + + send_packet(sock, json.dumps(msg).encode()) + print() + + +def do_recv( + msg: dict, + file_key: str | None, +) -> str | None: + encrypted = msg.get("encrypted", False) + + if encrypted: + salt = bytes.fromhex(msg["salt"]) + iv = bytes.fromhex(msg["iv"]) + ct = bytes.fromhex(msg["data"]) + print(f" [RX] ciphertext: {ct.hex()}") + print(f" [RX] salt: {salt.hex()}") + print(f" [RX] IV: {iv.hex()}") + if file_key is None: + print(" [RX] ERROR: message is encrypted but no key loaded") + return None + derived_hex = derive_3des_key(file_key, salt).hex() + print(f" [RX] 3DES key: {derived_hex}") + try: + text = decrypt_message(salt, iv, ct, file_key) + except Exception as e: + print(f" [RX] decryption failed: {e}") + return None + print(f" [RX] decrypted: {text}") + else: + text = msg["data"] + print(f" [RX] plaintext: {text}") + + if "hash" in msg: + received_hash = msg["hash"] + computed_hash = compute_hash(text) + ok = received_hash == computed_hash + print(f" [RX] recv hash: {received_hash}") + print(f" [RX] calc hash: {computed_hash}") + if ok: + print(" [RX] integrity: OK") + else: + print(" [RX] integrity: FAIL - message may have been tampered with!") + + if "hmac" in msg: + hmac_salt = bytes.fromhex(msg["hmac_salt"]) + received_hmac = msg["hmac"] + if file_key is None: + print(" [RX] ERROR: HMAC present but no key loaded") + else: + computed_h = compute_hmac(file_key, text, hmac_salt) + ok = hmac.compare_digest(received_hmac, computed_h) + print(f" [RX] HMAC salt: {hmac_salt.hex()}") + print(f" [RX] recv HMAC: {received_hmac}") + print(f" [RX] calc HMAC: {computed_h}") + if ok: + print(" [RX] HMAC auth: OK - sender verified") + else: + print(" [RX] HMAC auth: FAIL - sender NOT verified!") + + print() + return text + + +def receiver_thread( + sock: socket.socket, + file_key: str | None, + stop_event: threading.Event, +) -> None: + try: + while not stop_event.is_set(): + msg = recv_packet(sock) + if msg is None: + print("\n[*] Connection closed by remote side.") + stop_event.set() + break + do_recv(msg, file_key) + except OSError: + if not stop_event.is_set(): + print("\n[*] Connection lost.") + stop_event.set() + + +def sender_thread( + sock: socket.socket, + file_key: str | None, + use_encrypt: bool, + use_integrity: bool, + use_hmac: bool, + test_integrity: bool, + stop_event: threading.Event, +) -> None: + try: + while not stop_event.is_set(): + try: + text = input() + except EOFError: + stop_event.set() + break + if stop_event.is_set(): + break + if len(text) > MAX_MSG_LEN: + print(f" [!] Message too long ({len(text)} > {MAX_MSG_LEN}), truncated.") + text = text[:MAX_MSG_LEN] + do_send(sock, text, file_key, use_encrypt, use_integrity, use_hmac, test_integrity) + except OSError: + if not stop_event.is_set(): + stop_event.set() + + +def run_session( + sock: socket.socket, + addr: tuple, + file_key: str | None, + use_encrypt: bool, + use_integrity: bool, + use_hmac: bool, + test_integrity: bool, +) -> None: + flags = [] + if use_encrypt: + flags.append("3DES") + if use_integrity: + flags.append("SHA-256") + if use_hmac: + flags.append("HMAC-SHA256") + if test_integrity: + flags.append("test-integrity") + mode_str = ", ".join(flags) if flags else "plaintext" + print(f"[*] Session with {addr[0]}:{addr[1]} [{mode_str}]") + print("[*] Type messages and press Enter to send. Ctrl+C / Ctrl+D to quit.\n") + + stop = threading.Event() + rx = threading.Thread(target=receiver_thread, args=(sock, file_key, stop), daemon=True) + tx = threading.Thread(target=sender_thread, args=(sock, file_key, use_encrypt, use_integrity, use_hmac, test_integrity, stop), daemon=True) + rx.start() + tx.start() + + try: + while not stop.is_set(): + stop.wait(0.5) + except KeyboardInterrupt: + print("\n[*] Interrupted.") + stop.set() + + sock.close() + + +def run_server(args: argparse.Namespace) -> None: + file_key = load_key(args.key) if (args.encrypt or args.hmac) else None + + srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + srv.bind(("0.0.0.0", args.port)) + srv.listen(1) + print(f"[*] Listening on 0.0.0.0:{args.port} ...") + + conn, addr = srv.accept() + print(f"[*] Client connected: {addr[0]}:{addr[1]}") + srv.close() + run_session(conn, addr, file_key, args.encrypt, args.integrity, args.hmac, args.test_integrity) + + +def run_client(args: argparse.Namespace) -> None: + file_key = load_key(args.key) if (args.encrypt or args.hmac) else None + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((args.host, args.port)) + print(f"[*] Connected to {args.host}:{args.port}") + run_session(sock, (args.host, args.port), file_key, args.encrypt, args.integrity, args.hmac, args.test_integrity) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Encrypted messaging (3DES-CBC + SHA-256)") + sub = parser.add_subparsers(dest="role", required=True) + + for name, sp in [("server", sub.add_parser("server", help="Start server")), + ("client", sub.add_parser("client", help="Start client"))]: + if name == "server": + sp.add_argument("--port", type=int, required=True) + else: + sp.add_argument("host") + sp.add_argument("port", type=int) + sp.add_argument("--encrypt", action="store_true", help="Enable 3DES-CBC encryption") + sp.add_argument("--integrity", action="store_true", help="SHA-256 integrity check") + sp.add_argument("--hmac", action="store_true", help="HMAC-SHA256 integrity + authentication") + sp.add_argument("--test-integrity", action="store_true", help="Send corrupted hash/HMAC") + sp.add_argument("--key", default=DEFAULT_KEY_FILE, help="Path to key file (default: key.txt)") + + return parser + + +def main() -> None: + args = build_parser().parse_args() + if args.integrity and args.hmac: + print("[!] --integrity and --hmac are mutually exclusive") + sys.exit(1) + if args.test_integrity and not (args.integrity or args.hmac): + print("[!] --test-integrity requires --integrity or --hmac") + sys.exit(1) + if args.role == "server": + run_server(args) + else: + run_client(args) + + +if __name__ == "__main__": + main() diff --git a/lab5/pyproject.toml b/lab5/pyproject.toml new file mode 100644 index 0000000..114735f --- /dev/null +++ b/lab5/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "lab5" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "cryptography>=46.0.6", +] diff --git a/lab5/uv.lock b/lab5/uv.lock new file mode 100644 index 0000000..e2a07eb --- /dev/null +++ b/lab5/uv.lock @@ -0,0 +1,109 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" }, + { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" }, + { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" }, + { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" }, + { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" }, + { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" }, + { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" }, + { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" }, + { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" }, + { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" }, + { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" }, + { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" }, + { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" }, +] + +[[package]] +name = "lab5" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "cryptography" }, +] + +[package.metadata] +requires-dist = [{ name = "cryptography", specifier = ">=46.0.6" }] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] diff --git a/report/img/lab5-enc-int.png b/report/img/lab5-enc-int.png new file mode 100644 index 0000000..863226c Binary files /dev/null and b/report/img/lab5-enc-int.png differ diff --git a/report/img/lab5-enc-tcpdump.png b/report/img/lab5-enc-tcpdump.png new file mode 100644 index 0000000..f9349a9 Binary files /dev/null and b/report/img/lab5-enc-tcpdump.png differ diff --git a/report/img/lab5-enc.png b/report/img/lab5-enc.png new file mode 100644 index 0000000..e18c5b9 Binary files /dev/null and b/report/img/lab5-enc.png differ diff --git a/report/img/lab5-hmac-fail.png b/report/img/lab5-hmac-fail.png new file mode 100644 index 0000000..d2cfdec Binary files /dev/null and b/report/img/lab5-hmac-fail.png differ diff --git a/report/img/lab5-hmac.png b/report/img/lab5-hmac.png new file mode 100644 index 0000000..463458a Binary files /dev/null and b/report/img/lab5-hmac.png differ diff --git a/report/img/lab5-plain-tcpdump.png b/report/img/lab5-plain-tcpdump.png new file mode 100644 index 0000000..bec2926 Binary files /dev/null and b/report/img/lab5-plain-tcpdump.png differ diff --git a/report/img/lab5-plain.png b/report/img/lab5-plain.png new file mode 100644 index 0000000..c55cf62 Binary files /dev/null and b/report/img/lab5-plain.png differ diff --git a/report/img/lab5-salt.png b/report/img/lab5-salt.png new file mode 100644 index 0000000..52eb462 Binary files /dev/null and b/report/img/lab5-salt.png differ diff --git a/report/img/lab5-test-int.png b/report/img/lab5-test-int.png new file mode 100644 index 0000000..8d7ce3b Binary files /dev/null and b/report/img/lab5-test-int.png differ diff --git a/report/report.tex b/report/report.tex index 665cd17..d11df18 100755 --- a/report/report.tex +++ b/report/report.tex @@ -178,6 +178,7 @@ \item Практическая работа №2. Разработка и исследование системы аутентификации и авторизации. Данная практическая работа посвящена разработке системы доступа пользователей к конфиденциальным данным и исследованию стойкости паролей к атаке методом грубой силы. В ходе выполнения работы реализованы утилита управления пользователями, утилита доступа к конфиденциальным данным и программа перебора паролей, а также проведено экспериментальное исследование зависимости времени взлома от длины пароля. \item Практическая работа №3. Реализация моделей дискреционного и мандатного управления доступом. Данная практическая работа посвящена расширению системы из работы №2 путём реализации моделей DAC (дискреционный доступ) и MAC (мандатный доступ) на основе модели Белла–Лападулы. \item Практическая работа №4. Межсетевое экранирование средствами \texttt{iptables}. Данная практическая работа посвящена настройке межсетевого экрана в Linux, формированию политики фильтрации сетевого трафика и экспериментальной проверке пропуска разрешённых соединений и блокировки запрещённых пакетов с использованием \texttt{tcpdump}. + \item Практическая работа №5. Разработка клиент-серверного приложения для конфиденциального обмена сообщениями. Данная практическая работа посвящена разработке TCP-приложения с поддержкой шифрования 3DES-CBC с затравкой и контроля целостности на основе SHA-256, а также проверке корректности шифрования с помощью анализа трафика. \end{enumerate} \newpage @@ -956,6 +957,183 @@ $ sudo tcpdump -i any -n icmp В ходе практической работы №4 на стенде из двух виртуальных машин VirtualBox настроена политика межсетевого экранирования средствами \texttt{iptables}. Реализована схема с политикой \texttt{DROP} по умолчанию и разрешающими правилами для loopback, DNS, исходящего ping, входящего ping от адреса \texttt{192.168.100.2} и HTTP/HTTPS. Экспериментальная проверка подтвердила пропуск разрешённого трафика и блокировку соединений, не описанных в политике. Анализ трафика утилитой \texttt{tcpdump} продемонстрировал соответствие наблюдаемой сетевой активности заданным правилам фильтрации. +\newpage +\section{Разработка клиент-серверного приложения для конфиденциального обмена сообщениями} + +\subsection{Актуальность темы} + +Обеспечение конфиденциальности и целостности данных при передаче по сети является одной из ключевых задач информационной безопасности. Даже при использовании защищённых протоколов транспортного уровня понимание механизмов симметричного шифрования и контроля целостности на уровне приложения позволяет проектировать системы с дополнительным эшелоном защиты. Практическая реализация клиент-серверного приложения с шифрованием и хэш-контролем в рамках курса <<Защита информации>> закрепляет навыки работы с криптографическими примитивами и демонстрирует их влияние на передаваемый трафик. + +\subsection{Цели и задачи работы} + +Цель работы: разработка клиент-серверного приложения для обмена короткими сообщениями по протоколу TCP с обеспечением шифрования и контроля целостности. + +Задачи работы: +\begin{enumerate} + \item Получить базовые знания по использованию криптографических функций библиотеки \texttt{cryptography} в Python. + \item Разработать клиент-серверное приложение для обмена шифрованными сообщениями с контролем целостности. + \item Проверить работу приложения в различных режимах, в том числе с помощью программы анализа трафика \texttt{tcpdump}. +\end{enumerate} + +\subsection{Описание алгоритма шифрования} + +\textbf{3DES (Triple DES)} — симметричный блочный шифр, основанный на трёхкратном применении алгоритма DES в режиме EDE (Encrypt–Decrypt–Encrypt) с тремя независимыми подключами. Размер блока — 64 бита (8 байт), длина ключа — 192 бита (24 байта, из которых 168 бит являются эффективными ключевыми битами, а оставшиеся 24 бита — биты чётности). Несмотря на то что 3DES считается устаревшим и уступает AES по производительности, он по-прежнему обеспечивает достаточный уровень стойкости для учебных задач. + +В разработанном приложении используется режим сцепления блоков \textbf{CBC} (Cipher Block Chaining): каждый блок открытого текста перед шифрованием складывается по модулю 2 с предыдущим блоком шифротекста (для первого блока — с вектором инициализации IV). Это обеспечивает зависимость каждого блока шифротекста от всех предшествующих блоков, затрудняя анализ повторяющихся фрагментов. Дополнение до границы блока выполняется по схеме PKCS7. + +\textbf{Шифрование с затравкой (солью).} Для каждого сообщения генерируется случайная 16-байтовая затравка (salt). Ключ 3DES длиной 24 байта вычисляется как первые 24 байта хэша SHA-256 от конкатенации мастер-ключа из файла и затравки: $K_{\mathrm{3DES}} = \mathrm{SHA\text{-}256}(K_{\mathrm{file}} \mathbin\| \mathrm{salt})[{:}24]$. Кроме того, для каждого сообщения генерируется случайный 8-байтовый IV. Затравка и IV передаются вместе с шифротекстом в открытом виде, что позволяет получателю воспроизвести ключ. Благодаря уникальности затравки и IV одинаковые открытые тексты порождают различные шифротексты, что исключает атаки на основе повторения. + +\textbf{Контроль целостности} реализован с помощью хэш-функции SHA-256 (в соответствии с индивидуальным заданием из практической работы №2). При включённой опции контроля целостности отправитель вычисляет хэш открытого текста сообщения и передаёт его вместе с данными. Получатель вычисляет хэш самостоятельно и сравнивает с полученным значением; при расхождении выводится предупреждение о нарушении целостности. + +\textbf{HMAC-SHA256.} В качестве альтернативы простому SHA-256 реализован режим HMAC (Hash-based Message Authentication Code) на основе SHA-256. В отличие от обычного хэша, HMAC использует секретный ключ: $\mathrm{HMAC} = \mathrm{HMAC\text{-}SHA256}(K_{\mathrm{file}},\; \mathrm{plaintext} \mathbin\| \mathrm{salt})$. Это даёт два преимущества: (1)~получатель может убедиться, что отправитель владеет тем же секретным ключом (аутентификация), а не только в том, что сообщение не было изменено; (2)~случайная затравка, подмешиваемая к сообщению, обеспечивает различные значения HMAC для одинаковых открытых текстов. Режимы \texttt{-{}-integrity} (SHA-256) и \texttt{-{}-hmac} (HMAC-SHA256) являются взаимоисключающими. + +\subsection{Описание реализации} + +Приложение реализовано на языке Python~3.14 в виде единого файла \texttt{main.py} (исходный код приведён в приложении~9). Для криптографических операций используется библиотека \texttt{cryptography}. Управление зависимостями осуществляется менеджером \texttt{uv}. + +\textbf{Протокол обмена.} Сообщения передаются по TCP в формате: 4~байта длины (big-endian) + JSON-объект. Структура JSON-объекта: + +\begin{verbatim} +{ + "encrypted": true/false, + "salt": "hex (16 байт)", + "iv": "hex (8 байт)", + "data": "hex шифротекст / открытый текст", + "hash": "SHA-256 hex (если включён контроль)" +} +\end{verbatim} + +\textbf{Полнодуплексный обмен} обеспечивается двумя потоками: один читает ввод пользователя и отправляет сообщения, второй принимает входящие сообщения и выводит их на экран. + +\textbf{Режимы запуска} задаются ключами командной строки: \texttt{-{}-encrypt} включает шифрование 3DES-CBC, \texttt{-{}-integrity} включает контроль целостности SHA-256, \texttt{-{}-hmac} включает контроль целостности и аутентификацию HMAC-SHA256 (несовместим с \texttt{-{}-integrity}), \texttt{-{}-test-integrity} активирует режим отправки сообщений с заведомо некорректным хэш-значением (требует \texttt{-{}-integrity} или \texttt{-{}-hmac}). + +\textbf{Защита ключа.} Мастер-ключ считывается из файла (по умолчанию \texttt{key.txt}). При запуске программа проверяет права доступа к файлу ключа и завершает работу с ошибкой, если файл доступен для чтения группе или другим пользователям (права более открытые, чем \texttt{600}). + +\subsection{Команды сборки и запуска} + +Установка зависимостей: +\begin{verbatim} +uv sync +\end{verbatim} + +Создание файла ключа: +\begin{verbatim} +openssl rand -hex 32 > key.txt +chmod 600 key.txt +\end{verbatim} + +Запуск сервера и клиента (примеры): +\begin{verbatim} +uv run main.py server --port 9000 +uv run main.py client 127.0.0.1 9000 + +uv run main.py server --port 9000 --encrypt +uv run main.py client 127.0.0.1 9000 --encrypt + +uv run main.py server --port 9000 --encrypt --integrity +uv run main.py client 127.0.0.1 9000 --encrypt --integrity +\end{verbatim} + +\subsection{Проверка работы приложения} + +\subsubsection{Передача сообщений без шифрования} + +При запуске без флагов сообщения передаются открытым текстом. На рисунке~\ref{fig:lab5-plain} показан вывод серверной и клиентской частей приложения. + +\begin{figure}[H] + \centering + \includegraphics[width=0.8\linewidth]{img/lab5-plain.png} + \caption{Обмен сообщениями без шифрования} + \label{fig:lab5-plain} +\end{figure} + +Анализ трафика утилитой \texttt{tcpdump} (рис.~\ref{fig:lab5-plain-tcpdump}) подтверждает, что текст сообщения передаётся в открытом виде и может быть прочитан непосредственно из дампа пакетов. + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\linewidth]{img/lab5-plain-tcpdump.png} + \caption{Дамп трафика (\texttt{tcpdump}) при передаче без шифрования — текст виден} + \label{fig:lab5-plain-tcpdump} +\end{figure} + +\subsubsection{Передача сообщений с шифрованием} + +При включённой опции \texttt{-{}-encrypt} сообщения шифруются алгоритмом 3DES-CBC с затравкой. На рисунке~\ref{fig:lab5-enc} показан вывод приложения: видны параметры шифрования (соль, производный ключ, IV, шифротекст) и результат расшифровки. + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\linewidth]{img/lab5-enc.png} + \caption{Обмен сообщениями с шифрованием 3DES-CBC} + \label{fig:lab5-enc} +\end{figure} + +Дамп трафика (рис.~\ref{fig:lab5-enc-tcpdump}) показывает, что в пакетах присутствует только шифротекст в виде hex-строк; читаемый текст сообщения отсутствует. + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\linewidth]{img/lab5-enc-tcpdump.png} + \caption{Дамп трафика при передаче с шифрованием — текст не виден} + \label{fig:lab5-enc-tcpdump} +\end{figure} + +\subsubsection{Шифрование с контролем целостности} + +При одновременном использовании флагов \texttt{-{}-encrypt} и \texttt{-{}-integrity} к зашифрованному сообщению добавляется хэш SHA-256 открытого текста. На стороне получателя после расшифровки вычисляется собственный хэш и сравнивается с полученным (рис.~\ref{fig:lab5-enc-int}). + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\linewidth]{img/lab5-enc-int.png} + \caption{Обмен сообщениями с шифрованием и контролем целостности} + \label{fig:lab5-enc-int} +\end{figure} + +\subsubsection{Тестирование контроля целостности с некорректным хэшем} + +В режиме \texttt{-{}-test-integrity} отправитель намеренно передаёт некорректный хэш. Получатель обнаруживает расхождение и выводит предупреждение (рис.~\ref{fig:lab5-test-int}). + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\linewidth]{img/lab5-test-int.png} + \caption{Обнаружение некорректного хэш-значения} + \label{fig:lab5-test-int} +\end{figure} + +\subsubsection{Демонстрация шифрования с затравкой} + +При отправке одного и того же сообщения дважды шифротексты различаются благодаря случайной затравке и IV (рис.~\ref{fig:lab5-salt}). + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\linewidth]{img/lab5-salt.png} + \caption{Разные шифротексты для одинаковых сообщений (эффект затравки)} + \label{fig:lab5-salt} +\end{figure} + +\subsubsection{Контроль целостности и аутентификация с HMAC-SHA256} + +При использовании флага \texttt{-{}-hmac} вместо \texttt{-{}-integrity} к каждому сообщению прикладывается код HMAC-SHA256, вычисленный с использованием секретного ключа и случайной затравки. На рисунке~\ref{fig:lab5-hmac} показан обмен сообщениями в режиме \texttt{-{}-encrypt -{}-hmac}: видны HMAC-затравка, вычисленный HMAC на стороне отправителя и результат проверки на стороне получателя. При отправке одного и того же сообщения повторно значение HMAC отличается благодаря новой затравке. + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\linewidth]{img/lab5-hmac.png} + \caption{Обмен сообщениями с HMAC-SHA256: аутентификация и контроль целостности} + \label{fig:lab5-hmac} +\end{figure} + +На рисунке~\ref{fig:lab5-hmac-fail} показана проверка режима \texttt{-{}-test-integrity} совместно с \texttt{-{}-hmac}: отправитель передаёт намеренно некорректный HMAC, получатель обнаруживает несоответствие и сообщает о неудачной аутентификации. + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\linewidth]{img/lab5-hmac-fail.png} + \caption{Обнаружение некорректного HMAC — отправитель не аутентифицирован} + \label{fig:lab5-hmac-fail} +\end{figure} + +\subsection{Выводы} + +В ходе практической работы №5 разработано клиент-серверное приложение для конфиденциального обмена сообщениями по протоколу TCP. Реализовано шифрование 3DES-CBC с затравкой, обеспечивающей различные шифротексты при одинаковых открытых текстах, контроль целостности на основе хэш-функции SHA-256 и режим HMAC-SHA256, обеспечивающий одновременно контроль целостности и аутентификацию отправителя. Экспериментальная проверка с помощью \texttt{tcpdump} подтвердила, что при включённом шифровании содержимое сообщений не передаётся в открытом виде, а при включённом контроле целостности получатель успешно обнаруживает намеренно повреждённые сообщения. + + \newpage \section*{Заключение} \addcontentsline{toc}{section}{Заключение} @@ -968,6 +1146,8 @@ $ sudo tcpdump -i any -n icmp В ходе выполнения практической работы №4 на стенде из двух виртуальных машин VirtualBox настроена политика межсетевого экранирования средствами \texttt{iptables}. Реализованы правила фильтрации, разрешающие loopback-трафик, DNS, ICMP и HTTP/HTTPS, а все прочие соединения блокируются. Корректность работы правил подтверждена тестовыми соединениями и анализом пакетов утилитой \texttt{tcpdump}. +В ходе выполнения практической работы №5 разработано клиент-серверное приложение для конфиденциального обмена сообщениями по протоколу TCP. Реализовано шифрование 3DES-CBC с затравкой, контроль целостности SHA-256 и режим HMAC-SHA256 для аутентификации отправителя. Анализ трафика утилитой \texttt{tcpdump} подтвердил, что при включённом шифровании содержимое сообщений не доступно в открытом виде, а режим тестирования продемонстрировал корректное обнаружение нарушений целостности. + \newpage \printbibliography[heading=bibintoc] @@ -1021,4 +1201,11 @@ $ sudo tcpdump -i any -n icmp \addcontentsline{toc}{section}{Приложение 8} \lstinputlisting{../lab3/setup.sh} +\newpage +\section*{Приложение 9} +\subsubsection*{main.py} +\addcontentsline{toc}{section}{Приложение 9} +\label{app:lab5-main} +\lstinputlisting{../lab5/main.py} + \end{document}