
ギガバイト毎秒のマルチスレッドファイル暗号エンジン。ロックフリーでトリプルバッファリングされたio_uringパイプライン、Rayonの並列チャンク処理、ハードウェアアクセラレーションAEAD(AES-256-GCM / ChaCha20)を使用して極限のスループットを実現。
Rustで構築されたマルチスレッドAEAD暗号化エンジンです。トリプルバッファリングされたio_uringパイプライン、Rayonによる並列チャンク処理、ringによるアセンブリ最適化暗号を利用して、ギガバイト/秒のスループットでファイルを暗号化・復号します。
⚠️ 免責事項:実験的ソフトウェア ⚠️
このプロジェクトは非常に新しく、現在、本番環境やミッションクリティカルな使用には推奨されていません。 暗号プリミティブ(ringによるAES-256-GCM、ChaCha20-Poly1305)と形式設計は妥当ですが、コードベースは正式なセキュリティ監査や広範な実世界テストを受けていません。自己責任で使用してください。機密データを保護するには、このプロジェクトが成熟するまでGnuPG、age、OpenSSLなどの実績のあるツールを使用することを検討してください。
ringによるAES-256-GCM(ハードウェアAES-NI)およびChaCha20-Poly1305(アセンブリ最適化)--memoryで設定可能)ringによるseal_in_place_separate_tag / open_in_placeを使用し、ホットループでのアロケーションを最小化O_DIRECT I/Oを有効にし、NVMe上でDMA速度の読み取り/書き込みのためにカーネルページキャッシュをバイパスします。バッファプールは4096バイトアライメントでstd::allocを使用します。cargo bench(Criterion、測定ごとに10サンプル)でベンチマーク。鍵導出は除外 — 数値は純粋な暗号スループットのみを示します。
ハードウェア:
I/Oに関する注記: Criterionは一時ファイルを/tmpに書き込みます。このシステムではtmpfs(RAMバッキング)です。O_DIRECTを使用する場合、カーネルはtmpfs上で実際の非同期DMAを使用できないため、これらの数値はDMAバイパスの利点なしでの暗号スループットとio_uringオーバーヘッドを反映しています。実際のGen4 NVMeドライブでは、O_DIRECTはページキャッシュのダブルバッファリングを排除し、アラインされたバッファプールへのDMAを可能にするため、大幅に高いスループットが期待できます。
チャンクサイズスイープ(AES-256-GCM、64 MiBファイル):
このエンジンは、暗号操作にring(アセンブリ最適化AES-NI / NEON / ARMv8-CE)を使用し、I/Oにトリプルバッファリングされたio_uringパイプラインを使用します。3つの事前割り当てバッファプールがパイプラインを循環します。プールAの書き込みがカーネルで完了している間に、プールBはCPU上でRayonによって暗号化され、プールCの読み取りがカーネルに送信されます。これにより、I/Oレイテンシと暗号計算がオーバーラップします。
なぜAES-256-GCMが小さいファイルでChaCha20-Poly1305より速いのか:
ringのAES-GCMバックエンドは、x86-64で利用可能なAES-NI + CLMULハードウェア命令を活用するため、ChaCha20(ソフトウェア暗号)よりもハードウェア上の利点があります。大きいサイズでは両方の暗号が約1.0 GiB/sに収束し、ボトルネックが暗号スループットからI/O送信オーバーヘッドに移行することを示しています。
なぜピークスループットが256 MiBではなく1〜16 MiBなのか: 小さいファイル(1〜16 MiB)はチャンク数が少ないため、Rayonの並列性が効率的で、ワーキングセットがキャッシュに収まります。64〜256 MiBでは、io_uringパイプラインが完全にアクティブ(3つのバッチが進行中)ですが、SQEあたりの送信およびCQE完了オーバーヘッドはチャンク数に比例して増加します。トリプルバッファ設計によりI/Oと暗号処理がオーバーラップし、このコストを部分的に隠蔽します。
なぜ約1.0 GiB/sであり、10+ GiB/sではないのか: 最新のAES-NIはコアあたり2〜4 GiB/sを実現できます。12スレッドでは、生の暗号スループットは10 GiB/sを超える可能性があります。ギャップを説明する3つの要因:
PIPELINE_DEPTH=3では、一度に3つのバッチのみがパイプラインを循環します。真の定常状態オーバーラップには少なくとも3つのバッチが必要です。1つまたは2つのバッチに収まるファイルは、パイプライン化の恩恵を受けません。バッファのライフサイクルと安全性:
バッファプールは、io_uringリングが作成される前にstd::alloc::alloc_zeroedとLayout::from_size_align(size, 4096)を使用して一度だけ割り当てられ、すべてのパイプライン反復で再利用され、再割り当ては行われません。各暗号化チャンクは、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
# カスタムチャンクサイズ(MiB単位)
concryptor encrypt largefile.iso --chunk-size 8
# より強力なKDF(512 MiBメモリコスト)
concryptor encrypt secrets.tar --memory 512
# 非対話型(パスワードプロンプトをスキップ)
concryptor encrypt myfile.dat -p "password"
セキュリティノート:
--password/-pはパスワードをCLI引数として渡します。これは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"
# 復号と展開を1ステップで実行(.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
すべての値はリトルエンディアンです。ヘッダーは完全な4 KiBセクターを占有し、各暗号化チャンクスロットは次の4 KiB境界にパディングされます。これにより、すべてのオフセットとI/Oサイズが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 (KiB単位、LE、0 = レガシー64 MiB)
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 MiBチャンクの場合: 各ディスクスロットは ceil((4194304 + 16) / 4096) * 4096 = 4198400 バイト(チャンクあたり4080バイトのパディング)です。ヘッダー内の4032バイトの予約領域は、将来の機能(非対称鍵スロット、メタデータなど)に使用できます。
ソルトとベースノンスは、暗号化ごとにrand::rng()(OSの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 KiBヘッダーセクター(コアフィールド、KDFパラメータ、予約パディング)がすべてのチャンクの認証タグにバインドされます。任意のヘッダーバイト(暗号タイプ、チャンクサイズ、元のサイズ、ソルト、ノンス、KDFパラメータ、予約パディング)の改ざんは、すべてのチャンクを無効にします。これにより、攻撃者がoriginal_sizeを編集して末尾のチャンクを削除する切り詰め攻撃を防止し、予約パディング領域へのデータ密輸も防止します。レガシーv3ファイルは後方互換性のために52バイトのAADで復号されます。v4からv3へのダウングレードは、バージョンバイト自体が認証されたAAD内にあるため不可能です。0x01、それ以外は0x00です。これにより2つの攻撃を防止します:
is_final = 0x00で暗号化されているが、復号では0x01が期待されるため失敗します。is_final = 0x01に対する有効なタグを生成できないため失敗します。rand::rng())から取得されます。同じパスワードで同じファイルを2回暗号化すると、完全に異なる暗号文が生成されます。ノンスの再利用(AES-GCMにとって壊滅的)は構造的に回避されます。# 完全なテストスイートを実行(67テスト)
cargo test
# ベンチマークを実行(HTMLレポートは target/criterion/)
cargo bench
# ベンチマークをフィルタリング
cargo bench -- "encrypt/AES"
cargo bench -- "chunk_sweep"
テストスイートの対象:
original_sizeの変更 + チャンク削除)chunk_sizeの変更)# 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の下でライセンスされています。
| ファイルサイズ | AES-256-GCM 暗号化 | ChaCha20 暗号化 | AES-256-GCM 復号 | ChaCha20 復号 |
|---|
| 64 KiB | 244 MiB/s | 233 MiB/s | 233 MiB/s | 234 MiB/s |
| 1 MiB | 1.08 GiB/s | 882 MiB/s | 1010 MiB/s | 876 MiB/s |
| 16 MiB | 1.10 GiB/s | 923 MiB/s | 1.06 GiB/s | 988 MiB/s |
| 64 MiB | 984 MiB/s | 935 MiB/s | 988 MiB/s | 973 MiB/s |
| 256 MiB | 1.00 GiB/s | 1015 MiB/s | 1.01 GiB/s | 1.02 GiB/s |
| チャンクサイズ | スループット |
|---|
| 64 KiB | 1.01 GiB/s |
| 256 KiB | 1.05 GiB/s |
| 1 MiB | 1.07 GiB/s |
| 4 MiB | 988 MiB/s |
| 8 MiB | 988 MiB/s |
| 16 MiB | 1.00 GiB/s |
--memoryで調整可能)、3回の時間反復、並列度4。256 MiBのデフォルトはOWASPの最小値の4倍であり、GPU/FPGA/ASIC攻撃者にとって高コストです。KDFパラメータはファイルヘッダー(バイト52-63)に保存され、ファイルは自己記述的になります。復号は現在のデフォルトに関係なく、常に正しいパラメータを使用します。バイト52-63がすべてゼロ(KDFパラメータ前のレガシーファイル)の場合、古い64 MiB / 3 / 4のデフォルトが適用されます。| Crate | 目的 |
|---|
ring | アセンブリ最適化AES-256-GCMおよびChaCha20-Poly1305 AEAD |
io-uring | 非同期読み取り/書き込みI/OのLinux io_uringインターフェース |
libc | O_DIRECTフラグとヘッダーI/O用のアラインされたpread/pwrite |
argon2 | Argon2id鍵導出 |
rayon | データ並列チャンク処理 |
clap | CLI引数解析 |
indicatif | ターミナルプログレスバー |
rand | 暗号学的乱数生成 |
zeroize | 安全なメモリ消去 |
anyhow | エラーハンドリング |
rpassword | 非表示パスワード入力 |
tar | ディレクトリアーカイブと展開 |