# ULocker: A Streaming, Re-keyable Encrypted Container for Removable Media, and What Its Framing Still Leaks

Author: yxpil

Suggested arXiv categories: **cs.CR** (primary), cross-lists **cs.OS** and **cs.SE**.

## Abstract

Full-disk encryption protects a USB drive only where a filesystem filter driver
can be installed, and the ciphertext still exposes file names, directory
structure and exact sizes to anyone who reads the raw device. ULocker is a
container format that moves the unit of protection from the block device to a
single file: the archive carries an encrypted index, so names and the directory
tree are not readable without the password, and file content is written as
independently authenticated 1 MiB chunks. A password-derived key unwraps an
index key, which in turn unwraps a random data key, so a password change
rewrites the header rather than the payload. We measure the format rather than
assert its properties. The implementation passes its 212-test suite; a 24-case
tamper matrix is detected in 19 cases, and the five misses are reported with
their consequences (most are benign, one is a rollback with no freshness
counter). A raw-byte audit finds 0 of 11 distinctive file-name, directory-name
and content tokens in the container, but reconstructs the number of records and
a size profile from the public 16-byte framing alone; eight files collapse into
six inferred groups because two sizes are exact multiples of the chunk size.
Password rotation takes 105--108 ms for containers from 8 MiB to 1 GiB, changes
388--399 bytes of header, rewrites 69,632 bytes, and leaves the data zone
byte-identical. Create and extract throughput are 130--211 and 222--261 MiB/s on
one host; the Python process's traced allocation stays at 6.2--7.2 MiB for both
an 8 MiB and a 256 MiB container, which supports the streaming claim, while peak
working set is dominated by the 64 MiB Argon2id cost. The contribution is the
measured boundary: what the framing hides, what it cannot hide, and which
attacks the format is not designed to stop.

## Files

| Path | What it is |
|---|---|
| `main.tex` | The paper: 8 sections, 4 tables, 2 figures. |
| `refs.bib` | Bibliography (AES-GCM, AEAD, Argon2id, HKDF, LUKS, CFS, cold boot, nonce misuse). |
| `common.tex` | Copy of `../_shared/common.tex` (shared preamble). |
| `figures/password.pdf` | Password-change latency vs. container size. |
| `figures/throughput.pdf` | Throughput by layer (AEAD, SHA-256, container). |
| `scripts/_common.py` | Shared harness: imports the repository under test, writes JSON/CSV to `scripts/results/`. |
| `scripts/run_tests.py` | Runs the repository's pytest suite and records per-file counts. |
| `scripts/probe_format.py` | Independent parser for the public format; checks alignment, records, index fields. |
| `scripts/tamper.py` | 24-case single-mutation matrix through the three public entry points. |
| `scripts/metadata_check.py` | Token search, record-length inference, header growth, plaintext header dump. |
| `scripts/bench_password.py` | Password rotation at 8/64/256/1024 MiB. |
| `scripts/bench_throughput.py` | End-to-end and per-layer throughput on 64/256 MiB. |
| `scripts/bench_memory.py` | Peak working set and traced allocation at 8/256 MiB. |
| `scripts/make_figures.py` | Generates the two figures from the JSON. |
| `scripts/results/*.json`, `*.csv` | Raw measurement output. Every quantitative claim traces here. |
| `README.md` | This file. |

## Build

```bash
pdflatex main
bibtex main
pdflatex main
pdflatex main
```

or, with Tectonic:

```bash
tectonic -X compile main.tex --keep-logs
```

The preamble uses only packages in a standard TeX Live image. `natbib` is
loaded after `common.tex`, because the bibliography style is `abbrvnat`.

## Packaging for arXiv

```bash
tar czf ulocker-arxiv.tar.gz main.tex refs.bib common.tex figures/*.pdf
```

Full artifact tarball:

```bash
tar czf ulocker-artifact.tar.gz main.tex refs.bib common.tex figures/*.pdf \
    scripts README.md
```

`common.tex` must stay byte-identical to the shared copy:

```bash
cmp common.tex ../_shared/common.tex && echo identical
```

## Submission steps

1. Start a new arXiv submission at <https://arxiv.org/submit>.
2. Choose **cs.CR** as the primary category; add **cs.OS** and **cs.SE** as cross-lists.
3. Paste the title and the abstract above; author `Daozhendong Yang`.
4. Upload `ulocker-arxiv.tar.gz`. arXiv detects `main.tex` and runs `pdflatex` + `bibtex`.
5. Inspect the auto-built PDF: 2 figures, 4 tables, references rendered by `abbrvnat`.
6. Select a license and submit. A first cs.CR submission may require an endorsement.

## Reproduction

Requirements: Python 3.10+, `cryptography`, `argon2-cffi`, `pytest`, `matplotlib`,
and the ULocker checkout at `../../_repos/ULocker` (override with
`ULOCKER_REPO`). The GUI tests run offscreen and need PyQt6.

```bash
cd papers/ulocker
python scripts/run_tests.py
python scripts/probe_format.py
python scripts/tamper.py
python scripts/metadata_check.py
python scripts/bench_password.py
python scripts/bench_throughput.py
python scripts/bench_memory.py
python scripts/make_figures.py
```

Each script writes its own JSON and CSV under `scripts/results/`. The benchmark
scripts were run on the host recorded in `scripts/results/env.json`; the raw
runs, including min and max, are in the JSON, so the medians in the paper can be
recomputed.

## What the paper does not claim

- It does not claim that 19/24 detection is complete. The five misses are named:
  padding flip, trailing bytes, re-sealed index reordering (key required),
  rollback, and selective extraction.
- It does not claim rollback protection. Replacing a container with an older
  valid snapshot is accepted; there is no freshness counter or external anchor.
- It does not claim that the number of files or their exact sizes are hidden.
  The framing leaks record count and size modulo the chunk size; the paper
  measures the leak (8 files inferred as 6 groups).
- It does not claim that drive binding is cryptographic. It is a policy check
  with an explicit override; a spoofed volume serial defeats it.
- It does not claim that password rotation revokes a compromised data key. It
  protects against password compromise only; the payload is not re-encrypted.
- It does not claim that the default KDF parameters are strong. 64 MiB / 3
  passes is below the RFC 9106 recommendation; the parameters are configurable.
- It does not claim that shredded plaintext is unrecoverable on flash media.
- It does not claim a performance comparison with LUKS, CFS or hardware
  encryption, nor a cold-cache measurement. All timing is single-host.
- It does not claim an independent cryptographic audit or formal verification.
