Files
android-encrypted-archiver/.planning/phases/06-obfuscation-hardening/06-01-PLAN.md
2026-02-25 02:12:16 +03:00

9.5 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
06-obfuscation-hardening 01 execute 1
src/format.rs
src/archive.rs
src/crypto.rs
tests/golden_vectors.rs
true
FMT-06
FMT-07
FMT-08
truths artifacts key_links
Rust archiver pack() produces archives with XOR-obfuscated headers (magic bytes not visible in raw hex)
Rust archiver pack() encrypts the TOC with AES-256-CBC using a random toc_iv stored in header
Rust archiver pack() inserts random decoy padding between data blocks
Rust unpack() and inspect() correctly decode obfuscated archives (XOR de-obfuscation + TOC decryption)
All existing cargo test pass (unit tests + integration tests + golden vectors)
Flags byte is 0x0F when compression + all 3 obfuscation features are active
path provides contains
src/format.rs XOR_KEY constant, xor_header_buf() function, read_header_auto() with XOR bootstrapping XOR_KEY
path provides contains
src/archive.rs Updated pack() with TOC encryption + decoy padding + XOR header; updated unpack()/inspect() with de-obfuscation xor_header_buf
path provides
src/crypto.rs generate_iv (unchanged) used for toc_iv
from to via pattern
src/archive.rs pack() src/format.rs xor_header_buf() XOR applied to 40-byte header buffer after write_header xor_header_buf
from to via pattern
src/archive.rs pack() src/crypto.rs encrypt_data() TOC plaintext buffer encrypted with toc_iv encrypt_data.*toc
from to via pattern
src/archive.rs unpack()/inspect() src/format.rs XOR bootstrapping on header read, then TOC decryption xor_header_buf|decrypt_data
Implement all three obfuscation features (XOR headers, encrypted TOC, decoy padding) in the Rust archiver and unpacker, with all existing tests passing.

Purpose: Make the archive format resist casual analysis by hiding the header structure, encrypting all metadata, and inserting random noise between data blocks. This is the encoder-side implementation that the Kotlin and Shell decoders will build against.

Output: Updated src/format.rs, src/archive.rs with full obfuscation pipeline. All cargo test pass including existing unit, golden vector, and round-trip integration tests.

<execution_context> @/home/nick/.claude/get-shit-done/workflows/execute-plan.md @/home/nick/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-obfuscation-hardening/06-RESEARCH.md @docs/FORMAT.md (Sections 9.1-9.3 and Section 10 for decode order) @src/format.rs @src/crypto.rs @src/archive.rs @src/key.rs Task 1: Add XOR header obfuscation and TOC encryption to format.rs src/format.rs Add the following to format.rs:
  1. XOR_KEY constant (FORMAT.md Section 9.1):
pub const XOR_KEY: [u8; 8] = [0xA5, 0x3C, 0x96, 0x0F, 0xE1, 0x7B, 0x4D, 0xC8];
  1. xor_header_buf() function that XORs a mutable byte slice (first 40 bytes) with the cyclic 8-byte key. XOR is its own inverse, so the same function encodes and decodes.

  2. read_header_auto() function (replaces or wraps read_header for external use):

    • Read 40 raw bytes.
    • Check bytes 0-3 against MAGIC.
    • If match: parse header normally from the buffer.
    • If NO match: apply xor_header_buf to all 40 bytes, re-check magic. If still wrong, return error.
    • Parse header fields from the (possibly de-XORed) buffer.
    • This function should accept &mut impl (Read + Seek) or work from a [u8; 40] buffer passed in. The simplest approach: accept a [u8; 40] buffer and return a Header (factoring out the parsing from read_header into a parse_header_from_buf helper).
  3. write_header_to_buf() helper that serializes header to a [u8; 40] buffer (instead of directly to writer), so the caller can XOR it before writing.

  4. write_toc_entry_to_vec() / serialize_toc() helper that serializes all TOC entries to a Vec<u8> buffer, so the caller can encrypt the buffer. This can reuse write_toc_entry with a Vec writer.

  5. read_toc_from_buf() helper that parses TOC entries from a byte slice (using a Cursor), so the caller can pass in the decrypted TOC buffer.

Keep the existing read_header() and write_header() functions for backward compatibility with existing tests, but the new pack/unpack code will use the _buf variants.

Add unit tests:

  • XOR round-trip: write header to buf, XOR, XOR again, verify identical to original.
  • XOR changes magic: write header to buf, XOR, verify bytes 0-3 are NOT 0x00 0xEA 0x72 0x63.
  • read_header_auto works with both plain and XOR'd headers. cd /home/nick/Projects/Rust/encrypted_archive && cargo test --lib format -- --nocapture 2>&1 | tail -5 Verify XOR_KEY constant matches FORMAT.md Section 9.1 exactly format.rs has XOR_KEY, xor_header_buf(), read_header_auto() with bootstrapping, and helper functions for buffer-based header/TOC serialization/parsing. All format unit tests pass.
