Введение: Почему не VeraCrypt?
Всё началось с простой задачи: нужно было безопасно передавать файлы на обычных USB-флешках. Существующие решения либо создавали контейнеры (VeraCrypt), что неудобно для быстрого доступа к отдельным файлам на разных ОС, либо работали слишком сложно для конечного пользователя.
Мне нужно было решение уровня «вставил флешку -> ввел пароль -> файлы зашифрованы». Но главное требование — безопасность данных даже при сбое питания. Если выдернуть флешку посередине шифрования, данные не должны превратиться в кашу.
Так появился crypto_engine. Это не попытка изобрести свою криптографию (мы используем стандартные AES-GCM и ChaCha20), а инженерная работа над тем, как безопасно управлять ключами в памяти, обрабатывать гигабайтные файлы без переполнения RAM и гарантировать целостность данных.
1. Проблема памяти в Python и класс SecureBytes
Самая большая уязвимость криптографических утилит на управляемых языках (Python, Java) — это работа с памятью. Когда вы храните пароль или ключ в переменной bytes, сборщик мусора (GC) может скопировать эти данные в другое место памяти при сборке мусора, оставив исходные копии «висеть» в RAM до неопределенного времени.
В моем движке я реализовал класс SecureBytes, который решает эту проблему:
class SecureBytes: def __init__(self, data: Union[bytes, bytearray, int]): if isinstance(data, int): self._buffer = bytearray(data) else: self._buffer = bytearray(data) self._finalized = False # Регистрируем слабый финализатор self._weak_ref = weakref.ref(self, self._cleanup_callback) def wipe(self, passes: int = 3): if self._finalized or len(self._buffer) == 0: return # Проход 1: случайные данные self._buffer[:] = secrets.token_bytes(len(self._buffer)) # Проход 2: нули self._buffer[:] = b'\x00' * len(self._buffer) self._finalized = True gc.collect() def __del__(self): if not self._finalized: self.wipe()
Что здесь важно:
Использование
bytearray: В отличие от неизменяемыхbytes,bytearrayпозволяет перезаписывать данные по тому же адресу памяти.Многопроходная очистка: Перед освобождением памяти буфер перезаписывается случайными данными, затем нулями (согласно рекомендациям NIST SP 800-88).
Контекстный менеджер: Ключи используются только внутри блока
with secure_key(...):, что гарантирует очистку даже при возникновении исключений.
2. Потоковое шифрование больших файлов
Шифрование файла размером 10 ГБ на флешке с 4 ГБ оперативной памяти — нетривиальная задача. Загружать файл целиком в RAM нельзя.
Я реализовал MemorySensitiveReader, который автоматически переключается между режимами работы в зависимости от размера файла и доступной памяти:
class MemorySensitiveReader: def __init__(self, file_path: str, memory_threshold: int = 100 * 1024 * 1024): self.file_size = os.path.getsize(file_path) # Порог переключения на потоковый режим self.use_streaming = self.file_size > memory_threshold def iter_chunks(self, chunk_size: int = 8192): # Чтение и шифрование блоками ...
Проблема Nonce при потоковом шифровании:
В режимах AES-GCM и ChaCha20 нельзя использовать один и тот же nonce (номер однократного использования) для разных блоков с одним ключом. Это критическая уязвимость. Решение в моем коде — деривация уникального nonce для каждого блока на основе базового nonce и индекса блока:
def _derive_block_nonce_12bit(base_nonce: bytes, block_index: int) -> bytes: # Первые 8 байт — префикс, последние 4 — счётчик блока prefix = base_nonce[:8] block_counter = block_index.to_bytes(4, byteorder='big') return prefix + block_counter
Это позволяет шифровать файлы любого размера, не нарушая криптографические стандарты.
3. Отказоустойчивость: что если выдернуть флешку?
Самый страшный сценарий для пользователя — потеря данных из-за сбоя во время шифрования. Стандартный подход «зашифровать -> удалить оригинал» здесь не работает.
Я внедрил систему блокировок и отката (rollback):
Lock-файл (
.encryption_lock.json): Перед началом операции создается файл, где записывается статусin_progressи список уже обработанных файлов.Временные файлы: Шифрование идет в
.tmpфайл. Только после успешной проверки целостности оригинал удаляется, а временный файл переименовывается.Проверка целостности: Перед удалением оригинала я расшифровываю блок данных обратно и сравниваю HMAC и SHA-256 хеши. Если не совпадает — оригинал не трогается.
Восстановление: Если процесс прервался, утилита видит lock-файл и предлагает откатить операцию (
rollback_operation), расшифровав уже обработанные файлы обратно.
4. Алгоритмы и производительность
Движок поддерживает три алгоритма:
AES-256-GCM: Стандарт индустрии, аппаратное ускорение на большинстве CPU.
ChaCha20-Poly1305: Быстрее на устройствах без AES-NI (например, некоторые ARM-процессоры).
XChaCha20-Poly1305: Увеличенный nonce (24 байта), что снижает риск коллизий при очень больших объемах данных.
Для ускорения работы с множеством мелких файлов реализована параллельная обработка через ThreadPoolExecutor. Однако из-за GIL в Python прирост заметен скорее на операциях I/O, чем на чистом шифровании.
5. Интерфейс и использование
Хотя ядро написано на Python, для пользователей доступен GUI, чтобы не запускать скрипты через консоль.
6. Ограничения и Threat Model
Важно понимать, для чего этот инструмент подходит, а для чего — нет.
Метаданные не скрыты: Имена файлов и структура папок сохраняются в
.usb_crypt_meta.json. Злоумышленник с доступом к флешке увидит список файлов, но не сможет их открыть. Скрыть имена файлов без создания контейнера технически сложно и неудобно для навигации.Защита от физической потери: Инструмент защищает данные, если вы потеряли флешку. Он не защищает от кейлоггеров на компьютере, где вы вводите пароль.
Парольная политика: Встроенная валидация требует минимум 12 символов, цифры и спецсимволы. Слабые пароли блокируются на уровне кода.



Заключение
Написание собственного крипто-движка — это всегда баланс между безопасностью и удобством. Я постарался сделать акцент на безопасности управления памятью (что редко встречается в Python-скриптах) и отказоустойчивости операций.
Проект открыт, код доступен для аудита. Если вы найдете уязвимости или способы оптимизировать SecureBytes — добро пожаловать в Issues.
