Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

  ____________  ______ 
 / ___/ ___/ / / / __ \
/ /__/ /  / /_/ / /_/ /
\___/_/   \__, /\____/ 
         /____/

CI Crates.io

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.

Install

cargo install cryoarc

Build from source

cargo build --release

Binary lands at target/release/cryo.

Usage

Compress

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 aes

Decompress

cryo decompress <archive.cryo> [options]
Flag Default Description
-o, --output <DIR> ./ Output directory (must be empty or new)
cryo decompress backup.cryo -o ./restored

List

cryo 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.

Verify

cryo verify <archive.cryo>

Reads every block and checks its BLAKE3 checksum without writing anything to disk.

Debugging

Pass -v before the subcommand to enable debug tracing output on stderr:

cryo -v decompress archive.cryo -o ./out

Archive format

[ 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.

Size limits

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.

Benchmarks

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.

Encryption

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages