A predictable salt is not a salt. If the only thing keeping two AES-encrypted ZIP archives from sharing keystream is a value an attacker can guess, you don’t have encryption — you have an inconvenience.
Today we released OxiArc 0.4.1 — a security-hardening release, with two long-standing encoder-ratio limitations closed as a bonus. No archive or stream wire format changed, and no public API was removed from any read/decode path. There is exactly one intentional breaking change: the ZIP write-side salt/header generation API moved from infallible to fallible, because a CSPRNG that can fail must be allowed to say so.
No C, no Fortran, no zlib, no libarchive, no external shared libraries. Just clean, memory-safe archiving and compression that compiles to a single static binary, targets WASM, and runs everywhere. OxiArc is the Pure Rust replacement for the zip, tar, gzip, zstd, and 7-zip tools — and for the Rust crates zip, flate2, zstd, bzip2, lz4, tar, snap, brotli, and miniz_oxide.
Why OxiArc 0.4.1 is a game changer
Four separate defects, each independently serious, all fixed in one pass:
- A weak PRNG fallback masqueraded as an OS CSPRNG.
zip::encryption::generate_saltandZipCrypto::generate_header_randomclaimed to be “sourced from the operating system CSPRNG” in their own doc comments — but on every non-Unix target, and on Unix whenever/dev/urandomcouldn’t be opened (fd exhaustion, chroot, seccomp), they silently fell back to a low-entropy seed built from wall-clock nanoseconds, PID, a per-process counter, a stack address, a heap address, and a thread-id hash, expanded with SHA-1. For WinZip-AES, the salt is the only input differentiating PBKDF2-SHA1 key derivation between archives — a collision is direct AES-CTR keystream reuse. - The x86_64 CRC-32 SIMD path was silently wrong.
crc32_pclmulqdqmixed non-reflected Intel whitepaper fold constants with reflected-mode folding and read the result from the wrong dword lane — but it shipped as a publicunsafe fnwith dispatch hardcoded off specifically because it was unverified, so the bug was latent unless a caller did its own feature detection. - WinZip-AES’s cipher core leaked key material through cache timing.
SubBytes,SubWord, andMixColumns’ GF(2⁸) doubling used a 256-byte S-box lookup and a branch on the high bit — both indexed or branched on secret data, the textbookprime+probetarget. - A crafted 7z folder could chain decompression stages into a bomb. A bind-pair graph that revisits coders could amplify a tiny input through
folder.coders.len()nested stages with no cumulative output budget.
OxiArc 0.4.1 ends all of that:
- A real, unconditional platform-bound CSPRNG (
zip::csprng, new module) —/dev/urandomon Unix,BCryptGenRandomwithBCRYPT_USE_SYSTEM_PREFERRED_RNGon Windows, pure-Rust#[link]declarations, zero external crates — with no software fallback: if the OS source is unreachable, generation now returns a typedOxiArcError::Ioinstead of emitting weak material.generate_salt/generate_header_randomare therefore fallible now. - Corrected, shared reflected-mode fold constants for x86_64, ported directly from the already-validated aarch64 PMULL path, with
SimdCrc32Dispatchernow actually dispatching to PCLMULQDQ + SSE4.1 when detected. - A bitsliced Boyar-Peralta constant-time AES S-box (113 AND/XOR/NOT gates over eight 16-bit bit planes) and a mask-based
xtime— no table indexed with secret data, no branch taken on it, anywhere in the cipher. Ciphertext is provably unchanged: checked against the FIPS 197 table for all 256 inputs across all 16 lanes. - Per-folder visited-coder tracking plus a ratio-capped cumulative output budget (10^6× packed size, floored at 64 MiB, ceilinged at 16 GiB) for 7z decompression, checked both against declared sizes before decoding and actual bytes produced after.
Technical Deep Dive: the security pass, layer by layer
- Randomness.
zip::csprngis the single, unconditional source of cryptographic randomness for ZIP writes — nocfg-gated software fallback exists anywhere downstream of it anymore. - Constant-time crypto.
zip::aes_ctreplaces every secret-dependent table lookup and branch in the AES core with a bitsliced circuit, verified bit-for-bit against FIPS 197 and NIST SP 800-38A ECB test vectors on an unstructured key. - Verified SIMD.
crc_simd’s x86_64 and aarch64 paths now share onereflected_constantsmodule and are cross-checked against the scalar slicing-by-8 reference across a 0–4096-byte length sweep plus 100 randomized-input vectors. - Bounded decompression.
decode_coder_chainin the 7z folder walker enforces per-coder single-decode and a cumulative byte budget, so a crafted bind-pair graph can no longer turn a small input into unbounded memory growth.
Every layer ships with new regression coverage: zip_encryption_e2e.rs for full AES-256/ZipCrypto write→read round-trips including wrong-password rejection, dispatched_crc32_matches_software_reference for the corrected SIMD path, and iso_sevenz_mutation.rs for a deterministic bit-flip/byte-mutation fuzz-adjacent regression suite against the two least-covered untrusted-input parsers.
Getting Started
cargo add [email protected]
use oxiarc_archive::zip::{crypto::ZipCrypto, encryption};
// Salt/header generation is now fallible -- it reports an error instead of
// silently degrading to a weak seed if the OS CSPRNG can't be reached.
let salt = encryption::generate_salt(16)?;
let header_random = ZipCrypto::generate_header_random()?;
What’s New in 0.4.1
- Security: OS-CSPRNG-only ZIP salt/header generation (
zip::csprng, no fallback, now fallible); constant-time AES for WinZip-AES ZIP encryption (zip::aes_ct); corrected + enabled x86_64 CRC-32 PCLMULQDQ SIMD dispatch; bounded 7z folder coder-chain decompression against amplification bombs. - Fixed:
BrotliCompressoris now genuinely streaming instead of buffering the whole input untilfinish();repair_zip/repair_tarusize-overflow fix on attacker-controlled sizes; XZ stream-footer Backward Size validation and construction now useu32::try_frominstead of a wrappingas u32cast on both the read and write side. - Added — encoder-ratio limitations closed: Zstandard gained
FSE_Compressed_Modesequence-table encoding (was Predefined/RLE-only) — 173,521 → 109,359 bytes (-37%) on a structured-record corpus. Brotli gained literal, insert-and-copy, and distance block splitting with per-context histogram assignment at quality 10-11 — both changes alter only the bytes produced, remain RFC-conformant and reference-decodable, and are strictly smaller-or-equal, never larger. - Added — LZH legacy methods:
-lh2-,-lh3-,-lzs-,-lz4-,-lz5-,-pm0-now decode and encode (previously reported asUnknownand refused at extraction). Verified against reallha(Lhasa 0.6.0) and, for-lh2-/-lh3-(which Lhasa doesn’t implement), against the canonical LHa for UNIXdhuf.c/shuf.cdecode path. - Added: TAR PAX 1.0 sparse format support (
GNU.sparse.major=1/minor=0) in both the seekable and streaming readers. - 2,658 tests passing, 0 failed (2,519 via nextest, all-features, 13/13 crates green + 139 doctests); zero clippy warnings (
--all-features --all-targets -D warnings); zero rustdoc warnings;cargo fmt --all --checkclean; newdeny.tomlbans for the full COOLJAPAN replacement table.
Tips
- If you call
zip::encryption::generate_saltorZipCrypto::generate_header_randomdirectly, handle the newResult. This is the release’s one breaking change — the OS CSPRNG can now fail loudly instead of degrading silently, so the signature reflects that. - You don’t need to do anything to get the constant-time AES fix or the corrected CRC-32 dispatch — both are internal to the existing encryption and CRC APIs; same call sites, same output, just no longer leaking timing information or computing the wrong checksum on x86_64.
- Zstandard callers writing at level 1+ get smaller output for free —
FSE_Compressed_Modeis chosen automatically per symbol category whenever it beats the predefined table and RLE on total bits including the table description. - Brotli’s ratio gains only apply at quality 10-11 — quality 0-9 output is byte-frozen and unchanged, so if you compress at a lower quality for speed, this release changes nothing about your output.
repair_zip/repair_tarare now safer against declared-size overflow on 32-bit targets — if you use them against untrusted/truncated archives, upgrade for thesaturating_addfix alone.
This is the foundation
Correct, non-leaking encryption and a decompression path that can’t be turned into a bomb matter most for whatever handles untrusted archives at the edge. NumRS2, SciRS2, ToRSh, RusMES, FVRS, TrustFormers, SkLearS, OxiGeo, VoiRS, and OxiMedia all pin oxiarc-* crates for archive and compression I/O.
Repository: https://github.com/cool-japan/oxiarc
Star the repo if a ZIP library correctly failing instead of silently handing you a weak salt is the bar it should always have cleared.
The era of “close enough” cryptographic randomness is over. Pure Rust archiving that’s fast, safe, and sovereign — is here.
— KitaSan at COOLJAPAN OÜ August 6, 2026