Task 2: Update pack/unpack/inspect with full obfuscation pipeline src/archive.rs Update archive.rs to implement all three obfuscation features. Follow the encoder order from 06-RESEARCH.md:

pack() changes:

  1. Generate decoy padding for each file: let padding_after: u16 = rng.random_range(64..=4096); using rand::Rng. Generate the random bytes too: let mut padding_bytes = vec![0u8; padding_after as usize]; rand::Fill::fill(&mut padding_bytes[..], &mut rng);. Store padding_after and padding_bytes in ProcessedFile struct (add fields).

  2. Compute data offsets accounting for padding: After computing toc_offset + toc_size (which will now be the ENCRYPTED toc size), compute data offsets as current_offset += pf.encrypted_size + pf.padding_after as u32 for each file.

  3. Serialize TOC entries to a buffer: Use the new serialize_toc helper. Include padding_after values in entries.

  4. Encrypt serialized TOC: Generate toc_iv = crypto::generate_iv(). Call crypto::encrypt_data(&toc_plaintext, &KEY, &toc_iv). The toc_size in the header becomes encrypted_toc.len() as u32.

  5. Build header: Set flags bits 1-3 in addition to bit 0 (compression). When all obfuscation is active and files are compressed, flags = 0x0F. Set toc_iv in header.

  6. Compute toc_offset and data offsets: toc_offset = HEADER_SIZE. Data block start = toc_offset + encrypted_toc_size. Then compute per-file data_offset accounting for preceding files' encrypted_size + padding_after.

  7. Serialize header to buffer and XOR: Use write_header_to_buf, then xor_header_buf on the resulting 40-byte buffer.

  8. Write archive: XOR'd header bytes || encrypted TOC bytes || (for each file: ciphertext || padding_bytes).

unpack() changes:

  1. Read 40 bytes raw. Use read_header_auto (XOR bootstrapping).
  2. Check flags bit 1 (0x02) for TOC encryption. If set: seek to toc_offset, read toc_size bytes, decrypt with crypto::decrypt_data(&encrypted_toc, &KEY, &header.toc_iv). Parse TOC from decrypted buffer using read_toc_from_buf.
  3. If TOC not encrypted (backward compat): read TOC directly as before.
  4. Rest of unpack is unchanged -- each file uses data_offset from TOC entries, which already accounts for padding.

inspect() changes:

Apply the same header and TOC de-obfuscation as unpack. Factor out a shared read_archive_metadata() helper that returns (Header, Vec) with all de-obfuscation applied. Both unpack() and inspect() call this helper.

Important notes:

  • Use use rand::Rng; for random_range().
  • Padding range 64..=4096 bytes per file.
  • The --no-compress flag behavior is unchanged.
  • Do NOT add a --no-obfuscate flag yet (always obfuscate). cd /home/nick/Projects/Rust/encrypted_archive && cargo test 2>&1 | tail -10 Run cargo run -- pack test_file.txt -o /tmp/test.bin && xxd /tmp/test.bin | head -3 and verify first 4 bytes are NOT 00 ea 72 63 pack() produces fully obfuscated archives (XOR header + encrypted TOC + decoy padding). unpack() and inspect() correctly de-obfuscate. All cargo test pass including existing integration tests and round-trip tests (which now exercise the full obfuscation pipeline end-to-end).
1. `cargo test` -- all existing unit, golden, and integration tests pass 2. `cargo run -- pack -o /tmp/obf.bin` produces an archive where `xxd /tmp/obf.bin | head -3` shows no recognizable magic bytes 3. `cargo run -- inspect /tmp/obf.bin` correctly displays metadata after de-obfuscation 4. `cargo run -- unpack /tmp/obf.bin -o /tmp/obf_out/` extracts files byte-identically to originals 5. `binwalk /tmp/obf.bin` and `file /tmp/obf.bin` show no recognized signatures

<success_criteria>

  • All three obfuscation features (FMT-06, FMT-07, FMT-08) are implemented in Rust archiver
  • Flags byte is 0x0F for archives with compression + all obfuscation
  • XOR bootstrapping allows decoders to detect both plain and obfuscated archives
  • All cargo test pass (0 failures)
  • Archives are unrecognizable by file/binwalk/strings </success_criteria>
After completion, create `.planning/phases/06-obfuscation-hardening/06-01-SUMMARY.md`