import base64 import json import hashlib from Crypto.Cipher import AES from core.constants import * from core.config import Config class CipherManager: def __init__(self, config: Config): self.config = config # hash self.hash_name = "sha256" self.dklen = 32 # 256 bits for AES-256 # encryption self.mode = AES.MODE_GCM def hash_password(self, password: str) -> bytes: return hashlib.pbkdf2_hmac( hash_name=self.hash_name, password=password.encode(), salt=( self.config.data["username"] + password + self.config.data["salt"] ).encode("utf-8"), iterations=self.config.data["hash_rounds"], dklen=self.dklen, ) def encrypt(self, plaintext: str) -> dict: key = self.config.data["hashed_password"] plaintext_bytes = plaintext.encode("utf-8") cipher = AES.new(key, self.mode) ciphertext, tag = cipher.encrypt_and_digest(plaintext_bytes) return {"nonce": cipher.nonce, "ciphertext": ciphertext, "tag": tag} def decrypt(self, nonce: bytes, ciphertext: bytes, tag: bytes) -> str: key = self.config.data["hashed_password"] cipher = AES.new(key, self.mode, nonce=nonce) return cipher.decrypt_and_verify(ciphertext, tag).decode() @staticmethod def encode_to_json_string(**kwargs: bytes) -> str: """ Convert bytes values to Base64 and create a JSON string. Args: **kwargs: Key-value pairs where values must be of type `bytes`. Returns: str: A JSON string with all `bytes` values Base64-encoded. Raises: ValueError: If a value is not of type `bytes`. """ json_data = {} for key, value in kwargs.items(): if isinstance(value, bytes): json_data[key] = base64.b64encode(value).decode("utf-8") else: raise ValueError( f"Unsupported value type for key '{key}': {type(value)}. " f"This method only supports 'bytes'." ) return json.dumps(json_data) @staticmethod def decode_from_json_string(json_string: str) -> dict: """ Decode a JSON string where all values are Base64-encoded back to their original bytes. Args: json_string (str): A JSON string with Base64-encoded values. Returns: dict: A dictionary with the original keys and `bytes` values decoded from Base64. Raises: ValueError: If the JSON string is not valid or if decoding fails. """ # Parse the JSON string into a dictionary json_data = json.loads(json_string) decoded_data = {} # Decode each Base64-encoded value back to bytes for key, value in json_data.items(): if isinstance(value, str): decoded_data[key] = base64.b64decode(value) else: raise ValueError( f"Unsupported value type for key '{key}': {type(value)}. " + f"Expected 'str' for Base64 decoding." ) return decoded_data @staticmethod def string_to_sha3_512_lowercase_hex(input_string: str) -> str: """ Convert a string to its lowercase hexadecimal SHA3-512 hash. Args: input_string (str): The input string to hash. Returns: str: The lowercase hexadecimal representation of the SHA3-512 hash. """ return hashlib.sha3_512(input_string.encode("utf-8")).hexdigest()