import base64 import hashlib from cryptography.fernet import Fernet from django.conf import settings from django.db import models def _get_fernet() -> Fernet: key = hashlib.sha256(settings.SECRET_KEY.encode()).digest() return Fernet(base64.urlsafe_b64encode(key)) def encrypt_value(plaintext: str) -> str: if not plaintext: return "" f = _get_fernet() return f.encrypt(plaintext.encode()).decode() def decrypt_value(ciphertext: str) -> str: if not ciphertext: return "" f = _get_fernet() return f.decrypt(ciphertext.encode()).decode() def compute_national_id_hash(value: str) -> str: if not value: return "" salt = settings.SECRET_KEY.encode() return hashlib.sha256(salt + value.strip().encode()).hexdigest() def mask_national_id(value: str) -> str: if not value: return "" value = value.strip() if len(value) <= 4: return "*" * len(value) return "*" * (len(value) - 4) + value[-4:] class EncryptedCharField(models.CharField): def get_prep_value(self, value): if not value: return value return encrypt_value(str(value)) def from_db_value(self, value, expression, connection): if not value: return value try: return decrypt_value(value) except Exception: return value def to_python(self, value): if not value: return value if isinstance(value, str): try: return decrypt_value(value) except Exception: return value return value def deconstruct(self): name, path, args, kwargs = super().deconstruct() path = "apps.core.encryption.EncryptedCharField" return name, path, args, kwargs