Введение: Почему не 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.
Автор: slimeopus
