""" وحدة تشفير الملفات (File Encryption Module) ============================================== تشفير وفك تشفير الملفات الحساسة باستخدام Fernet (AES-128). يدعم تشفير الملفات الفردية والمجلدات بالكامل. """ import os import base64 import hashlib import logging from pathlib import Path from typing import Optional logger = logging.getLogger(__name__) class FileEncryptor: """مشفر الملفات باستخدام Fernet (AES-128-CBC).""" def __init__(self, key: Optional[str] = None, key_file: Optional[str] = None): """ تهيئة مشفر الملفات. Args: key: مفتاح التشفير (Base64 encoded) أو None لتوليد واحد key_file: مسار ملف لحفظ/تحميل المفتاح """ self._key = None self._cipher = None if key: self._init_from_key(key) elif key_file and os.path.exists(key_file): self._load_key_from_file(key_file) else: self._generate_key() if key_file and not os.path.exists(key_file): self._save_key_to_file(key_file) def _init_from_key(self, key: str) -> None: """تهيئة من مفتاح.""" try: from cryptography.fernet import Fernet if not key.startswith("gAAAAA"): # ربما كلمة مرور - تحويلها لمفتاح key = base64.urlsafe_b64encode(hashlib.sha256(key.encode()).digest()).decode() self._key = key.encode() if isinstance(key, str) else key self._cipher = Fernet(self._key) logger.info("تم تهيئة مشفر الملفات من مفتاح") except ImportError: logger.error("مكتبة cryptography غير مثبتة. pip install cryptography") except Exception as e: logger.error("فشل تهيئة التشفير: %s", e) def _generate_key(self) -> None: """توليد مفتاح تشفير عشوائي.""" try: from cryptography.fernet import Fernet self._key = Fernet.generate_key() self._cipher = Fernet(self._key) logger.info("تم توليد مفتاح تشفير جديد") except ImportError: logger.error("مكتبة cryptography غير مثبتة") def _load_key_from_file(self, path: str) -> None: """تحميل المفتاح من ملف.""" try: with open(path, "r") as f: key = f.read().strip() self._init_from_key(key) logger.info("تم تحميل مفتاح التشفير من %s", path) except Exception as e: logger.warning("فشل تحميل المفتاح: %s", e) self._generate_key() def _save_key_to_file(self, path: str) -> None: """حفظ المفتاح في ملف.""" try: os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w") as f: f.write(self._key.decode() if isinstance(self._key, bytes) else self._key) os.chmod(path, 0o600) logger.info("تم حفظ مفتاح التشفير في %s", path) except Exception as e: logger.warning("فشل حفظ المفتاح: %s", e) @property def is_available(self) -> bool: """هل التشفير متاح؟""" return self._cipher is not None @property def key(self) -> Optional[str]: """المفتاح الحالي (Base64).""" return self._key.decode() if isinstance(self._key, bytes) else self._key def encrypt_file(self, input_path: str, output_path: Optional[str] = None) -> str: """ تشفير ملف. Args: input_path: مسار الملف المراد تشفيره output_path: مسار الملف المشفر (الافتراضي: نفس المسار + .enc) Returns: مسار الملف المشفر """ if not self._cipher: raise RuntimeError("التشفير غير متاح - تأكد من تثبيت cryptography") input_path = str(input_path) output_path = output_path or input_path + ".enc" with open(input_path, "rb") as f: data = f.read() encrypted = self._cipher.encrypt(data) with open(output_path, "wb") as f: f.write(encrypted) logger.info("تم تشفير %s (%d bytes -> %d bytes)", input_path, len(data), len(encrypted)) return output_path def decrypt_file(self, input_path: str, output_path: Optional[str] = None) -> str: """ فك تشفير ملف. Args: input_path: مسار الملف المشفر output_path: مسار الملف بعد فك التشفير (الافتراضي: إزالة .enc) Returns: مسار الملف المفكوك """ if not self._cipher: raise RuntimeError("التشفير غير متاح") input_path = str(input_path) if output_path is None: output_path = input_path.removesuffix(".enc") if input_path.endswith(".enc") else input_path + ".dec" with open(input_path, "rb") as f: encrypted = f.read() decrypted = self._cipher.decrypt(encrypted) with open(output_path, "wb") as f: f.write(decrypted) logger.info("تم فك تشفير %s (%d bytes -> %d bytes)", input_path, len(encrypted), len(decrypted)) return output_path def encrypt_data(self, data: bytes) -> bytes: """تشفير بيانات خام.""" if not self._cipher: raise RuntimeError("التشفير غير متاح") return self._cipher.encrypt(data) def decrypt_data(self, data: bytes) -> bytes: """فك تشفير بيانات خام.""" if not self._cipher: raise RuntimeError("التشفير غير متاح") return self._cipher.decrypt(data) def encrypt_directory(self, input_dir: str, output_dir: Optional[str] = None, pattern: str = "*") -> list[str]: """ تشفير جميع الملفات في مجلد. Args: input_dir: مسار المجلد output_dir: مسار الإخراج (الافتراضي: مجلد مشفر جديد) pattern: نمط أسماء الملفات (glob) Returns: قائمة مسارات الملفات المشفرة """ import glob input_dir = Path(input_dir) output_dir = Path(output_dir) if output_dir else input_dir.parent / (input_dir.name + "_encrypted") output_dir.mkdir(parents=True, exist_ok=True) encrypted_files = [] for file_path in sorted(input_dir.glob(pattern)): if file_path.is_file(): out = str(output_dir / (file_path.name + ".enc")) self.encrypt_file(str(file_path), out) encrypted_files.append(out) logger.info("تم تشفير %d ملف من %s", len(encrypted_files), input_dir) return encrypted_files def decrypt_directory(self, input_dir: str, output_dir: Optional[str] = None) -> list[str]: """ فك تشفير جميع الملفات .enc في مجلد. Args: input_dir: مسار المجلد المشفر output_dir: مسار الإخراج Returns: قائمة مسارات الملفات المفكوكة """ import glob input_dir = Path(input_dir) output_dir = Path(output_dir) if output_dir else input_dir.parent / (input_dir.name + "_decrypted") output_dir.mkdir(parents=True, exist_ok=True) decrypted_files = [] for file_path in sorted(input_dir.glob("*.enc")): out = str(output_dir / file_path.name.removesuffix(".enc")) self.decrypt_file(str(file_path), out) decrypted_files.append(out) logger.info("تم فك تشفير %d ملف من %s", len(decrypted_files), input_dir) return decrypted_files