diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..de61102d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/lab_2/task_1/ +/lab_2/task_2/ diff --git a/lab_3/cryptosystem/asymmetric.py b/lab_3/cryptosystem/asymmetric.py new file mode 100644 index 000000000..d4ba2493e --- /dev/null +++ b/lab_3/cryptosystem/asymmetric.py @@ -0,0 +1,98 @@ +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.backends import default_backend +from cryptography.exceptions import UnsupportedAlgorithm + +from file_manager import FileManager + + +class AsymmetricRSA: + @staticmethod + def generate_keys() -> (rsa.RSAPrivateKey, rsa.RSAPublicKey): + """ + Generates public and private RSA keys + :return: Private key, public key + """ + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048 + ) + public_key = private_key.public_key() + return private_key, public_key + + @staticmethod + def export_keys(private_key: rsa.RSAPrivateKey, public_key: rsa.RSAPublicKey, + private_path: str, public_path: str) -> None: + """ + Serialization of asymmetric keys + :param private_key: Private key + :param public_key: Public key + :param private_path: Path to file to save private key + :param public_path: Path to file to save public key + :return: None + """ + private_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + public_pem = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + FileManager.save_bytes(private_path, private_pem) + FileManager.save_bytes(public_path, public_pem) + + @staticmethod + def load_private_key(path: str) -> rsa.RSAPrivateKey: + """ + Loads a private key from a file and deserializes it + :param path: Path to file to load private key + :return: private key + """ + try: + key_data = FileManager.load_bytes(path) + private_key = serialization.load_pem_private_key( + key_data, + password=None, + backend=default_backend() + ) + return private_key + except UnsupportedAlgorithm: + raise Exception("Unsupported key algorithm") + except ValueError: + raise Exception("Invalid key format") + + @staticmethod + def encrypt(data: bytes, public_key: rsa.RSAPublicKey) -> bytes: + """ + Encrypting data with public RSA key with using OAEP encrypting standard with SHA256 hash-algorithm + :param data: Data for encryption + :param public_key: Public key + :return: Encrypted data + """ + return public_key.encrypt( + data, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) + + @staticmethod + def decrypt(data: bytes, private_key: rsa.RSAPrivateKey) -> bytes: + """ + Decrypting data with public RSA key with using OAEP encrypting standard with SHA256 hash-algorithm + :param data: Data for decryption + :param private_key: Private key + :return: Decrypted data + """ + return private_key.decrypt( + data, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) \ No newline at end of file diff --git a/lab_3/cryptosystem/hybrid_system.py b/lab_3/cryptosystem/hybrid_system.py new file mode 100644 index 000000000..85965290e --- /dev/null +++ b/lab_3/cryptosystem/hybrid_system.py @@ -0,0 +1,60 @@ +from .asymmetric import AsymmetricRSA +from file_manager import FileManager +from .symmetric import Symmetric3DES + + +class HybridCryptoSystem: + + @staticmethod + def generate_keys(key_length: int, private_path: str, public_path: str, symmetric_path: str) -> None: + """ + Hybrid System Key Generation. + :param key_length: Key length for 3DES (64, 128, 192) + :param private_path: Path to file to save private key + :param public_path: Path to file to save public key + :param symmetric_path: Path to the file to save the encrypted symmetric key + :return: None + """ + private_key, public_key = AsymmetricRSA.generate_keys() + symmetric_key = Symmetric3DES.generate_key(key_length) + + AsymmetricRSA.export_keys(private_key, public_key, private_path, public_path) + c_symmetric_key = AsymmetricRSA.encrypt(symmetric_key, public_key) + FileManager.save_bytes(symmetric_path, c_symmetric_key) + + @staticmethod + def encrypt(file_path: str, private_path: str, symmetric_path: str, save_path: str) -> None: + """ + Data encryption by hybrid system + :param file_path: Path to the file to encrypt + :param private_path: Path to file to load private key + :param symmetric_path: Path to the file to load the encrypted symmetric key + :param save_path: Path to file to save encrypted data + :return: + """ + c_symmetric_key = FileManager.load_bytes(symmetric_path) + private_key = AsymmetricRSA.load_private_key(private_path) + text = FileManager.read_file(file_path) + + symmetric_key = AsymmetricRSA.decrypt(c_symmetric_key, private_key) + encrypted_text = Symmetric3DES.encrypt(text.encode('utf-8'), symmetric_key) + FileManager.save_bytes(save_path, encrypted_text) + + @staticmethod + def decrypt(file_path: str, private_path: str, symmetric_path: str, save_path: str) -> None: + """ + Data decryption by hybrid system + :param file_path: Path to the file to decrypt + :param private_path: Path to file to load private key + :param symmetric_path: Path to the file to load the encrypted symmetric key + :param save_path: Path to file to save decrypted data + :return: + """ + c_symmetric_key = FileManager.load_bytes(symmetric_path) + private_key = AsymmetricRSA.load_private_key(private_path) + text = FileManager.load_bytes(file_path) + + symmetric_key = AsymmetricRSA.decrypt(c_symmetric_key, private_key) + decrypted_text = Symmetric3DES.decrypt(text, symmetric_key) + FileManager.write_file(save_path, decrypted_text.decode('utf-8')) + diff --git a/lab_3/cryptosystem/symmetric.py b/lab_3/cryptosystem/symmetric.py new file mode 100644 index 000000000..393358022 --- /dev/null +++ b/lab_3/cryptosystem/symmetric.py @@ -0,0 +1,59 @@ +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives import padding as sym_padding +from cryptography.hazmat.backends import default_backend +import os + + +class Symmetric3DES: + @staticmethod + def generate_key(key_length: int) -> bytes: + """ + Generate a 3DES key of the selected length + :param key_length: Key length (64, 128, 192) + :return: Unencrypted symmetric key + """ + return os.urandom(key_length // 8) + + @staticmethod + def encrypt(data: bytes, key: bytes) -> bytes: + """ + Encrypting data with 3DES algorythm in Cipher Block Chaining mode with random initialization vector + :param data: Data for encryption + :param key: Symmetric key + :return: Encrypted data + """ + iv = os.urandom(8) # 3DES block size + padder = sym_padding.PKCS7(algorithms.TripleDES.block_size).padder() # 64 bits = 8 bytes block size + padded_data = padder.update(data) + padder.finalize() + + cipher = Cipher( + algorithms.TripleDES(key), + modes.CBC(iv), + backend=default_backend() + ) + encryptor = cipher.encryptor() + ciphertext = encryptor.update(padded_data) + encryptor.finalize() + return iv + ciphertext + + @staticmethod + def decrypt(data: bytes, key: bytes) -> bytes: + """ + Decrypting data with 3DES algorythm in Cipher Block Chaining mode with random initialization vector + :param data: Data for decryption + :param key: Symmetric key + :return: Decrypted data + """ + iv = data[:8] + ct = data[8:] + + cipher = Cipher( + algorithms.TripleDES(key), + modes.CBC(iv), + backend=default_backend() + ) + decryptor = cipher.decryptor() + padded_data = decryptor.update(ct) + decryptor.finalize() + + unpadder = sym_padding.PKCS7(algorithms.TripleDES.block_size).unpadder() + data = unpadder.update(padded_data) + unpadder.finalize() + return data \ No newline at end of file diff --git a/lab_3/file_manager.py b/lab_3/file_manager.py new file mode 100644 index 000000000..f595e97ed --- /dev/null +++ b/lab_3/file_manager.py @@ -0,0 +1,81 @@ +import json + + +class FileManager: + + @staticmethod + def save_bytes(path: str, data: bytes) -> None: + """ + Writing data to a file + :param path: path to file to save + :param data: data to save + :return: None + """ + try: + with open(path, "wb") as f: + f.write(data) + except FileNotFoundError: + raise FileNotFoundError(f"The file was not found: {path}") + except Exception as e: + raise Exception(f"An error occurred with the file: {str(e)}") + + @staticmethod + def load_bytes(path: str) -> bytes: + """ + Reading data from a file + :param path: path to the file + :return: bytes format object + """ + try: + with open(path, "rb") as f: + return f.read() + except FileNotFoundError: + raise FileNotFoundError(f"The file was not found: {path}") + except Exception as e: + raise Exception(f"An error occurred with the file: {str(e)}") + + @staticmethod + def load_json(json_path: str) -> dict: + """ + Method read json file as a dict with requirements + :param json_path: path of json file + :return: dictionary + """ + try: + with open(json_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + raise FileNotFoundError(f"JSON file not found: {json_path}") + except json.JSONDecodeError: + raise ValueError(f"Invalid JSON format in file: {json_path}") + + @staticmethod + def read_file(filename: str) -> str: + """ + Opens text file from given directory + :param filename: Directory to file + :return: String with file contents + """ + try: + with open(filename, "r", encoding="utf-8") as file: + return file.read() + except FileNotFoundError as e: + print(f"File doesn't exist or the path specified is invalid: {e}") + except PermissionError as e: + print(f"Can't access this file: {e}") + except Exception as e: + print(f"Error reading file: {e}. Check for correct data") + + @staticmethod + def write_file(filename: str, data: str) -> None: + """ + Saves string to .txt file + :param filename: Path to .txt file + :param data: Contents for file + :return: None + """ + try: + with open(filename, "w", encoding="utf-8") as file: + file.write(data) + except Exception as e: + print(f"Something went wrong: {e}") \ No newline at end of file diff --git a/lab_3/keys/private_key.pem b/lab_3/keys/private_key.pem new file mode 100644 index 000000000..8ab3c28fc --- /dev/null +++ b/lab_3/keys/private_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCqRA81cygbBkUu +kipVeVHcdF/xAnpBt6vm9XtQw/d0pGU1djf80yILYNlfXH2FBDMDSvVzADawEsK2 +Tde8vaHuImdqm0U4m4RuBAxHJiLtRK2ABWg3Ep/hXPdYhogoNMYi6PjP5AFC5xsZ +6123bC5M+BO3c47W7vtWsqqBCrtkOr//6uLyJ0E1BK/EnTMLLFhQ9BJV83KHFuvc +kJI6ds1BB1ZUtgPbAOtW0ayKtQi2sgJUTiL9LeeeqV21NxRCq6Nxaku5EvRTYOWd +1zZILqgkUqnQRZD1L0JXtRcH/y4Fob7gJgvbkmPhI2O6Mi8PJQjr8eMqzDstgAae +7cvblfazAgMBAAECggEAR8jK+rdy1LX3i/Nu6rtsAnyECJoJTlSwIn8jvwDn/uLi +ksAlSbAAPfjWnIhjmSWUllJPmm0gIWq/cdnu13HB6CLUJBOTgxK6KiIFxSd0eUFH +vt5IulNdWcf2tnl9xSm+0XAUmp1f1MOX3v0m9VKkUKoUsfcD5WU7TSmljiMmr1IP +F25hOnvNJVpPjuE2fwu2OLX6Dz7y+/Q7HMJNt8E9Nn35kEC1gTLjL906L7f6ZV9b +MY6kXwGeOLN2/8voIBNJVOYJgzY8QdugB18TOBQyilYJWgFoQV4og/GG0/K+ymY2 +e0B2F02t5VXv/GGmR79ezHkigwUAAvJV3uqdqCQAxQKBgQDhkkA2I/qzUp9g1qgc +zSapHpUhMm5JJ8JukHwj30yzH05IZWtkbAzlgrDz6tjlcxkWs5uwY8+7KGc6QX48 +1HVjVXw5M4qD2LW9q7fy1gYv6m5Pp+SO/tlNT2LbSK06QoHaz3uNEOgilyYUxyoU +kx92AlDh8mw92EnijDRj3h9H3wKBgQDBO+wqAGn1KBkAZ+Slpmygbcp4cJ6rxnen +lCPcw28/fI+f+pCLVpUR+Q4E4a2GGxQgntKIj4mkvlNM3o3uStkGjpw5nj2iD4S9 +P6gGbdUA7/cBeejdSKEgTegTYlId466jxlUazyuHiqu9ahnkGDF8SeO5Gou4flTe +0ZJvuV47rQKBgHyPAvAijARLooCZ5/kHe8q1fYn4TBgPYXkmRbaVTsg2iEbH4jZw +x+pQcaAvVZfWJ8t2YIlVhFcH54Cuu6OhejTg9pirklhd6XWUBh6M+puo60MHJdmk +dqAPLzqBdk6OfSAzpDjwVg8LwdaFaAI2f4/tlXY/JHA+KAZ2f1OKS2GnAoGAD7Wf +bYq7EoNABRhtLkppamGCpGgDflOURrt0bu40jSTDSG5Gcg2H8P4edacjRFPPPxeq +Zg/FUO9oNkehok3TdwUBDm4e9J3uXLRgJKWpO3pGyofuto7BCq9KvsivhF6ORCJL +qPJOx6YucCfAExskasZXDSVrVoRuwe6nyQ1468ECgYBCsicjla8vXI9ujPOpEBYB +qPldXWAjxz56ZHtabQ68ryWlYbKf3LHukPse9hsjRVG9T640qkQA22mfBEWpFwPK +jmi0hH33B4XYbHpIXMij0c+kPYJOkURlavQY4DCSWHGeAd5Mcw0w6Bp/PVo9HLL6 +4MfMMxC20NcHQ2AvQ7l23Q== +-----END PRIVATE KEY----- diff --git a/lab_3/keys/public_key.pem b/lab_3/keys/public_key.pem new file mode 100644 index 000000000..e15e84aeb --- /dev/null +++ b/lab_3/keys/public_key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqkQPNXMoGwZFLpIqVXlR +3HRf8QJ6Qber5vV7UMP3dKRlNXY3/NMiC2DZX1x9hQQzA0r1cwA2sBLCtk3XvL2h +7iJnaptFOJuEbgQMRyYi7UStgAVoNxKf4Vz3WIaIKDTGIuj4z+QBQucbGetdt2wu +TPgTt3OO1u77VrKqgQq7ZDq//+ri8idBNQSvxJ0zCyxYUPQSVfNyhxbr3JCSOnbN +QQdWVLYD2wDrVtGsirUItrICVE4i/S3nnqldtTcUQqujcWpLuRL0U2Dlndc2SC6o +JFKp0EWQ9S9CV7UXB/8uBaG+4CYL25Jj4SNjujIvDyUI6/HjKsw7LYAGnu3L25X2 +swIDAQAB +-----END PUBLIC KEY----- diff --git a/lab_3/keys/symmetric_key.txt b/lab_3/keys/symmetric_key.txt new file mode 100644 index 000000000..c64dd0ef8 Binary files /dev/null and b/lab_3/keys/symmetric_key.txt differ diff --git a/lab_3/main.py b/lab_3/main.py new file mode 100644 index 000000000..48fa71762 --- /dev/null +++ b/lab_3/main.py @@ -0,0 +1,70 @@ +import argparse + +from cryptosystem.hybrid_system import HybridCryptoSystem +from file_manager import FileManager + + +def parse_arguments() -> argparse.Namespace: + """ + Function parses arguments from cmd + :return: Object with arguments + """ + parser = argparse.ArgumentParser( + description = "Hybrid crypto system (RSA + 3DES)", + formatter_class = argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument('-s', '--settings', default='settings.json', + help = 'Path to JSON file containing settings') + + parser.add_argument('-k', '--key_length', type = int, choices = [64, 128, 192], default = 192, + help = 'Symmetric key length in bits (64, 128 or 192)') + + group = parser.add_mutually_exclusive_group(required = True) + group.add_argument('-gen', '--generation', action = 'store_true', help = 'Generate keys') + group.add_argument('-enc', '--encryption', action = 'store_true', help = 'Encrypt file') + group.add_argument('-dec', '--decryption', action = 'store_true', help = 'Decrypt file') + + return parser.parse_args() + +def main(): + try: + args = parse_arguments() + if args.key_length not in (64, 128, 192): + raise ValueError("3DES key length must be 64, 128 or 192 bits") + settings = FileManager.load_json(args.settings) + + if args.generation: + HybridCryptoSystem.generate_keys( + args.key_length, + settings["private_key"], + settings["public_key"], + settings["symmetric_key"] + ) + print( + f"Keys generated and saved:\n" + f" - Private key: {settings['private_key']}\n" + f" - Public key: {settings['public_key']}\n" + f" - Symmetric key: {settings['symmetric_key']}" + ) + elif args.encryption: + HybridCryptoSystem.encrypt( + settings["initial_file"], + settings["private_key"], + settings["symmetric_key"], + settings["encrypted_file"] + ) + print(f"File encrypted and saved to {settings['encrypted_file']}") + elif args.decryption: + HybridCryptoSystem.decrypt( + settings["encrypted_file"], + settings["private_key"], + settings["symmetric_key"], + settings["decrypted_file"] + ) + print(f"File decrypted and saved to {settings['decrypted_file']}") + except Exception as e: + print(f"Error: {e}") + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/lab_3/settings.json b/lab_3/settings.json new file mode 100644 index 000000000..e1580e084 --- /dev/null +++ b/lab_3/settings.json @@ -0,0 +1,8 @@ +{ + "initial_file":"texts/initial_file.txt", + "encrypted_file":"texts/encrypted_file.txt", + "decrypted_file":"texts/decrypted_file.txt", + "symmetric_key":"keys/symmetric_key.txt", + "public_key":"keys/public_key.pem", + "private_key":"keys/private_key.pem" +} diff --git a/lab_3/texts/decrypted_file.txt b/lab_3/texts/decrypted_file.txt new file mode 100644 index 000000000..6fdc249f0 --- /dev/null +++ b/lab_3/texts/decrypted_file.txt @@ -0,0 +1,11 @@ +== История разработки == +Впервые список реплик персонажа был составлен 26 мая в документе ''script_replics.doc''. К 28 мая список был переработан, и реплики в тот же день пошли на озвучку. +Первыми были озвучены монологи Торговца ('''trader_monolog1''' и '''trader_monolog2'''), позднее - все остальные реплики. +Реплики вошли в сборку [[Build 1842|'xrCore' build 1842, Jun 17 2004]], и их состав оставался без изменений вплоть до декабря 2004 года. + +3 декабря 2004 года в документе ''scenes_sound.doc'' был составлен новый список реплик для различных персонажей, в том числе и Сидоровича: в него вошли сценарные реплики и реплики про использование КПК. Позднее, 13 декабря, был составлен окончательный вариант, после чего список был отдан на озвучку. К 15 декабря реплики были озвучены, и они вошли в состав сборки [[Build 1994|'xrCore' build 1994, Dec 16 2004]]. Позже реплики были переозвучены, а их содержание переделано, и они вошли уже в состав сборки [[Build 2212|'xrCore' build 2212, Jan 22 2005]]. + + + + + diff --git a/lab_3/texts/encrypted_file.txt b/lab_3/texts/encrypted_file.txt new file mode 100644 index 000000000..7d1fc13f2 Binary files /dev/null and b/lab_3/texts/encrypted_file.txt differ diff --git a/lab_3/texts/initial_file.txt b/lab_3/texts/initial_file.txt new file mode 100644 index 000000000..6fdc249f0 --- /dev/null +++ b/lab_3/texts/initial_file.txt @@ -0,0 +1,11 @@ +== История разработки == +Впервые список реплик персонажа был составлен 26 мая в документе ''script_replics.doc''. К 28 мая список был переработан, и реплики в тот же день пошли на озвучку. +Первыми были озвучены монологи Торговца ('''trader_monolog1''' и '''trader_monolog2'''), позднее - все остальные реплики. +Реплики вошли в сборку [[Build 1842|'xrCore' build 1842, Jun 17 2004]], и их состав оставался без изменений вплоть до декабря 2004 года. + +3 декабря 2004 года в документе ''scenes_sound.doc'' был составлен новый список реплик для различных персонажей, в том числе и Сидоровича: в него вошли сценарные реплики и реплики про использование КПК. Позднее, 13 декабря, был составлен окончательный вариант, после чего список был отдан на озвучку. К 15 декабря реплики были озвучены, и они вошли в состав сборки [[Build 1994|'xrCore' build 1994, Dec 16 2004]]. Позже реплики были переозвучены, а их содержание переделано, и они вошли уже в состав сборки [[Build 2212|'xrCore' build 2212, Jan 22 2005]]. + + + + +