____________ ______
/ ___/ ___/ / / / __ \
/ /__/ / / /_/ / /_/ /
\___/_/ \__, /\____/
/____/
Block-based archive tool with zstd compression and optional AES-256-GCM or ChaCha20-Poly1305 encryption. Compression is parallelized across a worker pool: one thread reads and chunks the input, a pool of workers compresses/encrypts blocks concurrently, and a single writer thread persists them in order.
cargo install cryoarccargo build --releaseBinary lands at target/release/cryo.
cryo compress <name> [options]| Flag | Default | Description |
|---|---|---|
-P, --path <PATH> |
./ |
Source path |
-r, --recursive |
false | Recurse into subdirectories |
-c, --compression-level <N> |
3 |
zstd level (-7 to 22) |
-e, --encryption-type <TYPE> |
none |
aes, chacha, or none |
--ep <PROFILE> |
balanced |
Argon2 profile: fast, balanced, paranoid |
--bs <SIZE> |
512KiB |
Block size (e.g. 1MiB, 256KiB) |
cryo compress backup -P ./docs -r -c 9 -e aescryo decompress <archive.cryo> [options]| Flag | Default | Description |
|---|---|---|
-o, --output <DIR> |
./ |
Output directory (must be empty or new) |
cryo decompress backup.cryo -o ./restoredcryo list <archive.cryo> [--format auto|human|plain|json]Prints file entries with permissions, type, size, compression ratio, timestamp, and path. Defaults to colored output when stdout is a terminal and plain tab-separated output when piped.
cryo verify <archive.cryo>Reads every block and checks its BLAKE3 checksum without writing anything to disk.
Pass -v before the subcommand to enable debug tracing output on stderr:
cryo -v decompress archive.cryo -o ./out[ 4 bytes ] header length (little-endian u32)
[ N bytes ] msgpack-encoded Header
[ blocks... ] zstd-compressed, encrypted data blocks
[ index ] msgpack-encoded Index (optionally compressed, optionally encrypted)
[ 17 bytes ] Footer
Footer layout (all little-endian):
bytes 0-7 : u64 index offset in file
bytes 8-11 : u32 stored index size (after encryption)
bytes 12-15 : u32 plain index size (before compression)
byte 16 : u8 1 if index is zstd-compressed, 0 otherwise
Each block is compressed with zstd if that shrinks it, then encrypted. The BLAKE3 checksum in the index covers the raw plaintext block before compression or encryption.
Files spanning multiple blocks are reassembled by slicing the relevant byte ranges out of each decoded block.
All limits have conservative defaults and can be raised per-decompression:
| Flag | Default |
|---|---|
--max-file-size |
10 GiB |
--max-block-size |
256 MiB |
--max-m-cost |
1 GiB (Argon2 memory) |
--max-index-size |
100 MiB |
--max-header-size |
64 KiB |
These exist to protect against malformed or malicious archives that claim enormous sizes before any data is read.
scripts/bench.sh generates a ~970 MiB compressible corpus and times cryo compress (default level, 512 KiB blocks) against tar | gzip and tar | zstd at their default settings. Run it yourself with ./scripts/bench.sh (needs tar, gzip, zstd on PATH).
Measured on a 16-core machine, average of 3 runs:
| Time | Output size | |
|---|---|---|
| cryo (sequential, pre-parallel) | 0.76s | 23 MiB |
| cryo (parallel worker pool) | 0.28s | 23 MiB |
tar + gzip -6 |
2.26s | 49 MiB |
tar + zstd (default) |
0.45s | 21 MiB |
Parallelizing compression gives ~2.7x speedup over the sequential implementation with identical output size (block layout and compression level are unchanged, only how blocks get produced). gzip is both slower and produces a larger archive than either. tar+zstd streams the whole archive through one zstd context so it can find long-range matches across block/file boundaries that cryo's fixed-size block chunking can't see, which is why its ratio edges out cryo's on highly repetitive data; cryo's parallel path is still faster in wall-clock time on multi-core machines.
The password is hashed with Argon2id using a per-archive random salt. Block nonces are derived by XOR-ing a random base nonce with the block index, so each block gets a unique nonce without storing one per block. The archive index uses a fixed sentinel value (u64::MAX) as its block number.