
Многопоточный движок шифрования файлов со скоростью гигабайт в секунду. Достигает экстремальной пропускной способности за счет конвейера io_uring без блокировок с тройной буферизацией, параллельной фрагментации Rayon и аппаратно-ускоренных AEAD (AES-256-GCM / ChaCha20).
Многопоточный движок AEAD-шифрования, написанный на Rust. Шифрует и расшифровывает файлы с пропускной способностью гигабайты в секунду, используя трёхбуферный конвейер на io_uring, параллельную обработку чанков через Rayon и оптимизированные на ассемблере шифры через ring.
⚠️ ПРЕДУПРЕЖДЕНИЕ: ЭКСПЕРИМЕНТАЛЬНОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ⚠️
Этот проект чрезвычайно новый и в настоящее время НЕ рекомендуется для производственного или критически важного использования. Хотя криптографические примитивы (AES-256-GCM, ChaCha20-Poly1305 через ring) и дизайн формата являются обоснованными, кодовая база не прошла формальных аудитов безопасности или обширного реального тестирования. Используйте на свой страх и риск. Для защиты конфиденциальных данных рассмотрите использование проверенных инструментов, таких как GnuPG, age или OpenSSL, пока этот проект не станет зрелым.
ring (оптимизированный на ассемблере)--memory)seal_in_place_separate_tag / open_in_place через ring минимизирует выделение памяти в горячем циклеO_DIRECT ввод-вывод, минуя кэш страниц ядра для чтения/записи на скорости DMA на NVMe. Пулы буферов используют std::alloc с выравниванием в 4096 байтРезультаты бенчмарков с cargo bench (Criterion, 10 выборок на измерение). Вывод ключа исключён — числа отражают только чистую криптографическую пропускную способность.
Оборудование:
Примечание о вводе-выводе: Criterion записывает временные файлы в /tmp, который в этой системе является tmpfs (на базе ОЗУ). При использовании O_DIRECT ядро не может выполнять настоящий асинхронный DMA на tmpfs, поэтому эти числа отражают пропускную способность шифрования + накладные расходы io_uring без преимущества DMA bypass. На реальном Gen4 NVMe-диске O_DIRECT устраняет двойную буферизацию кэша страниц и включает прямой DMA в выровненные пулы буферов, что должно дать значительно более высокую пропускную способность.
| Размер файла | Шифрование AES-256-GCM | Шифрование ChaCha20 | Расшифровка AES-256-GCM | Расшифровка ChaCha20 |
|---|---|---|---|---|
| 64 КиБ | 244 МиБ/с | 233 МиБ/с | 233 МиБ/с | 234 МиБ/с |
| 1 МиБ | 1.08 ГиБ/с | 882 МиБ/с | 1010 МиБ/с | 876 МиБ/с |
| 16 МиБ | 1.10 ГиБ/с | 923 МиБ/с | 1.06 ГиБ/с | 988 МиБ/с |
| 64 МиБ | 984 МиБ/с | 935 МиБ/с | 988 МиБ/с | 973 МиБ/с |
| 256 МиБ | 1.00 ГиБ/с | 1015 МиБ/с | 1.01 ГиБ/с | 1.02 ГиБ/с |
Развёртка размера чанка (AES-256-GCM, файл 64 МиБ):
| Размер чанка | Пропускная способность |
|---|---|
| 64 КиБ | 1.01 ГиБ/с |
| 256 КиБ | 1.05 ГиБ/с |
| 1 МиБ | 1.07 ГиБ/с |
| 4 МиБ | 988 МиБ/с |
| 8 МиБ | 988 МиБ/с |
| 16 МиБ | 1.00 ГиБ/с |
Движок использует ring (оптимизированные на ассемблере AES-NI / NEON / ARMv8-CE) для криптографических операций и трёхбуферный конвейер io_uring для ввода-вывода. Три предварительно выделенных пула буферов циклически проходят через конвейер: пока завершаются записи пула A в ядре, пул B шифруется Rayon на ЦП, а операции чтения пула C отправляются в ядро. Это перекрывает задержку ввода-вывода с криптографическими вычислениями.
Почему AES-256-GCM быстрее ChaCha20-Poly1305 на маленьких файлах:
Бэкенд AES-GCM в ring использует аппаратные инструкции AES-NI + CLMUL, доступные на x86-64, что даёт аппаратное преимущество перед ChaCha20 (который является программным шифром). При больших размерах оба шифра сходятся к ~1.0 ГиБ/с, что указывает на то, что узкое место смещается с пропускной способности шифрования на накладные расходы отправки ввода-вывода.
Почему пиковая пропускная способность приходится на 1–16 МиБ, а не на 256 МиБ: Маленькие файлы (1–16 МиБ) имеют мало чанков, поэтому параллелизм Rayon эффективен, и рабочий набор помещается в кэш. При 64–256 МиБ конвейер io_uring полностью активен (в работе находятся три набора), но накладные расходы на отправку SQE и завершение CQE растут с количеством чанков. Конструкция с тройным буфером обеспечивает перекрытие ввода-вывода и криптографии, частично скрывая эту стоимость.
Почему ~1.0 ГиБ/с, а не 10+ ГиБ/с: Современный AES-NI может выдавать 2–4 ГиБ/с на ядро. С 12 потоками сырая пропускная способность шифрования могла бы превысить 10 ГиБ/с. Разрыв объясняют три фактора:
PIPELINE_DEPTH=3 в любой момент времени через конвейер проходят только три набора. Для настоящего установившегося перекрытия требуется как минимум три набора; файлы, умещающиеся в один или два набора, не получают выгоды от конвейеризации.Жизненный цикл буфера и безопасность:
Пулы буферов выделяются один раз через std::alloc::alloc_zeroed с Layout::from_size_align(size, 4096) до создания кольца io_uring и повторно используются во всех итерациях конвейера без перевыделения. Каждый зашифрованный чанк дополняется нулями для выравнивания на сектор перед записью O_DIRECT. Кольцо явно удаляется перед пулами буферов, что гарантирует, что ядро никогда не ссылается на освобождённую память (никаких UAF).
git clone https://github.com/frogsnot/concryptor.git
cd concryptor
cargo build --release
Бинарный файл будет находиться по пути target/release/concryptor.
# AES-256-GCM (по умолчанию), вывод в myfile.dat.enc
concryptor encrypt myfile.dat
# ChaCha20-Poly1305, собственный путь вывода
concryptor encrypt myfile.dat --cipher chacha -o encrypted.enc
# Собственный размер чанка (в МиБ)
concryptor encrypt largefile.iso --chunk-size 8
# Более сильный KDF (стоимость памяти 512 МиБ)
concryptor encrypt secrets.tar --memory 512
# Неинтерактивный режим (пропускает запрос пароля)
concryptor encrypt myfile.dat -p "password"
Замечание по безопасности:
--password/-pпередаёт пароль как аргумент командной строки, который виден в выводеpsи истории оболочки. Для интерактивного использования опустите его, чтобы получить защищённый скрытый запрос. Для скриптов предпочтительно очищать историю после использования или использовать обёртку, читающую из файлового дескриптора.
# Шифрование каталога (автоопределение, создаёт mydir.tar.enc)
concryptor encrypt mydir/
# С собственным шифром и выводом
concryptor encrypt mydir/ --cipher chacha -o secrets.enc
Шифрование каталога создаёт временный tar-архив (.concryptor-*.tar, права 0600, имя от CSPRNG), шифрует его, затем автоматически удаляет временный файл. Имена файлов, структура каталогов, права доступа и временные метки находятся внутри зашифрованного полезного груза.
# Автоматическое удаление расширения .enc
concryptor decrypt myfile.dat.enc
# Собственный путь вывода
concryptor decrypt encrypted.enc -o restored.dat
# Неинтерактивный режим
concryptor decrypt myfile.dat.enc -p "password"
# Расшифровка и извлечение за один шаг (автоудаление .tar.enc -> имя каталога)
concryptor decrypt mydir.tar.enc --extract
# Короткий флаг, собственный выходной каталог
concryptor decrypt mydir.tar.enc -x -o restored_dir/
Без --extract расшифровка архива каталога создаёт промежуточный .tar-файл, который можно изучить или извлечь вручную.
concryptor --help
concryptor encrypt --help
concryptor decrypt --help
Все значения в little-endian. Заголовок занимает полный сектор 4 КиБ; каждый слот зашифрованного чанка дополняется до следующей границы 4 КиБ. Это гарантирует, что каждое смещение и размер ввода-вывода выровнены по секторам для O_DIRECT.
Смещение Размер Поле
------- ------ ---------------------
0 10 Магические байты "CONCRYPTOR"
10 1 Версия формата (4)
11 1 Тип шифра (0 = AES-256-GCM, 1 = ChaCha20-Poly1305)
12 4 Размер чанка (байты, LE)
16 8 Исходный размер файла (байты, LE)
24 16 Соль Argon2 (криптографически случайная, уникальная для каждого файла)
40 12 Базовый одноразовый номер (криптографически случайный, уникальный для каждого файла)
52 4 Argon2 m_cost в КиБ (LE, 0 = устаревшие 64 МиБ)
56 4 Argon2 t_cost / итерации (LE, 0 = устаревшие 3)
60 4 Argon2 p_cost / параллелизм (LE, 0 = устаревший 4)
64 4032 Зарезервировано (заполнено нулями до 4096 байт)
4096 ... [Чанк 0: шифротекст + 16-байтовый тег + нулевое выравнивание до границы сектора]
[Чанк 1: шифротекст + 16-байтовый тег + нулевое выравнивание до границы сектора]
...
Для чанков по 4 МиБ: каждый дисковой слот занимает ceil((4194304 + 16) / 4096) * 4096 = 4198400 байт (4080 байт заполнения на чанк). 4032 зарезервированных байта в заголовке доступны для будущих функций (слоты асимметричных ключей, метаданные и т.д.).
Соль и базовый одноразовый номер генерируются заново из rand::rng() (использующего CSPRNG ОС) при каждом шифровании. Повторное использование пароля для разных файлов безопасно, потому что разные соли дают разные ключи Argon2id, а разные базовые одноразовые номера дают разные одноразовые номера для чанков.
chunk_nonce = base_nonce XOR chunk_index (стиль TLS 1.3). Перестановка чанков вызывает ошибку расшифровки, потому что одноразовый номер на позиции N не совпадает с одноразовым номером, использованным для шифрования чанка, изначально находившегося на позиции M. Примечание: основанный на XOR вывод одноразовых номеров имеет теоретическую уязвимость, когда один и тот же ключ используется в нескольких потоках (разные базовые одноразовые номера могут давать пересекающиеся пространства одноразовых номеров). Это не относится к Concryptor, потому что каждое шифрование генерирует новую 128-битную случайную соль, создавая уникальный ключ Argon2id для каждого файла. Уникальность одноразовых номеров важна только при одном и том же ключе, а вероятность повторного использования ключа составляет ~2^-128 для каждой пары файлов.AAD = full_aligned_header (4096) || chunk_index (8 LE) || is_final (1) (всего 4105 байт). Весь 4-КиБ сектор заголовка (основные поля, параметры KDF и зарезервированное заполнение) привязывается к тегу аутентификации каждого чанка. Изменение любого байта заголовка (тип шифра, размер чанка, исходный размер, соль, одноразовый номер, параметры KDF или зарезервированное заполнение) делает все чанки недействительными. Это предотвращает атаки усечения, когда злоумышленник изменяет original_size и удаляет последние чанки, а также предотвращает скрытую запись данных в область зарезервированного заполнения. Устаревшие файлы v3 расшифровываются с AAD длиной 52 байта для обратной совместимости; понижение версии с v4 до v3 невозможно, потому что сам байт версии находится внутри аутентифицированного AAD.0x01 для финального чанка и 0x00 для всех остальных. Это предотвращает две атаки:
is_final = 0x00, а расшифровка ожидает 0x01.is_final = 0x01 без ключа.rand::rng()) для каждого шифрования. Два шифрования одного и того же файла с одним и тем же паролем дают полностью разные шифротексты. Повторное использование одноразового номера (катастрофическое для AES-GCM) исключено конструкцией.--memory), 3 временные итерации, параллелизм 4. Значение по умолчанию 256 МиБ в 4 раза превышает минимальный стандарт OWASP и является дорогим для атакующих с GPU/FPGA/ASIC. Параметры KDF хранятся в заголовке файла (байты 52–63), что делает файлы самодокументируемыми — расшифровка всегда использует корректные параметры независимо от текущих значений по умолчанию. Если байты 52–63 все нулевые (устаревшие файлы до параметров KDF), применяются старые значения по умолчанию 64 МиБ / 3 / 4.# Запуск полного набора тестов (67 тестов)
cargo test
# Запуск бенчмарков (HTML-отчёты в target/criterion/)
cargo bench
# Фильтрация бенчмарков
cargo bench -- "encrypt/AES"
cargo bench -- "chunk_sweep"
Набор тестов покрывает:
original_size + удалённые чанки)chunk_size)| Пакет | Назначение |
|---|---|
ring | Оптимизированные на ассемблере AES-256-GCM и ChaCha20-Poly1305 AEAD |
io-uring | Интерфейс Linux io_uring для асинхронного чтения/записи |
libc | Флаг O_DIRECT и выровненные pread/pwrite для ввода-вывода заголовка |
argon2 | Вывод ключа Argon2id |
rayon | Распараллеливание обработки чанков |
clap | Разбор аргументов командной строки |
indicatif | Индикатор прогресса в терминале |
rand | Генерация криптографических случайных чисел |
zeroize | Безопасное стирание памяти |
anyhow | Обработка ошибок |
rpassword | Скрытый ввод пароля |
tar | Архивирование и извлечение каталогов |
# Из crates.io (рекомендуется)
cargo install concryptor
# Из исходников
git clone https://github.com/FrogSnot/Concryptor
cd Concryptor
cargo build --release
# Бинарный файл находится в target/release/concryptor
Этот проект лицензирован под GNU Affero General Public License v3.0.