diff --git a/lab_3/.gitignore b/lab_3/.gitignore new file mode 100644 index 000000000..1800114dc --- /dev/null +++ b/lab_3/.gitignore @@ -0,0 +1,174 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# 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 + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.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/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc \ No newline at end of file diff --git a/lab_3/asymmetric_cipher.py b/lab_3/asymmetric_cipher.py new file mode 100644 index 000000000..1b44869c4 --- /dev/null +++ b/lab_3/asymmetric_cipher.py @@ -0,0 +1,88 @@ +import os +from typing import Tuple +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey, RSAPrivateKey +from cryptography.hazmat.primitives import serialization, hashes + + +def generate_rsa_keypair() -> Tuple[RSAPrivateKey, RSAPublicKey]: + """ + Generates a new RSA private-public key pair. + + :returns: A tuple containing the RSA private key and corresponding public key. + """ + private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + return private_key, private_key.public_key() + + +def encrypt(key: bytes, public_key: RSAPublicKey) -> bytes: + """ + Encrypts data using the given RSA public key with OAEP padding. + + :param key: Symmetric key or data to encrypt. + :param public_key: RSA public key used for encryption. + :returns: Encrypted data as bytes. + """ + return public_key.encrypt( + key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) + + +def decrypt(encrypted_key: bytes, private_key: RSAPrivateKey) -> bytes: + """ + Decrypts data using the given RSA private key with OAEP padding. + + :param encrypted_key: Encrypted data to decrypt. + :param private_key: RSA private key used for decryption. + :returns: Decrypted data as bytes. + """ + return private_key.decrypt( + encrypted_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) + + +def serialize_public_key(public_key: RSAPublicKey) -> bytes: + """ + Serializes an RSA public key to PEM format. + + :param public_key: RSA public key to serialize. + :returns: Serialized public key in PEM format. + """ + return public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + + +def serialize_private_key(private_key: RSAPrivateKey) -> bytes: + """ + Serializes an RSA private key to PEM format without encryption. + + :param private_key: RSA private key to serialize. + :returns: Serialized private key in PEM format. + """ + return private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + ) + + +def load_private_key(data: bytes) -> RSAPrivateKey: + """ + Loads an RSA private key from PEM-encoded data. + + :param data: PEM-encoded private key bytes. + :returns: Deserialized RSA private key object. + """ + return serialization.load_pem_private_key(data, password=None) \ No newline at end of file diff --git a/lab_3/data.txt b/lab_3/data.txt new file mode 100644 index 000000000..ddbf13c3d --- /dev/null +++ b/lab_3/data.txt @@ -0,0 +1,99 @@ +Вы помните, +Вы всё, конечно, помните, +Как я стоял, +Приблизившись к стене, +Взволнованно ходили вы по комнате +И что-то резкое +В лицо бросали мне. +Вы говорили: +Нам пора расстаться, +Что вас измучила +Моя шальная жизнь, +Что вам пора за дело приниматься, +А мой удел — +Катиться дальше, вниз. +Любимая! +Меня вы не любили. +Не знали вы, что в сонмище людском +Я был как лошадь, загнанная в мыле, +Пришпоренная смелым ездоком. +Не знали вы, +Что я в сплошном дыму, +В развороченном бурей быте +С того и мучаюсь, что не пойму — +Куда несет нас рок событий. +Лицом к лицу +Лица не увидать. +Большое видится на расстоянье. +Когда кипит морская гладь — +Корабль в плачевном состоянье. +Земля — корабль! +Но кто-то вдруг +За новой жизнью, новой славой +В прямую гущу бурь и вьюг +Ее направил величаво. +Ну кто ж из нас на палубе большой +Не падал, не блевал и не ругался? +Их мало, с опытной душой, +Кто крепким в качке оставался. +Тогда и я, +Под дикий шум, +Но зрело знающий работу, +Спустился в корабельный трюм, +Чтоб не смотреть людскую рвоту. +Тот трюм был — +Русским кабаком. +И я склонился над стаканом, +Чтоб, не страдая ни о ком, +Себя сгубить +В угаре пьяном. +Любимая! +Я мучил вас, +У вас была тоска +В глазах усталых: +Что я пред вами напоказ +Себя растрачивал в скандалах. +Но вы не знали, +Что в сплошном дыму, +В развороченном бурей быте +С того и мучаюсь, +Что не пойму, +Куда несет нас рок событий… +Теперь года прошли. +Я в возрасте ином. +И чувствую и мыслю по-иному. +И говорю за праздничным вином: +Хвала и слава рулевому! +Сегодня я +В ударе нежных чувств. +Я вспомнил вашу грустную усталость. +И вот теперь +Я сообщить вам мчусь, +Каков я был, +И что со мною сталось! +Любимая! +Сказать приятно мне: +Я избежал паденья с кручи. +Теперь в Советской стороне +Я самый яростный попутчик. +Я стал не тем, +Кем был тогда. +Не мучил бы я вас, +Как это было раньше. +За знамя вольности +И светлого труда +Готов идти хоть до Ла-Манша. +Простите мне… +Я знаю: вы не та — +Живете вы +С серьезным, умным мужем; +Что не нужна вам наша маета, +И сам я вам +Ни капельки не нужен. +Живите так, +Как вас ведет звезда, +Под кущей обновленной сени. +С приветствием, +Вас помнящий всегда +Знакомый ваш +Сергей Есенин. \ No newline at end of file diff --git a/lab_3/encrypted_data.txt b/lab_3/encrypted_data.txt new file mode 100644 index 000000000..ae99d7e5e Binary files /dev/null and b/lab_3/encrypted_data.txt differ diff --git a/lab_3/encrypted_key.txt b/lab_3/encrypted_key.txt new file mode 100644 index 000000000..8999a4f95 --- /dev/null +++ b/lab_3/encrypted_key.txt @@ -0,0 +1,4 @@ +/S +pI +H˻{֍9;3mUBfOa9U2AzDDQp[ >g(1UJrXFl2W-|1R9ztwl祷MW7.'zf٢UXI<+лJzI׫Fs]'.{`4kwfn縃׈Ro9"ȍ4M7X|x pZ\}`1s/ ~83 +^ \ No newline at end of file diff --git a/lab_3/file_utils.py b/lab_3/file_utils.py new file mode 100644 index 000000000..a52a2c5df --- /dev/null +++ b/lab_3/file_utils.py @@ -0,0 +1,38 @@ +import os +from typing import List + + +def read_file(path: str) -> bytes: + """ + Reads binary data from the specified file path. + + :param path: Path to the file to be read. + :returns: Contents of the file as bytes. + """ + with open(path, 'rb') as file: + return file.read() + + +def write_file(path: str, data: bytes) -> None: + """ + Writes binary data to the specified file path. + + :param path: Path to the file where data should be written. + :param data: Data to write to the file. + """ + with open(path, 'wb') as file: + file.write(data) + + +def ensure_exists(paths: List[str]) -> None: + """ + Checks whether all specified file paths exist. + Terminates the program if any of the files is missing. + + :param paths: List of file paths to check. + :raises SystemExit: If any file does not exist. + """ + for path in paths: + if not os.path.exists(path): + print(f"[!] File not found: {path}") + exit(1) \ No newline at end of file diff --git a/lab_3/helpers.py b/lab_3/helpers.py new file mode 100644 index 000000000..fa63930b7 --- /dev/null +++ b/lab_3/helpers.py @@ -0,0 +1,32 @@ +import argparse +import json +from typing import Any, Dict + + +def load_config() -> Dict[str, Any]: + """ + Loads configuration settings from the 'settings.json' file. + + :returns: A dictionary containing configuration settings. + :raises SystemExit: If the file 'settings.json' is not found. + """ + try: + with open('settings.json', 'r') as file: + return json.load(file) + except FileNotFoundError: + print("[!] settings.json file not found.") + exit(1) + + +def get_args() -> argparse.Namespace: + """ + Parses command-line arguments for hybrid cryptosystem operations. + + :returns: Parsed command-line arguments as a namespace object. + """ + parser = argparse.ArgumentParser(description="Hybrid Cryptosystem") + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument('-gen', '--create_keys', action='store_true', help='Generate keys') + mode.add_argument('-enc', '--encrypt', action='store_true', help='Encrypt data') + mode.add_argument('-dec', '--decrypt', action='store_true', help='Decrypt data') + return parser.parse_args() \ No newline at end of file diff --git a/lab_3/main.py b/lab_3/main.py new file mode 100644 index 000000000..912b195f2 --- /dev/null +++ b/lab_3/main.py @@ -0,0 +1,85 @@ +import helpers +import file_utils +import asymmetric_cipher +import symmetric_cipher +import os +from typing import Dict, Any + + +def create_keys(config: Dict[str, Any]) -> None: + """ + Generates a symmetric AES key and an RSA key pair, encrypts the symmetric key + with the RSA public key, and saves all keys to files. + + :param config: Configuration dictionary containing the path to store the encrypted symmetric key. + """ + print("[*] Generating keys...") + sym_key = os.urandom(16) + priv_key, pub_key = asymmetric_cipher.generate_rsa_keypair() + encrypted_sym_key = asymmetric_cipher.encrypt(sym_key, pub_key) + + file_utils.write_file("rsa_pub_key.txt", asymmetric_cipher.serialize_public_key(pub_key)) + file_utils.write_file("rsa_private_key.txt", asymmetric_cipher.serialize_private_key(priv_key)) + file_utils.write_file(config['encoded_sym_key'], encrypted_sym_key) + + print("[+] Keys successfully generated.") + + +def encrypt_data(config: Dict[str, Any]) -> None: + """ + Encrypts plaintext data using a symmetric key that is decrypted from a previously + RSA-encrypted file. Saves the encrypted result to a file. + + :param config: Configuration dictionary containing paths to the RSA private key, + encrypted symmetric key, plaintext input, and encrypted output. + """ + print("[*] Loading data for encryption...") + file_utils.ensure_exists([config['rsa_private'], config['encoded_sym_key'], config['plain_data']]) + + private_key = asymmetric_cipher.load_private_key(file_utils.read_file(config['rsa_private'])) + sym_key = asymmetric_cipher.decrypt(file_utils.read_file(config['encoded_sym_key']), private_key) + plaintext = file_utils.read_file(config['plain_data']) + encrypted = symmetric_cipher.encrypt(plaintext, sym_key) + + file_utils.write_file(config['encrypted_data'], encrypted) + print("[+] Data encrypted successfully.") + + +def decrypt_data(config: Dict[str, Any]) -> None: + """ + Decrypts encrypted data using a symmetric key that is decrypted from a file + encrypted with RSA. Saves the decrypted output to a file. + + :param config: Configuration dictionary containing paths to the RSA private key, + encrypted symmetric key, encrypted input, and decrypted output. + """ + print("[*] Loading data for decryption...") + file_utils.ensure_exists([config['rsa_private'], config['encoded_sym_key'], config['encrypted_data']]) + + private_key = asymmetric_cipher.load_private_key(file_utils.read_file(config['rsa_private'])) + sym_key = asymmetric_cipher.decrypt(file_utils.read_file(config['encoded_sym_key']), private_key) + encrypted_data = file_utils.read_file(config['encrypted_data']) + decrypted = symmetric_cipher.decrypt(encrypted_data, sym_key) + + file_utils.write_file(config['decrypted_output'], decrypted) + print("[+] Data decrypted successfully.") + + +def main() -> None: + """ + Parses command-line arguments and performs the selected action: + key generation, encryption, or decryption. + """ + args = helpers.get_args() + config = helpers.load_config() + + if args.create_keys: + create_keys(config) + elif args.encrypt: + encrypt_data(config) + elif args.decrypt: + decrypt_data(config) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/lab_3/output.txt b/lab_3/output.txt new file mode 100644 index 000000000..ddbf13c3d --- /dev/null +++ b/lab_3/output.txt @@ -0,0 +1,99 @@ +Вы помните, +Вы всё, конечно, помните, +Как я стоял, +Приблизившись к стене, +Взволнованно ходили вы по комнате +И что-то резкое +В лицо бросали мне. +Вы говорили: +Нам пора расстаться, +Что вас измучила +Моя шальная жизнь, +Что вам пора за дело приниматься, +А мой удел — +Катиться дальше, вниз. +Любимая! +Меня вы не любили. +Не знали вы, что в сонмище людском +Я был как лошадь, загнанная в мыле, +Пришпоренная смелым ездоком. +Не знали вы, +Что я в сплошном дыму, +В развороченном бурей быте +С того и мучаюсь, что не пойму — +Куда несет нас рок событий. +Лицом к лицу +Лица не увидать. +Большое видится на расстоянье. +Когда кипит морская гладь — +Корабль в плачевном состоянье. +Земля — корабль! +Но кто-то вдруг +За новой жизнью, новой славой +В прямую гущу бурь и вьюг +Ее направил величаво. +Ну кто ж из нас на палубе большой +Не падал, не блевал и не ругался? +Их мало, с опытной душой, +Кто крепким в качке оставался. +Тогда и я, +Под дикий шум, +Но зрело знающий работу, +Спустился в корабельный трюм, +Чтоб не смотреть людскую рвоту. +Тот трюм был — +Русским кабаком. +И я склонился над стаканом, +Чтоб, не страдая ни о ком, +Себя сгубить +В угаре пьяном. +Любимая! +Я мучил вас, +У вас была тоска +В глазах усталых: +Что я пред вами напоказ +Себя растрачивал в скандалах. +Но вы не знали, +Что в сплошном дыму, +В развороченном бурей быте +С того и мучаюсь, +Что не пойму, +Куда несет нас рок событий… +Теперь года прошли. +Я в возрасте ином. +И чувствую и мыслю по-иному. +И говорю за праздничным вином: +Хвала и слава рулевому! +Сегодня я +В ударе нежных чувств. +Я вспомнил вашу грустную усталость. +И вот теперь +Я сообщить вам мчусь, +Каков я был, +И что со мною сталось! +Любимая! +Сказать приятно мне: +Я избежал паденья с кручи. +Теперь в Советской стороне +Я самый яростный попутчик. +Я стал не тем, +Кем был тогда. +Не мучил бы я вас, +Как это было раньше. +За знамя вольности +И светлого труда +Готов идти хоть до Ла-Манша. +Простите мне… +Я знаю: вы не та — +Живете вы +С серьезным, умным мужем; +Что не нужна вам наша маета, +И сам я вам +Ни капельки не нужен. +Живите так, +Как вас ведет звезда, +Под кущей обновленной сени. +С приветствием, +Вас помнящий всегда +Знакомый ваш +Сергей Есенин. \ No newline at end of file diff --git a/lab_3/rsa_private_key.txt b/lab_3/rsa_private_key.txt new file mode 100644 index 000000000..b09a83ddc --- /dev/null +++ b/lab_3/rsa_private_key.txt @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAqu7SPXGk36KgM4kKtwhKCyDZINa7uHZuAmQcNF/NlnFU9/o3 +liuseD0LbgAbXhp85lQHaP0rjsSI9cT0mFmMSpHWeWccZsIPvnSZrGi214zDY9z3 +lQSnEtF5j1g+CdGr/VCF/UnCLqN4mZbeeM4t1r1XNV1Xd/JJdhFd/ThbFWF0Micn +sTPoLfEQXDE6hZtxh67xC5wE+qqGRrPEjdbnSZG0E4/ThSSiXv0omHlTm9xg5vBr +9+/jeWz2JAJw1TphYSQVB0SogSXQvYBoZjPT4xMu3GrTGMmCHeSlqBKNU9L9ENeD +GP6N9hsxGnvexrKjl4Vg8HYqQU5/WgV+EJrRRwIDAQABAoIBAC5sqaYtgOmKSjdk +/4eBlVabZ81e9OIfLDazCOQCpxw2kKy/BSzglLmdXsbT+Zrf189YwCB4LkXaxBX4 +FbRxEcTJAja71f9kspD4Kyqj0BEP9ptfTtDm0Bew0/KmNUdGbo9guNt3lmlDVcRl +MOwfl1yM1Sw1u0VlBSWvhtb44OgXuqgplbTgw0Lfc+iTkQ4sGH3U/a0C+4LaEhUr ++R8KvJLaI4nDYWg8esoev0geFrnS5l+pG1ZiTrk2Jy2K8N/71/D3pwd1Kh/NIX4T +dLR464A4qYATQBXMepda/rS13RMQBNd2B2SNqLHFn1nJx2sj0Ef7gzIqWX9Dzq04 +Ftt+PXECgYEA7y3MT1katLaGF4PzW0ev2jNDSQzz7EPga3ATsco4FIobavPPhwEF +191nwXp0MAbeOiARo/scnlapEjskJNDwFN7raZzXmyAV8J0Elrz0ldluRFqUqScr +tpikPZN9r2kmQjNN7ZYrvQzUZ1+JNDBA+iEyDv8yw1PMLjnyANGJWbECgYEAtvRQ +oq04pW5NsyPM1Nq1R94uz67afCu/Biyuhf71B6NTtGWSY0Kd/SUwvALwmJAOy9mr +GRVCSVXPaYUCa2ooZosooESMDWpkfYLER+K9LrQ3qfhFsn8/FMPxfxqMmZ2ZE+Zr +on1vwOeTKCx7Hd0uPQAEwr3CWKFHHqKqLD5MIHcCgYBqCXTd7NQRMCaMwFwgqMyG +wVlgRpTRt1oEZ8DjfpKUUJJNPWBRKxvrEuaP+XkPXcwiGtuXRpnufN8iDQQ+Kj92 +EUpvHTDdPkFb1lEVfxo0YRwow7vKrmoL3upZvkzneoeSq+otnfkwLELyvYE3mPkF +q7fkvDFGNZ19FQV8ZWkuUQKBgF3X08SdtYClo5VPt7E2veWvQbOlrMwkZEd2g86X +iHfyfuz5bK0dbox6lxTZLTAUNIK7k9e539Zd/ZXSc/tE6JQwrRYIEw6OlB0NBkEZ +PJDIQAUfJCD6xBCSsTzREW16ORrMBknCrZ/1KLZimoQm/6CqccayLw3nK+O67C7l +hYbhAoGAeiqj3ICjEO58Dp4t8CQABOOTHcqJD5IdeTiq9J0jokjqQfikB4WVpQY+ +H5FIUeDvK0DKIG8XqTBh+gX/l/Ao2CuezI+XUGTbCRK2V+WSKeom654PeNU8eIdu +bqpvLumpOzQOwtOe47OIKkd40go2QKZ/nOGHWyvqZmQN/u1JCVo= +-----END RSA PRIVATE KEY----- diff --git a/lab_3/rsa_pub_key.txt b/lab_3/rsa_pub_key.txt new file mode 100644 index 000000000..607275f5d --- /dev/null +++ b/lab_3/rsa_pub_key.txt @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqu7SPXGk36KgM4kKtwhK +CyDZINa7uHZuAmQcNF/NlnFU9/o3liuseD0LbgAbXhp85lQHaP0rjsSI9cT0mFmM +SpHWeWccZsIPvnSZrGi214zDY9z3lQSnEtF5j1g+CdGr/VCF/UnCLqN4mZbeeM4t +1r1XNV1Xd/JJdhFd/ThbFWF0MicnsTPoLfEQXDE6hZtxh67xC5wE+qqGRrPEjdbn +SZG0E4/ThSSiXv0omHlTm9xg5vBr9+/jeWz2JAJw1TphYSQVB0SogSXQvYBoZjPT +4xMu3GrTGMmCHeSlqBKNU9L9ENeDGP6N9hsxGnvexrKjl4Vg8HYqQU5/WgV+EJrR +RwIDAQAB +-----END PUBLIC KEY----- diff --git a/lab_3/settings.json b/lab_3/settings.json new file mode 100644 index 000000000..8fb8a0b4d --- /dev/null +++ b/lab_3/settings.json @@ -0,0 +1,7 @@ +{ + "encoded_sym_key": "encrypted_key.txt", + "rsa_private": "rsa_private_key.txt", + "plain_data": "data.txt", + "encrypted_data": "encrypted_data.txt", + "decrypted_output": "output.txt" +} \ No newline at end of file diff --git a/lab_3/symmetric_cipher.py b/lab_3/symmetric_cipher.py new file mode 100644 index 000000000..10f01c77d --- /dev/null +++ b/lab_3/symmetric_cipher.py @@ -0,0 +1,48 @@ +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives import padding as sym_pad +import os + + +def encrypt(data: bytes, key: bytes) -> bytes: + """Encrypt data using SEED algorithm in CBC mode with ANSIX923 padding. + + :param data: Raw bytes to be encrypted. Length must be compatible with padding + :param key: Encryption key (16 bytes for SEED-128, 32 bytes for SEED-256) + :return: Encrypted data in format: IV (16 bytes) + ciphertext + :raises ValueError: If key length is invalid for SEED algorithm + :raises TypeError: If input data is not bytes + """ + if len(key) not in {16, 32}: + raise ValueError("SEED key must be 16 or 32 bytes long") + + padder = sym_pad.ANSIX923(128).padder() + padded = padder.update(data) + padder.finalize() + iv = os.urandom(16) + cipher = Cipher(algorithms.SEED(key), modes.CBC(iv)) + encryptor = cipher.encryptor() + encrypted = encryptor.update(padded) + encryptor.finalize() + return iv + encrypted + + +def decrypt(encrypted_data: bytes, key: bytes) -> bytes: + """Decrypt data encrypted with SEED-CBC-ANSIX923. + + :param encrypted_data: Encrypted data in format: IV (16 bytes) + ciphertext + :param key: Decryption key (same as used for encryption) + :return: Original decrypted data + :raises ValueError: If key is invalid or encrypted_data is too short + :raises TypeError: If inputs are not bytes + :raises PaddingError: If padding is corrupted + """ + if len(encrypted_data) < 16: + raise ValueError("Encrypted data must contain at least 16 bytes (IV)") + if len(key) not in {16, 32}: + raise ValueError("SEED key must be 16 or 32 bytes long") + + iv = encrypted_data[:16] + content = encrypted_data[16:] + cipher = Cipher(algorithms.SEED(key), modes.CBC(iv)) + decryptor = cipher.decryptor() + decrypted_padded = decryptor.update(content) + decryptor.finalize() + unpadder = sym_pad.ANSIX923(128).unpadder() + return unpadder.update(decrypted_padded) + unpadder.finalize() \ No newline at end of file