File size: 8,287 Bytes
900df0b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
"""
وحدة تشفير الملفات (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