From 3aa6d8a9e551fed3941bcdcccd42540c79c2f5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=BC=D0=B0=D0=B5=D0=B2=20=D0=9C=D0=B0=D0=BA?= =?UTF-8?q?=D1=81=D0=B8=D0=BC?= Date: Thu, 25 Sep 2025 19:30:00 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=97=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=83=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B4=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=B9=20=D1=87=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 166 +++++++++++++++++++++++++++++++++++ lab_3/hybrid_cryptosystem.py | 74 ++++++++++++++++ lab_3/main.py | 29 ++++++ lab_3/private_key.pem | 27 ++++++ lab_3/public_key.pem | 9 ++ lab_3/settings.json | 6 ++ lab_3/sym_key.txt | Bin 0 -> 256 bytes lab_3/tools.py | 39 ++++++++ 8 files changed, 350 insertions(+) create mode 100644 .gitignore create mode 100644 lab_3/hybrid_cryptosystem.py create mode 100644 lab_3/main.py create mode 100644 lab_3/private_key.pem create mode 100644 lab_3/public_key.pem create mode 100644 lab_3/settings.json create mode 100644 lab_3/sym_key.txt create mode 100644 lab_3/tools.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3c080a83 --- /dev/null +++ b/.gitignore @@ -0,0 +1,166 @@ +.vscode/launch.json +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +.vscode/ + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + diff --git a/lab_3/hybrid_cryptosystem.py b/lab_3/hybrid_cryptosystem.py new file mode 100644 index 00000000..b64bf782 --- /dev/null +++ b/lab_3/hybrid_cryptosystem.py @@ -0,0 +1,74 @@ +import os +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import serialization, hashes +from cryptography.hazmat.primitives.serialization import load_pem_public_key, load_pem_private_key +import tools +import json + +class SerelizationKey: + + def deser_key_public(public_pem): + with open(public_pem, 'rb') as pem_in: + public_bytes = pem_in.read() + d_public_key = load_pem_public_key(public_bytes) + return d_public_key + + def deser_key_private(private_pem): + with open(private_pem, 'rb') as pem_in: + private_bytes = pem_in.read() + d_private_key = load_pem_private_key(private_bytes,password=None,) + return d_private_key + + def ser_public_key(public_key , public_pem): + with open(public_pem, 'wb') as public_out: + public_out.write(public_key.public_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo)) + + def ser_private_key(private_key, private_pem): + with open(private_pem, 'wb') as private_out: + private_out.write(private_key.private_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption())) + +class GenerationKey: + + def gen_key_sym(num_of_bit: int): + settings = tools.read_json("settings.json") + key = os.urandom(num_of_bit//8) # это байты + tools.save_txt(settings["path_sym_key"], str(key)) + + def gen_key_asym(): + keys = rsa.generate_private_key( + public_exponent=65537, + key_size=2048 + ) + private_key = keys + public_key = keys.public_key() + return private_key, public_key + +class Crypt: + def ass_crypt(public_key, path_sym_key): + text = tools.read_txt(path_sym_key) + c_text = public_key.encrypt(bytes(text, encoding="UTF-8"), padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None)) + file_name = path_sym_key + with open(file_name, 'wb') as key_file: + key_file.write(c_text) + + def ass_decrtpt(private_key, path_sym_key): + text = tools.read_txt(path_sym_key) + dc_text = private_key.decrypt(bytes(text),padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None)) + print(dc_text.decode('UTF-8')) + +class Modes: + def hybrid_sys_key_gen(path_sym_key, path_private_key, path_public_key): + with open('settings.json') as json_file: + settings = json.load(json_file) + GenerationKey.gen_key_sym(settings["num_of_bit"]) + private_key, public_key = GenerationKey.gen_key_asym() + SerelizationKey.ser_private_key(private_key, path_private_key) + SerelizationKey.ser_public_key(public_key, path_public_key) + Crypt.ass_crypt(public_key, path_sym_key) + + def data_encryption(path_sym_key, path_public_key): + Crypt.ass_decrtpt(path_public_key, path_sym_key) + \ No newline at end of file diff --git a/lab_3/main.py b/lab_3/main.py new file mode 100644 index 00000000..bbe6c8e4 --- /dev/null +++ b/lab_3/main.py @@ -0,0 +1,29 @@ +import json +import argparse +from hybrid_cryptosystem import Modes + +def main(): + with open('settings.json') as json_file: + settings = json.load(json_file) + parser = argparse.ArgumentParser() + group = parser.add_mutually_exclusive_group(required = True) + group.add_argument('-gen','--generation',help='Запускает режим генерации ключей') + group.add_argument('-enc','--encryption',help='Запускает режим шифрования') + group.add_argument('-dec','--decryption',help='Запускает режим дешифрования') + + args = parser.parse_args() + if args.generation is not None: + + Modes.hybrid_sys_key_gen(settings["path_sym_key"], settings["path_private_key"], settings["path_public_key"]) + + elif args.encryption is not None: + + Modes.data_encryption(settings["path_sym_key"], settings["path_public_key"]) + + else: + + Modes.hybrid_sys_key_gen(settings["path_sym_key"], settings["path_private_key"], settings["path_public_key"]) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/lab_3/private_key.pem b/lab_3/private_key.pem new file mode 100644 index 00000000..5807b5e2 --- /dev/null +++ b/lab_3/private_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtrj1xhF2AW4n9TIgmX6jbkBIDUHc1WIB790dSffw4l+fKpCd +OWHHjrxDN6QtYt7qkXjJKeoWvKU5lAm3KWJKH3QTCl6g+vh37oR9IduXm17qVwpK +YM5P10Kjrjf+S9dtRDABQR7IUGL0r/seLRIhzCYKBJT4nnHTwcTu/d6ZHlD3oWdW +8HGUsxCnFWuaW7bHKRJbQylgSqGPnd4iTNB7xHaNCom/j2KViUTboj6SmtY3Yfqa +zjyFciDZauMSGWl1+snlIKLlJD6JcZmApvk8kC9Dpcy08eum+xb24vFpPlgCifQQ +NayH6jQ3bU+yzXmlXqdkqGSLBCwwxGs4HeEAhwIDAQABAoIBAAjM8sV17NWXzSPh +pLFP6KY0QWQmEnFSlZ5Ma482+mqkyWE+8tujKn3k8mXZKfhznR+hjirPJAY0/Qd0 +ylYbbk+CgzJCKYW89uXNistZLL2mfinwDHIKLc1D3+dRuhVEDHa2yzCsP2Cjy344 +XZU1JNyUznG3DRxwhg70ocAnev97OO/wTWVQFR7RUh10DX1y/zpIBP+F+sYRWze1 +G0izuKh604JX4ppKY4nWqv4uIngR5dafMs36PZhS2j+vx3s45uLDIDT9fTwWfMOi +gbGqfQx/Md81Cyi4tP2d9gqV4/zRDQ1GWNqGlwXLCxcWKGwZk8WGPoQyrIk0sM5F +6jIy5vECgYEA7CqoVXkK+AzqDjFWwwQSEFwADAImKvAJ1enbgZju3oB5elP/oEFV +wRTKiDTkwiwxX/rXl/H5Bv2Loiv94diUkFAuJPKZWVwDl/6Ow9AL65Uby7hdBjQ5 +OqHiz8Wn9mQcc56IRYZaoBVLcQtnCa0lPVY8s2xQFnDh+eV84bMsAFECgYEAxhFO +2rsKxM2H4pdDhS91S5n/WXgp/eqewheT7G1nHs98cgkHgtgL857WT02hIbiKAQHB +c2qYR95Df8nnFK2j3KaXx3g661CLpvJkNSQfLUv/H1S6JUAzpJEWVbQ5YmmUAt6n +1nR8EzDAo39zWF4rn0vFZvMSfWzelyR1YC4yVVcCgYAH665Vn4ekpho7e6AjTkGk +1tERlgjdq8tIp107TXvFbp2kYqjSIR7VKPNZ+hwp/v1w8KLGb9EgUOSb/cm4B2GE +LYw+pPxLqPBd6qWzH4kmz4ttG7PS/3Fj7AYFXeWyN81Ue16qpkTn1Y8ALZe5FVuI +YRoMEnflKMgLb4zurOmu4QKBgCEzwY41EIcRFRhM1wq6e+UieYWb+R4ReG982FUn +ucOWcHwFO3cZlFdQZwiAMOSgNLIagz0/vys8/P+s7smumethGcxcQTQ1GlPwChBG +b9t+A9RStamM65tInv/a6Euw+xjIDkaK3EmzQHen48QKdTGM2Bh74ruXxnB7shlf +ZRydAoGBAITJmPAYIFEW+wpPCtGlzS7w3gQ9+56knFUUqtjRJxiwHGSLS7XaRLa4 +08HpLFbQLMvSguZx9VGeU+4rvDGdz5k3nd2yDhjFZOE57mtH9TKTUj+87LUdaPy3 +wP1YclDx6/+5VbV9VJl2LqGztJiYpqi3Rqj/dcw/Cuns2/vNOLDk +-----END RSA PRIVATE KEY----- diff --git a/lab_3/public_key.pem b/lab_3/public_key.pem new file mode 100644 index 00000000..e2c4a50c --- /dev/null +++ b/lab_3/public_key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtrj1xhF2AW4n9TIgmX6j +bkBIDUHc1WIB790dSffw4l+fKpCdOWHHjrxDN6QtYt7qkXjJKeoWvKU5lAm3KWJK +H3QTCl6g+vh37oR9IduXm17qVwpKYM5P10Kjrjf+S9dtRDABQR7IUGL0r/seLRIh +zCYKBJT4nnHTwcTu/d6ZHlD3oWdW8HGUsxCnFWuaW7bHKRJbQylgSqGPnd4iTNB7 +xHaNCom/j2KViUTboj6SmtY3YfqazjyFciDZauMSGWl1+snlIKLlJD6JcZmApvk8 +kC9Dpcy08eum+xb24vFpPlgCifQQNayH6jQ3bU+yzXmlXqdkqGSLBCwwxGs4HeEA +hwIDAQAB +-----END PUBLIC KEY----- diff --git a/lab_3/settings.json b/lab_3/settings.json new file mode 100644 index 00000000..6babe75f --- /dev/null +++ b/lab_3/settings.json @@ -0,0 +1,6 @@ +{ + "path_sym_key" : "sym_key.txt", + "path_private_key" : "private_key.pem", + "path_public_key" : "public_key.pem", + "num_of_bit" : 128 +} \ No newline at end of file diff --git a/lab_3/sym_key.txt b/lab_3/sym_key.txt new file mode 100644 index 0000000000000000000000000000000000000000..37b1800c25dfe2e2db2d169ab85e9781ed22c961 GIT binary patch literal 256 zcmV+b0ssE254jaE!;;ezv@uCf^HGRT$L)TM)A&xjl9duIj7#Z<#TfknQ&7a#n93F0 zdwCp3fLwD{(?yHu2{yffM)4j=wk7VLm@F9DQ!#Hyw*{ZE1J&M3V*7FNKu`>ubFn(b zNi-My3C>#xt9GCFeM!V^8)G{@jmf^r>k7J`D2z7>ZI}N|O{s8+!R3RpsV=RfAB_Xj zBQ*I15y?Tx212dZ1b&iu*9ZH9K-6pOp=Xhx##*^i$q6}Y&v9Z=Dc^o<$5qdZ@cpgq z)sgpI_CP8naq*P2{Vj(IdJVW{y|k*uq*Ak_!e3%qcE!OpRA9UO*o*-zXZ)bj^3=Xv G;@FE>oP#z1 literal 0 HcmV?d00001 diff --git a/lab_3/tools.py b/lab_3/tools.py new file mode 100644 index 00000000..4181a32d --- /dev/null +++ b/lab_3/tools.py @@ -0,0 +1,39 @@ +import json +from typing import Any, Dict + + +def read_txt(file_name: str) -> str: + """Считывает текcт из .txt файла + + Args: + file_name (str): Путь к файлу + + Returns: + str: Содержимое файла + """ + with open(file_name, 'r', encoding="utf-8") as f: + text: str = f.read() + return text + +def save_txt(file_name: str, text: str): + """Удаляет содержимое файла и сохраняет в нём новый текст. + Если файла не существует, создаёт его. + + Args: + file_name (str): Путь к файлу + text (str): Текст, который необходимо сохранить + """ + with open(file_name, 'w', encoding="utf-8") as f: + f.write(text) + +def read_json(file_name: str) -> Dict[str, Any]: + """Считывает данные из .json файла + + Args: + file_name (str): Путь к файлу + + Returns: + Dict[str, Any]: Словарь объектов, содеражавшихся в .json файле + """ + with open(file_name, 'r', encoding='utf-8') as f: + return(json.load(f)) \ No newline at end of file From 1ccfe2f4b8749c9740636da996113f72f41b1e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=BC=D0=B0=D0=B5=D0=B2=20=D0=9C=D0=B0=D0=BA?= =?UTF-8?q?=D1=81=D0=B8=D0=BC?= Date: Fri, 26 Sep 2025 18:20:53 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=9B=D0=B0=D0=B1=D0=BE=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=BD=D0=B0=D1=8F=20=D0=B4=D0=BE=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/ctext.txt | Bin 0 -> 5360 bytes lab_3/dctext.txt | 19 ++++ lab_3/hybrid_cryptosystem.py | 186 +++++++++++++++++++++++++++++++---- lab_3/iv.txt | 1 + lab_3/main.py | 53 ++++++---- lab_3/private_key.pem | 50 +++++----- lab_3/public_key.pem | 14 +-- lab_3/settings.json | 6 +- lab_3/sym_key.txt | Bin 256 -> 256 bytes lab_3/text.txt | 19 ++++ lab_3/tools.py | 108 ++++++++++++++------ 11 files changed, 356 insertions(+), 100 deletions(-) create mode 100644 lab_3/ctext.txt create mode 100644 lab_3/dctext.txt create mode 100644 lab_3/iv.txt create mode 100644 lab_3/text.txt diff --git a/lab_3/ctext.txt b/lab_3/ctext.txt new file mode 100644 index 0000000000000000000000000000000000000000..969bbe13190dd1e191b14383e53d305e3fc585e6 GIT binary patch literal 5360 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 RSAPublicKey: + """ + Десериализация публичного ключа из PEM файла + + Args: + public_pem (str): Путь к файлу с публичным ключом + + Returns: + RSAPublicKey: Десериализованный публичный ключ + """ with open(public_pem, 'rb') as pem_in: public_bytes = pem_in.read() d_public_key = load_pem_public_key(public_bytes) return d_public_key - def deser_key_private(private_pem): + def deser_key_private(private_pem: str) -> RSAPrivateKey: + """ + Десериализация приватного ключа из PEM файла + + Args: + private_pem (str): Путь к файлу с приватным ключом + + Returns: + RSAPrivateKey: Десериализованный приватный ключ + """ with open(private_pem, 'rb') as pem_in: private_bytes = pem_in.read() d_private_key = load_pem_private_key(private_bytes,password=None,) return d_private_key - def ser_public_key(public_key , public_pem): + def ser_public_key(public_key: RSAPublicKey, public_pem: str): + """ + Сериализация публичного ключа в PEM файл + + Args: + public_key (RSAPublicKey): Публичный ключ для сериализации + public_pem (str): Путь для сохранения публичного ключа + """ with open(public_pem, 'wb') as public_out: public_out.write(public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)) - def ser_private_key(private_key, private_pem): + def ser_private_key(private_key: RSAPrivateKey, private_pem: str): + """ + Сериализация приватного ключа в PEM файл + + Args: + private_key (RSAPrivateKey): Приватный ключ для сериализации + private_pem (str): Путь для сохранения приватного ключа + """ with open(private_pem, 'wb') as private_out: private_out.write(private_key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption())) class GenerationKey: + """Класс для генерации симметричных и асимметричных ключей""" def gen_key_sym(num_of_bit: int): - settings = tools.read_json("settings.json") - key = os.urandom(num_of_bit//8) # это байты - tools.save_txt(settings["path_sym_key"], str(key)) + """ + Генерация симметричного ключа заданной длины + + Args: + num_of_bit (int): Длина ключа в битах (128, 192, 256) + """ + settings = Tools.read_json("settings.json") + key = os.urandom(num_of_bit//8) + Tools.save_txt_binary(settings["path_sym_key"], key) - def gen_key_asym(): + def gen_key_asym() -> tuple[RSAPublicKey, RSAPrivateKey]: + """ + Генерация пары асимметричных ключей (приватный и публичный) + + Returns: + tuple: (private_key, public_key) - пара RSA ключей + """ keys = rsa.generate_private_key( public_exponent=65537, key_size=2048 @@ -46,29 +95,126 @@ def gen_key_asym(): public_key = keys.public_key() return private_key, public_key -class Crypt: - def ass_crypt(public_key, path_sym_key): - text = tools.read_txt(path_sym_key) - c_text = public_key.encrypt(bytes(text, encoding="UTF-8"), padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None)) +class Crypt: + """Класс для выполнения криптографических операций""" + + def ass_crypt(public_key: RSAPublicKey, path_sym_key: str): + """ + Асимметричное шифрование симметричного ключа + + Args: + public_key (RSAPublicKey): Публичный ключ для шифрования + path_sym_key (str): Путь к файлу с симметричным ключом + """ + text = Tools.read_txt_binary(path_sym_key) + c_text = public_key.encrypt(text, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None)) file_name = path_sym_key with open(file_name, 'wb') as key_file: key_file.write(c_text) - def ass_decrtpt(private_key, path_sym_key): - text = tools.read_txt(path_sym_key) + def ass_decrtpt(private_key_path, path_sym_key): + text = Tools.read_txt_binary(path_sym_key) + private_key = SerelizationKey.deser_key_private(private_key_path) dc_text = private_key.decrypt(bytes(text),padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None)) - print(dc_text.decode('UTF-8')) + return dc_text + + def encrypt_camellia_text(text_path: str, key: bytes, ctext_path: str, iv_path: str): + """ + Шифрование текста алгоритмом Camellia в режиме CBC + + Args: + text_path (str): Путь до исходного текстового файла + key (bytes): Симметричный ключ для шифрования + ctext_path (str): Путь для сохранения зашифрованного текста + iv_path (str): Путь для сохранения вектора инициализации (IV) + + """ + + text = Tools.read_txt(text_path) + padder = pd.ANSIX923(128).padder() + text_bytes = text.encode('UTF-8') if isinstance(text, str) else text + padded_text = padder.update(text_bytes) + padder.finalize() + iv = os.urandom(16) + cipher = Cipher(algorithms.Camellia(key), modes.CBC(iv)) + encryptor = cipher.encryptor() + cipher_text = encryptor.update(padded_text) + encryptor.finalize() + Tools.save_txt_binary(ctext_path, cipher_text) + Tools.save_txt_binary(iv_path, iv) + + def decrypt_camellia_text(cipher_text_path: str, key: bytes, iv_path: str, dcipher_text_path: str): + """ + Дешифрование текста алгоритмом Camellia + + Args: + cipher_text_path (str): Путь к файлу с зашифрованным текстом + key (bytes): Симметричный ключ для дешифрования + iv_path (str): Путь к файлу с вектором инициализации + dcipher_text_path (str): Путь для сохранения расшифрованного текста + + """ + iv = Tools.read_txt_binary(iv_path) + cipher_text = Tools.read_txt_binary(cipher_text_path) + cipher = Cipher(algorithms.Camellia(key), modes.CBC(iv)) + decryptor = cipher.decryptor() + + padded_text = decryptor.update(cipher_text) + decryptor.finalize() + + unpadder = pd.ANSIX923(128).unpadder() + original_text = unpadder.update(padded_text) + unpadder.finalize() + original_text = original_text.decode('UTF-8') + Tools.save_txt(dcipher_text_path, original_text) class Modes: - def hybrid_sys_key_gen(path_sym_key, path_private_key, path_public_key): + + """Класс для работы с режимами гибридной криптосистемы""" + + def hybrid_sys_key_gen(path_sym_key: str, path_private_key: str, path_public_key: str): + """ + Генерация ключей для гибридной криптосистемы + + Args: + path_sym_key (str): Путь для сохранения симметричного ключа + path_private_key (str): Путь для сохранения приватного ключа + path_public_key (str): Путь для сохранения публичного ключа + """ with open('settings.json') as json_file: settings = json.load(json_file) - GenerationKey.gen_key_sym(settings["num_of_bit"]) + key_length = settings["num_of_bit"] + if not key_length in [128, 192, 256]: + raise ValueError(f"Неверная длина ключа, может быть 128, 192, 256, получено {key_length}") + GenerationKey.gen_key_sym(key_length) private_key, public_key = GenerationKey.gen_key_asym() SerelizationKey.ser_private_key(private_key, path_private_key) SerelizationKey.ser_public_key(public_key, path_public_key) Crypt.ass_crypt(public_key, path_sym_key) - def data_encryption(path_sym_key, path_public_key): - Crypt.ass_decrtpt(path_public_key, path_sym_key) + def data_encryption(path_sym_key: str, path_private_key: str, text_path: str, ctext_path: str, iv_path: str): + """ + Шифрование данных с использованием гибридной системы + + Args: + path_sym_key (str): Путь к зашифрованному симметричному ключу + path_private_key (str): Путь к приватному ключу для дешифрования симметричного ключа + text_path (str): Путь к исходному текстовому файлу + ctext_path (str): Путь для сохранения зашифрованного текста + iv_path (str): Путь для сохранения вектора инициализации + """ + key = Crypt.ass_decrtpt(path_private_key, path_sym_key) + Crypt.encrypt_camellia_text(text_path, key ,ctext_path, iv_path) + + def data_decryption(path_sym_key: str, path_private_key: str, ctext_path: str, dctext_path: str, iv_path: str): + """ + Дешифрование данных с использованием гибридной системы + + Args: + path_sym_key (str): Путь к зашифрованному симметричному ключу + path_private_key (str): Путь к приватному ключу для дешифрования симметричного ключа + ctext_path (str): Путь к зашифрованному тексту + dctext_path (str): Путь для сохранения расшифрованного текста + iv_path (str): Путь к файлу с вектором инициализации + """ + key = Crypt.ass_decrtpt(path_private_key, path_sym_key) + Crypt.decrypt_camellia_text(ctext_path, key, iv_path, dctext_path) + + \ No newline at end of file diff --git a/lab_3/iv.txt b/lab_3/iv.txt new file mode 100644 index 00000000..781b1f40 --- /dev/null +++ b/lab_3/iv.txt @@ -0,0 +1 @@ +ocD#2m Θ \ No newline at end of file diff --git a/lab_3/main.py b/lab_3/main.py index bbe6c8e4..0d029a16 100644 --- a/lab_3/main.py +++ b/lab_3/main.py @@ -1,29 +1,46 @@ -import json import argparse from hybrid_cryptosystem import Modes +from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm +from tools import Tools def main(): - with open('settings.json') as json_file: - settings = json.load(json_file) - parser = argparse.ArgumentParser() - group = parser.add_mutually_exclusive_group(required = True) - group.add_argument('-gen','--generation',help='Запускает режим генерации ключей') - group.add_argument('-enc','--encryption',help='Запускает режим шифрования') - group.add_argument('-dec','--decryption',help='Запускает режим дешифрования') - args = parser.parse_args() - if args.generation is not None: + """ Точка входа в приложение """ + try: + settings_file = 'settings.json' + Tools.is_file_exists(settings_file) + settings = Tools.read_json(settings_file) + parser = argparse.ArgumentParser() + group = parser.add_mutually_exclusive_group(required = True) + group.add_argument('-gen','--generation',help='Запускает режим генерации ключей') + group.add_argument('-enc','--encryption',help='Запускает режим шифрования') + group.add_argument('-dec','--decryption',help='Запускает режим дешифрования') - Modes.hybrid_sys_key_gen(settings["path_sym_key"], settings["path_private_key"], settings["path_public_key"]) + args = parser.parse_args() + if args.generation is not None: - elif args.encryption is not None: - - Modes.data_encryption(settings["path_sym_key"], settings["path_public_key"]) + Modes.hybrid_sys_key_gen(settings["path_sym_key"], settings["path_private_key"], settings["path_public_key"]) - else: - - Modes.hybrid_sys_key_gen(settings["path_sym_key"], settings["path_private_key"], settings["path_public_key"]) - + elif args.encryption is not None: + + Modes.data_encryption(settings["path_sym_key"], settings["path_private_key"], settings["path_text"], settings["path_ctext"], settings["path_iv"]) + + else: + Modes.data_decryption(settings["path_sym_key"], settings["path_private_key"], settings["path_ctext"], settings["path_dctext"], settings["path_iv"]) + except UnsupportedAlgorithm as e: + print(f"Cryptographic algorithm not supported: {e}") + except InvalidKey as e: + print(f"Key serialization error: {e}") + except PermissionError as e: + print(f"PermissionError: {e}") + except FileNotFoundError as e: + print(f"FileNotFoundError: {e}") + except ValueError as e: + print(f"ValueError: {e}") + except KeyError as e: + print(f"Missing setting: {e}") + except Exception as e: + print(f"Error: {e}") if __name__ == "__main__": main() \ No newline at end of file diff --git a/lab_3/private_key.pem b/lab_3/private_key.pem index 5807b5e2..c60dd7af 100644 --- a/lab_3/private_key.pem +++ b/lab_3/private_key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/lab_3/public_key.pem b/lab_3/public_key.pem index e2c4a50c..dad67af8 100644 --- a/lab_3/public_key.pem +++ b/lab_3/public_key.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtrj1xhF2AW4n9TIgmX6j -bkBIDUHc1WIB790dSffw4l+fKpCdOWHHjrxDN6QtYt7qkXjJKeoWvKU5lAm3KWJK -H3QTCl6g+vh37oR9IduXm17qVwpKYM5P10Kjrjf+S9dtRDABQR7IUGL0r/seLRIh -zCYKBJT4nnHTwcTu/d6ZHlD3oWdW8HGUsxCnFWuaW7bHKRJbQylgSqGPnd4iTNB7 -xHaNCom/j2KViUTboj6SmtY3YfqazjyFciDZauMSGWl1+snlIKLlJD6JcZmApvk8 -kC9Dpcy08eum+xb24vFpPlgCifQQNayH6jQ3bU+yzXmlXqdkqGSLBCwwxGs4HeEA -hwIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Ww/NbihUcvJS4KSSIgb +SM3EKxGLTWTUo0gt8PltvH7vvfoDwezHObmuLexSQzWmDJlGY8frnDsrT4ssnX1a +2cQihzHWONtRab36TFRkykZsFeqkpn2oXOSaMlhfSPyvo+t/tmqSaWE6nI0ITj0K +OEvHWAWINVpAQ0gZOMV61LMAET4HgHZV4+blc4M9dy5pvaP+wbPK58P7Oa7Fj59F +TKLpnidOHYzMKxgxPsd9cm7500mxPHGy8jllR0JSaRVVotNvHYGHQP2ZSeHduAud +MpkTOU28YO1hEIMIk9XeXfU72bVOuutIH85+wd9yhjDEho6tnauG0V9gVr46oGzq +XQIDAQAB -----END PUBLIC KEY----- diff --git a/lab_3/settings.json b/lab_3/settings.json index 6babe75f..30e6868a 100644 --- a/lab_3/settings.json +++ b/lab_3/settings.json @@ -2,5 +2,9 @@ "path_sym_key" : "sym_key.txt", "path_private_key" : "private_key.pem", "path_public_key" : "public_key.pem", - "num_of_bit" : 128 + "path_text" : "text.txt", + "path_ctext" : "ctext.txt", + "path_iv" : "iv.txt", + "path_dctext" : "dctext.txt", + "num_of_bit" : 256 } \ No newline at end of file diff --git a/lab_3/sym_key.txt b/lab_3/sym_key.txt index 37b1800c25dfe2e2db2d169ab85e9781ed22c961..bf988d16f4df22c6428ac86691e8eb61e73d0cc5 100644 GIT binary patch literal 256 zcmV+b0ssCUJz97%5Hrc4FwTpXK{T{9b;lPOOA)5F=}-SW-d{(gz#0(d2(p|jXZ8hz;m0R9W#7>Qa_T81Ih`C#qi zdMWADmY#95*>lBMD$todyiIyc|BmO9=aD{yggvRUyoOfpQ;c~^ubFn(b zNi-My3C>#xt9GCFeM!V^8)G{@jmf^r>k7J`D2z7>ZI}N|O{s8+!R3RpsV=RfAB_Xj zBQ*I15y?Tx212dZ1b&iu*9ZH9K-6pOp=Xhx##*^i$q6}Y&v9Z=Dc^o<$5qdZ@cpgq z)sgpI_CP8naq*P2{Vj(IdJVW{y|k*uq*Ak_!e3%qcE!OpRA9UO*o*-zXZ)bj^3=Xv G;@FE>oP#z1 diff --git a/lab_3/text.txt b/lab_3/text.txt new file mode 100644 index 00000000..fd13b2ba --- /dev/null +++ b/lab_3/text.txt @@ -0,0 +1,19 @@ +тальник восемь любимых песен + +мне кажется скоро выйдет что то новое у группы тальник никаких точных сведений у меня нет но есть такое чувство весна же на дворе если осень пора обязательных ежегодных альбомов от пореза на собаке то весна самое время для тальника но даже если новинок не будет материала у дуэта и так немало хватит чтобы грамотно встретить приход тепла я решил собрать небольшой плейлистик из любимых песен саши и светы сгодится для начинающих слушателей ну а опытные тальниковеды просто переслушают по кайфу ссылки на плейлист в конце поста + +белая улыбка о песнях группы тальник нередко пишут как о русских поп песнях конца девяностых которые ты слушал вполуха в детстве по радио из соседней комнаты и которые вдруг вплывают в памяти спустя много лет подобной хонтологией творчество дуэта не исчерпывается но одна из её лучших иллюстраций вот этот хит с альбома музыкайф с величайшей строчкой я иду и мятный холодок во рту + +белый белый белый день тальник светлый неотразимый поп номер вроде бы лежащий перед нами как на ладони но в то же время ускользающий куда то прямо на глазах при первом прослушивании мне показалось что в песне поётся уши разморозились к утру а не лужи и с тех пор я не могу перестать об этом думать + +неизбежная близость тальник тёмный деконструированный поп с залипающим в глитче голосом и текстом про тающую тьму кофе жидкую грязь и слишком жидкие гвозди внутри а что не мятным холодком единым + +давай прервёмся тальник лёгкий парящий в закатных облаках брейкбит под который света предлагает прерваться и посмотреть на небо почему почему что оно такое клёвое + +крылья наверное самый прямодушный поп номер группы тальник в придачу чудесный клип который во первых доказывает что ии видео могут быть не кринжовыми если подойди к делу с фантазией а во вторых физически передаёт зрителю ощущение что весна опять пришла и лучики тепла + +рисунки в пустоту коротенькая песня с моим любимым текстом из всего каталога тальника на бетонных берегах промзоны рисунки в пустоту затихшие стоят вагоны и травами цветут цвет надежды песня с которой всё началось маленький обледенелый шедевр как писала афиша в этой песне много пустот и странных провалов в ней всего одна строчка повторяется по кругу но этих девяти слов хватает чтобы сказать сразу обо всем о чём нужно + +всё пронесётся моя любимая песня может быть не только у тальника а вообще ещё один шедевр минимализма сперва мы слышим лишь смурной голос под глубокую басовую линию, затем в воздухе начинают мерцать клавишные будто накрапывает дождь из пустоты нарастает ритм музыка незаметно обрастает деталями, а потом разом накрывает на тебя с головой + +как морская волна текст наполнен ностальгией и сожалением по проходящему времени скроется в прошлом самый важный миг, и вот он уже кажется пошлым но в то же время фраза все пронесется зазвучит успокаивающе и даже обнадеживающе ведь и плохое тоже пронесется мантра и гимн последних лет \ No newline at end of file diff --git a/lab_3/tools.py b/lab_3/tools.py index 4181a32d..0caa5fac 100644 --- a/lab_3/tools.py +++ b/lab_3/tools.py @@ -1,39 +1,89 @@ import json from typing import Any, Dict +import os +from pathlib import Path -def read_txt(file_name: str) -> str: - """Считывает текcт из .txt файла +class Tools: + @staticmethod + def read_txt_binary(file_name: str) -> bytes: + """Считывает текcт из .txt файла - Args: - file_name (str): Путь к файлу + Args: + file_name (str): Путь к файлу - Returns: - str: Содержимое файла - """ - with open(file_name, 'r', encoding="utf-8") as f: - text: str = f.read() - return text + Returns: + str: Содержимое файла + """ + with open(file_name, 'rb') as f: + text: str = f.read() + return text -def save_txt(file_name: str, text: str): - """Удаляет содержимое файла и сохраняет в нём новый текст. - Если файла не существует, создаёт его. - - Args: - file_name (str): Путь к файлу - text (str): Текст, который необходимо сохранить - """ - with open(file_name, 'w', encoding="utf-8") as f: - f.write(text) + @staticmethod + def read_txt(file_name: str) -> str: + """Считывает текcт из .txt файла + + Args: + file_name (str): Путь к файлу + + Returns: + str: Содержимое файла + """ + with open(file_name, 'r', encoding="utf-8") as f: + text: str = f.read() + return text -def read_json(file_name: str) -> Dict[str, Any]: - """Считывает данные из .json файла + @staticmethod + def save_txt(file_name: str, text: str): + """Удаляет содержимое файла и сохраняет в нём новый текст. + Если файла не существует, создаёт его. + + Args: + file_name (str): Путь к файлу + text (str): Текст, который необходимо сохранить + """ + with open(file_name, 'w', encoding="utf-8") as f: + f.write(text) + + @staticmethod + def save_txt_binary(file_name: str, text: bytes): + """Удаляет содержимое файла и сохраняет в нём новый текст. + Если файла не существует, создаёт его. - Args: - file_name (str): Путь к файлу + Args: + file_name (str): Путь к файлу + text (str): Текст, который необходимо сохранить + """ + with open(file_name, 'wb') as f: + f.write(text) - Returns: - Dict[str, Any]: Словарь объектов, содеражавшихся в .json файле - """ - with open(file_name, 'r', encoding='utf-8') as f: - return(json.load(f)) \ No newline at end of file + @staticmethod + def read_json(file_name: str) -> Dict[str, Any]: + """Считывает данные из .json файла + + Args: + file_name (str): Путь к файлу + + Returns: + Dict[str, Any]: Словарь объектов, содеражавшихся в .json файле + """ + with open(file_name, 'r', encoding='utf-8') as f: + return(json.load(f)) + + @staticmethod + def is_file_exists(file_path: str): + """Проверяет, существует ли файл + Args: + file_path (str): Путь к файлу + Raises: + FileNotFoundError: Если файл не существует + ValueError: Не является файлом + PermissionError: Нету доступа к файлу + """ + path = Path(file_path) + if not path.exists(): + raise FileNotFoundError(f"Файл не найден: {file_path}") + if not path.is_file(): + raise ValueError(f"Не является файлом: {file_path}") + if not os.access(file_path, os.R_OK): + raise PermissionError(f"Нету доступа к файлу: {file_path}") \ No newline at end of file From 98860ce9c41bd13be910a37bdf968239a8dc9a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=BC=D0=B0=D0=B5=D0=B2=20=D0=9C=D0=B0=D0=BA?= =?UTF-8?q?=D1=81=D0=B8=D0=BC?= Date: Sat, 27 Sep 2025 09:37:05 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=BC=D0=B0=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D1=83?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BE?= =?UTF-8?q?=20=20=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B8=20=D1=81=D0=B2=D0=BE=D0=B8=D1=85=20=D1=8D=D1=82=D0=BF?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_3/ctext.txt | Bin 5360 -> 5360 bytes lab_3/hybrid_cryptosystem.py | 11 ++++++++ lab_3/iv.txt | 2 +- lab_3/private_key.pem | 50 +++++++++++++++++------------------ lab_3/public_key.pem | 14 +++++----- lab_3/sym_key.txt | Bin 256 -> 256 bytes 6 files changed, 44 insertions(+), 33 deletions(-) diff --git a/lab_3/ctext.txt b/lab_3/ctext.txt index 969bbe13190dd1e191b14383e53d305e3fc585e6..a8508831bfb854742549c1a1408be79b80e35e3c 100644 GIT binary patch literal 5360 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 G4NH)Zbb*!t literal 256 zcmV+b0ssCUJz97%5Hrc4FwTpXK{T{9b;lPOOA)5F=}-SW-d{(gz#0(d2(p|jXZ8hz;m0R9W#7>Qa_T81Ih`C#qi zdMWADmY#95*>lBMD$todyiIyc|BmO9=aD{yggvRUyoOfpQ;c~^