lab4 / app /core /crypto.py
brestok's picture
init
a08f988
import os
from typing import List, Tuple
from dotenv import load_dotenv
from app.core.config import ALPHABET, ENCODING_UTF8
ENV_KEY_SHIFT = 5
class ShiftCipher:
@staticmethod
def _shift_char(ch: str, shift: int) -> str:
index = ALPHABET.find(ch)
if index == -1:
raise ValueError(f"Character '{ch}' is not in the configured alphabet")
return ALPHABET[(index + shift) % len(ALPHABET)]
@staticmethod
def apply(data: bytes, shift: int) -> bytes:
text = data.decode(ENCODING_UTF8)
shifted = "".join(ShiftCipher._shift_char(ch, shift) for ch in text)
return shifted.encode(ENCODING_UTF8)
@staticmethod
def transform_text(value: str, shift: int) -> str:
return "".join(ShiftCipher._shift_char(ch, shift) for ch in value)
class LZ78Codec:
@staticmethod
def compress(text: str) -> bytes:
dictionary: dict[str, int] = {}
current = ""
output: List[Tuple[int, str]] = []
for ch in text:
candidate = current + ch
if candidate in dictionary:
current = candidate
continue
index = dictionary.get(current, 0)
output.append((index, ch))
dictionary[candidate] = len(dictionary) + 1
current = ""
if current:
output.append((dictionary.get(current, 0), ""))
serialized = ";".join(f"{index}:{ord(ch) if ch else -1}" for index, ch in output)
return serialized.encode(ENCODING_UTF8)
@staticmethod
def decompress(data: bytes) -> str:
if not data:
return ""
serialized = data.decode(ENCODING_UTF8)
if not serialized:
return ""
pairs: List[Tuple[int, str]] = []
for chunk in serialized.split(";"):
if not chunk:
continue
index_part, code_part = chunk.split(":", 1)
idx = int(index_part)
code = int(code_part)
ch = "" if code == -1 else chr(code)
pairs.append((idx, ch))
dictionary: dict[int, str] = {0: ""}
result_parts: List[str] = []
for idx, ch in pairs:
prefix = dictionary.get(idx, "")
entry = prefix + ch
result_parts.append(entry)
dictionary[len(dictionary)] = entry
return "".join(result_parts)
class DataCipher:
def __init__(self, key: str) -> None:
self.key = key
self.shift = self._derive_shift(key)
@staticmethod
def _derive_shift(key: str) -> int:
value = sum(ord(ch) for ch in key) % 256
return value if value else 1
def encrypt(self, plain: str) -> str:
compressed = LZ78Codec.compress(plain)
encrypted = ShiftCipher.apply(compressed, self.shift)
return encrypted.hex()
def decrypt(self, encoded: str) -> str:
encrypted = bytes.fromhex(encoded)
decrypted = ShiftCipher.apply(encrypted, -self.shift)
return LZ78Codec.decompress(decrypted)
def load_encryption_key() -> str:
load_dotenv()
encrypted_key = os.getenv("APP_KEY_ENC")
if not encrypted_key:
raise RuntimeError("APP_KEY_ENC is not set")
return ShiftCipher.transform_text(encrypted_key, -ENV_KEY_SHIFT)