Version: 0.2-draft
Language: Rust (stable toolchain, x86_64-unknown-linux-musl for embedded binary)
Status: Specification only — not yet implemented
License: GPL-3.0 (matching Galdralag-firmware)
CESS conformance: CESS-FULL (targets CESS v0.2-draft; see §4 and §15)
Glossary: Plain-language definitions of technical terms
AI contributors: AGENTS.md (see also CLAUDE.md); Cursor rules live in .cursor/rules/*.mdc
SplitDisk is a command-line tool and initramfs-resident assembly agent for splitting a disk image or filesystem into k-of-n physical USB shares, each bootable, such that any k drives inserted in sequence can reconstruct and write the original content to a target disk. Members holding drives are not informed of their share index, the total number of shares, or the threshold required.
Optional integration with the Galdralag hardware security token (Baochip-1x running Galdralag firmware) provides on-device PIN verification, Shamir key-share storage, and Brainpool/post-quantum encryption without requiring any custom host-side cryptographic driver beyond a standard CCID stack.
| Goal | Description |
|---|---|
| Holder anonymity | A drive holder does not know their share index, the value of k or n, or the content. |
| Coercion resistance | A holder genuinely cannot answer "how many others are there?" or "are you required?" |
| Physical confidentiality | Drive contents look like opaque binary blobs. No filenames, labels, or metadata reveal the scheme. |
| Multi-factor unlock | Each drive requires PIN (≥ 5 alphanumeric characters) and optionally biometric before its share is accepted. |
| Long-term security | Encryption must remain secure for decades. Hybrid classical + post-quantum where available. |
| Fault tolerance | n > k so that absent or unavailable members do not prevent reconstruction. |
| Integrity | Each share chunk is hashed with BLAKE3; corruption is detected before reconstruction begins. |
| Resumable assembly | If power is lost mid-write, assembly can resume from a checkpoint. |
| Verified write | Reconstruction is dry-run tested against the source hash before the operator is permitted to destroy the original. |
| Source destruction | After verified write, the source drive or image file can be overwritten with random data so the plaintext cannot be recovered from the enrollment machine. |
| CESS conformance | All cryptographic primitives conform to CESS v0.2 algorithm registry; NSA-designed and NIST-only primitives are excluded. |
Non-goals: network transmission of shares, online key management, integration with cloud storage.
SplitDisk consists of two programs:
splitdisk-create— run once on an air-gapped enrollment machine to produce n share carriers (USB drives, Galdralag tokens, or a mix) from a source disk image.splitdisk-assemble— a statically linked binary embedded in the initramfs of every share USB drive, or invoked on the assembly machine when all shares are Galdralag tokens. Orchestrates reconstruction.
USB drive mode:
┌─────────────────────────────────────────────────────┐
│ splitdisk-create │
│ │
│ source image ──► [encrypt] ──► [Reed-Solomon │
│ split k-of-n] │
│ │ │
│ [SSS split of session key] │
│ │ │
│ ┌────────────┼────────────┐ │
│ USB 1 USB 2 ... USB n │
│ (bootable, (bootable, (bootable, │
│ chunk 1, chunk 2, chunk n, │
│ key share 1) key share 2) key share n) │
└─────────────────────────────────────────────────────┘
At boot time on any share USB:
┌─────────────────────────────────────────────────────┐
│ splitdisk-assemble (initramfs) │
│ │
│ Boot USB ──► read own chunk + key share silently │
│ Loop until k shares collected: │
│ insert drive ──► PIN check ──► biometric check │
│ ──► read chunk + key share ──► progress bar +1 │
│ Reconstruct session key via SSS │
│ Decrypt + Reed-Solomon decode │
│ Write to target disk with checkpoint journal │
└─────────────────────────────────────────────────────┘
Galdralag token mode:
┌─────────────────────────────────────────────────────┐
│ splitdisk-create │
│ │
│ source image ──► [encrypt] ──► [Reed-Solomon │
│ split k-of-n] │
│ │ │
│ [SSS split of session key] │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ Token 1 Token 2 ... Token n │
│ RRAM: key share RRAM: key share RRAM: key share│
│ SD: chunk 1 SD: chunk 2 SD: chunk n │
└─────────────────────────────────────────────────────┘
At assembly time (any machine, no bootable share USB needed):
┌─────────────────────────────────────────────────────┐
│ splitdisk-assemble │
│ │
│ Loop until k tokens inserted: │
│ insert token ──► CCID session ──► PIN on token │
│ ──► read chunk from SD ──► read key share │
│ from RRAM ──► progress bar +1 │
│ Reconstruct session key via SSS │
│ Decrypt + Reed-Solomon decode │
│ Write to target disk with checkpoint journal │
└─────────────────────────────────────────────────────┘
Mixed mode (some USB drives, some tokens) is also supported within the same scheme. The assembly agent handles both share types transparently.
SplitDisk targets CESS-FULL conformance (CESS v0.2-draft). All primitives are drawn from the CESS Algorithm Registry. NSA-designed primitives (AES, SHA-2, SHA-3) and NIST-only curves (P-256, P-384, P-521) are excluded. ML-KEM is excluded per CESS policy; FrodoKEM-1344 is the approved post-quantum KEM where PQ is enabled.
All encryption uses a hybrid scheme for long-term security:
Session key: 32 random bytes from hardware TRNG or OS CSPRNG
Classical KEM: Ephemeral ECDH over BrainpoolP384r1 (default, outer session)
or BrainpoolP512r1 (inner profile, --cipher brainpool512)
RFC 5639, BSI TR-03111
Shared secret: x-coordinate, fixed-length big-endian encoding
Point validation: mandatory (invalid curve points rejected)
Post-quantum KEM: FrodoKEM-1344 (feature-gated, --features pq)
Hybrid only; never standalone
KDF: HKDF-BLAKE3 (RFC 5869 structure, HMAC-BLAKE3 as PRF)
info strings: "cess-kem-v1" (KEM), "cess-pin-v1" (PIN wrap),
"cess-outer-envelope-v1" (outer K_outer)
Combined (hybrid):
session_key = HKDF-BLAKE3(
ikm = classical_shared_secret || pq_shared_secret,
info = "cess-kem-v1"
)
Bulk cipher (select one):
ChaCha20-Poly1305 (RFC 8439, 32-byte key, 12-byte nonce) [default]
Serpent-256-CTR + Poly1305 (32-byte key, RFC 8439 MAC layout)
Twofish-256-CTR + Poly1305 (256-bit key, 128-bit block, same MAC layout)
Cascade: ChaCha20-Poly1305 inner + Serpent-256-CTR+Poly1305 outer
Cascade: ChaCha20-Poly1305 inner + Twofish-256-CTR+Poly1305 outer
Cascade: Twofish inner + Serpent outer
Triple cascade: ChaCha inner + Serpent middle + Twofish outer
Suite selection is encoded as a CESS suite_id (16-bit, see §15).
Outer wrapper (Mode A): When --mode a is used (default for USB drive
mode), an outer ChaCha20-Poly1305 layer keyed from ephemeral BrainpoolP384r1
ECDH conceals the suite_id and inner ciphertext from observers. The suite_id
is never transmitted in cleartext. See CESS v0.2 §6.6 and §8.
Simple mode (Mode B): When --mode b is used, the cipher profile is
pre-agreed and no suite_id appears on the wire. Suitable for simpler deployments
where metadata concealment is not required.
BrainpoolP256r1 + ChaCha20-Poly1305: A lightweight profile using
BrainpoolP256r1 is available for less sensitive deployments via
--cipher brainpool256. This operates in Mode B (no outer wrapper, suite
profile visible); it is outside the CESS-FULL normative profile but supported
as a documented extension. Shared secret is the x-coordinate, 32-byte
fixed-length big-endian encoding.
Reed-Solomon erasure coding splits the encrypted image into n chunks, any k of which suffice to reconstruct.
chunk_size ≈ image_size / k (plus RS parity overhead ~10%)
Each chunk is prefixed with a BLAKE3 hash for integrity verification before reconstruction begins.
The 32-byte session key is split using Shamir's Secret Sharing over GF(2⁸)
(reduction polynomial x^8 + x^4 + x^3 + x + 1, identical to CESS §5.1)
into n shares, threshold k.
Each key share is encrypted at rest on the USB using the PIN-derived key (see §5.2), so raw access to the drive bytes does not expose the share.
All cryptographic primitives come from audited crates. Nothing is implemented
in-tree. Crates are selected for no_std + musl compatibility where required
by the embedded initramfs binary.
| Crate | Purpose | Audit status |
|---|---|---|
chacha20poly1305 |
Bulk AEAD (primary) | RustCrypto audited |
serpent |
Serpent-256 bulk cipher | Review pending |
twofish |
Twofish-256 bulk cipher | Review pending |
poly1305 |
MAC for Serpent/Twofish CTR modes | RustCrypto audited |
p384 |
BrainpoolP384r1 ECDH base | RustCrypto audited |
hkdf |
HKDF structure (RFC 5869) | RustCrypto audited |
blake3 |
BLAKE3 integrity and HKDF PRF | Audited (NCC Group, Kudelski) |
argon2 |
PIN hashing (Argon2id) | RustCrypto audited |
vsss-rs |
Shamir secret sharing GF(2⁸) | Independent review |
reed-solomon-erasure |
Data splitting | Review pending |
zeroize |
Secret memory clearing | RustCrypto audited |
subtle |
Constant-time comparisons | RustCrypto audited |
sequoia-openpgp |
Optional GPG envelope (Brainpool keys) | Active maintenance |
pqcrypto-frodo |
FrodoKEM-1344 (feature-gated) | Pending independent audit |
One can also check integrity of crates with this tool: https://github.com/Supermagnum/crypto-verify
Post-quantum features are gated behind --features pq and carry a warning
that the underlying crates have not been independently audited, matching the
policy in Galdralag firmware and CESS §7.3.
Each drive requires the holder to present:
| Factor | Mechanism | Required |
|---|---|---|
| Something they have | The USB drive | Always |
| Something they know | PIN ≥ 5 alphanumeric characters | Always |
| Something they are | Biometric (iris/retina scan) | Optional per drive |
All factors are checked before the drive's key share is released into memory. Failure in any factor does not indicate which factor failed — the UI reports only "Authentication failed. Please remove drive."
- Minimum length: 5 alphanumeric characters, enforced at input boundary before any hash comparison.
- Hashing: Argon2id (RFC 9106) with a per-drive random 16-byte salt, memory = 64 MiB, iterations = 3, parallelism = 4 (CESS §5.4 profile).
- The Argon2id hash is stored in the drive's hidden data partition.
- PIN-derived key:
K_pin = HKDF-BLAKE3(argon2id_output, info="cess-pin-v1")→ 32 bytes, wrapping the key share with ChaCha20-Poly1305 (default) or the profile's registered AEAD. - Attempt limit: 5 per drive insertion (configurable 3–10 at creation time).
- On limit exhaustion: drive is ejected and must be re-inserted. The PIN hash is not modified (no lockout wipe, unlike a hardware token).
- Cool-down: 30-second delay after each failed attempt.
Biometric support is optional and configured per drive at enrollment time.
Supported hardware: USB iris scanners with Linux drivers (e.g. IriShield MK series, or compatible devices exposing a V4L2 or proprietary SDK interface).
Enrollment flow:
- Capture iris image on the enrollment machine using the scanner.
- Extract ISO/IEC 19794-6 feature template.
- Encrypt template with
K_pin(PIN must be correct first). - Store encrypted template in hidden partition alongside PIN hash.
Verification flow:
- PIN verified first.
K_pinderived → template decrypted in memory.- Iris capture performed.
- Feature extraction and comparison against stored template, within a configurable tolerance threshold (Hamming distance on iris codes, threshold ≤ 0.32 by default, following ISO recommendations).
- On match: key share released. Template and derived keys zeroized from memory.
- On failure: 3 biometric attempts per successful PIN entry, then drive ejected.
The biometric template never leaves the drive unencrypted. No central biometric database exists.
When --galdralag mode is used, the Galdralag Baochip-1x token is the
complete share — no separate USB drive is needed or issued to the member.
The token holds both the encrypted data chunk and the key share in its
on-device storage (RRAM vault or optional SD card), and presents itself to
the assembly machine over USB. The member carries only the token.
Storage tiers on the token:
| Storage | Capacity | Used for |
|---|---|---|
| RRAM vault | Small (key material only) | SSS key share, PIN policy, metadata |
| Optional SD card | Large (bulk data) | Encrypted data chunk |
If the token has an SD card fitted, the data chunk is stored there. If no SD
card is present, the token cannot hold a data chunk and --galdralag mode
requires SD card presence — splitdisk-create will check and abort if the
card is absent or insufficient at enrollment time.
What the token provides during assembly:
- Data chunk — streamed from SD card over USB to the assembly machine.
- Key share — held in RRAM vault, released only after PIN and optional biometric verification on the token itself.
- BrainpoolP384r1 or BrainpoolP512r1 ECDH — wraps the key share in transit using the token's on-device keys.
- Authenticated ephemeral ECDH session — forward-secret channel between token and assembly agent; the key share never appears on the USB bus in plaintext.
- Hardware TRNG — used for all randomness during enrollment and assembly.
- Hardware PIN counter with zeroization — 3–10 configurable attempts; on exhaustion the token zeroizes its RRAM, permanently destroying the share.
Assembly agent behaviour:
The agent detects a Galdralag token by its CCID descriptor on any USB port. When detected, it opens an ephemeral ECDH session, requests PIN entry (handled on the token's own PIN policy, not the software attempt counter), reads the data chunk from the SD card, and retrieves the decrypted key share over the authenticated channel. No separate USB drive insertion is prompted for that share.
Requirements:
- CCID-capable host (
pcscd+cciddriver included in initramfs) - Token fitted with SD card of sufficient capacity (
ceil(image_size / k) + 10%) - Token provisioned at enrollment via
galdra device shamir-storeandgaldra device chunk-write
Mixed modes are supported: some members can hold USB drives (standard mode) and others can hold Galdralag tokens, within the same k-of-n scheme. The assembly agent handles both transparently in the same insertion loop.
Each drive presents a standard bootable layout. To an observer mounting the drive on any operating system, it looks like a minimal bootable Linux USB with some unremarkable binary files.
/dev/sdX1 FAT32 ~256 MiB EFI System Partition
/EFI/BOOT/BOOTX64.EFI Standard EFI bootloader (GRUB or systemd-boot)
/boot/vmlinuz Linux kernel (minimal config)
/boot/initramfs.img initramfs containing splitdisk-assemble
/dev/sdX2 ext4 remainder System partition
/usr/bin/splitdisk-assemble (statically linked, stripped)
/share/chunk.bin Encrypted RS data chunk (opaque bytes)
/share/auth/pin.hash Argon2id PIN hash
/share/auth/retina.enc Encrypted biometric template (if enrolled)
/share/meta.bin Encrypted metadata (share index, k, n, chunk hash)
meta.bin is encrypted with K_pin, so share index and scheme parameters are
not visible without the correct PIN. The partition label is blank. The volume
UUID is randomly generated and not reused across drives.
GRUB is configured with GRUB_TIMEOUT=0 and no menu. The screen is blank
during boot until the assembly agent TUI appears.
The TUI is built with ratatui and runs fullscreen. It intentionally reveals
no information about share indices or member count.
┌─────────────────────────────────────────────────────┐
│ │
│ SECURE ASSEMBLY SYSTEM │
│ │
│ Insert drives one at a time. │
│ Remove each drive when prompted. │
│ │
│ Progress: ░░░░░░░░░░ │
│ │
│ Waiting for first drive... │
│ │
└─────────────────────────────────────────────────────┘
The booting drive's own share is read silently and automatically at startup. Progress advances by one without user interaction.
┌─────────────────────────────────────────────────────┐
│ │
│ Drive detected. │
│ │
│ Enter PIN: ▓▓▓▓▓▓▓▓░ │
│ (minimum 5 characters) │
│ │
│ Press Enter to confirm. │
│ │
└─────────────────────────────────────────────────────┘
Input is masked with ▓ characters. Backspace supported. No character count
displayed (avoid leaking PIN length).
┌─────────────────────────────────────────────────────┐
│ │
│ Look into the scanner. │
│ │
│ [ Scanning... ████████░░ ] │
│ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ │
│ SECURE ASSEMBLY SYSTEM │
│ │
│ Progress: ████████░░░░░░░░░░░░ 3 drives read │
│ │
│ Please insert another drive. │
│ │
└─────────────────────────────────────────────────────┘
No member names, no share IDs, no indication of how many remain.
If the same drive is inserted twice (detected by comparing a hidden drive
fingerprint stored in meta.bin), the progress bar does not advance:
This drive has already been read. Please insert a different one.
No indication that it is "drive 4" or "already seen drive 4."
When k shares are collected:
┌─────────────────────────────────────────────────────┐
│ │
│ All required drives collected. │
│ │
│ Verifying integrity... ████████████ 100% │
│ Reconstructing... ████████████ 100% │
│ Decrypting... ██████░░░░░░ 63% │
│ Writing to disk... ████░░░░░░░░ 41% │
│ │
│ Target: /dev/sdb (238.5 GiB) │
│ │
└─────────────────────────────────────────────────────┘
Before writing, the agent lists available block devices (excluding share USBs) and prompts for confirmation. It checks that the target is large enough and warns if it is not.
All failure messages are generic:
Authentication failed. Please remove drive.
Integrity check failed. Drive may be corrupted.
Target disk too small. Minimum required: 128.4 GiB.
splitdisk-create \
--input /dev/sda \
--drives /dev/sdb /dev/sdc /dev/sdd /dev/sde /dev/sdf /dev/sdg \
--threshold 3 \
--cipher brainpool384 \
--bulk-cipher chacha20 \
--pin-attempts 5 \
--shredThis creates a 3-of-6 scheme using BrainpoolP384r1 + ChaCha20-Poly1305 in Mode A (outer wrapper concealing suite identity). The tool announces required drive sizes, writes all 6 drives, verifies reconstruction with 3 drives, then offers to shred the source.
| Flag | Description | Default |
|---|---|---|
--input <path> |
Source block device or image file | Required |
--drives <paths> |
Target USB devices (exactly n) | Required |
--threshold <k> |
Minimum shares for reconstruction | Required |
--cipher <n> |
brainpool256, brainpool384, brainpool512 |
brainpool384 |
--bulk-cipher <n> |
chacha20, serpent, twofish, cascade-cs, cascade-ct, cascade-ts, cascade-cst |
chacha20 |
--mode <n> |
a (outer wrapper, suite concealed) or b (pre-agreed, no wrapper) |
a |
--gpg-recipients <fps> |
Wrap session key for GPG recipients (Brainpool keys) | None |
--pin-attempts <n> |
Failed PIN attempts before cooldown per insertion | 5 |
--biometric |
Enroll biometric at creation time (requires scanner) | Off |
--galdralag |
Store key shares on Galdralag tokens instead of drives | Off |
--features pq |
Enable FrodoKEM-1344 post-quantum hybrid (feature-gated) | Off |
--label <text> |
Boot-time display label (shown before PIN prompt) | None |
--checkpoint-dir <path> |
Directory on target disk for resume journal | /var/splitdisk |
--shred |
Offer to overwrite source with random data after verified write | Off |
--shred-passes <n> |
Reserved; always 1 (multiple passes not meaningful on modern media) | 1 |
Bulk cipher shorthand for --bulk-cipher:
| Value | Meaning |
|---|---|
chacha20 |
ChaCha20-Poly1305 single layer |
serpent |
Serpent-256-CTR + Poly1305 single layer |
twofish |
Twofish-256-CTR + Poly1305 single layer |
cascade-cs |
ChaCha inner + Serpent outer (CESS default cascade) |
cascade-ct |
ChaCha inner + Twofish outer |
cascade-ts |
Twofish inner + Serpent outer |
cascade-cst |
Triple: ChaCha inner + Serpent middle + Twofish outer |
Before any writes, splitdisk-create inspects the source image and the chosen
k and n parameters, then prints a clear human-readable summary and requires
explicit confirmation before proceeding.
USB drive mode example:
Source: /dev/sda (476.9 GiB)
Scheme: 3-of-6 (any 3 drives reconstruct the content)
Cipher: BrainpoolP384r1 + ChaCha20-Poly1305 [Mode A]
CESS suite_id: 0x0001
You will need: 6 × USB drives, each at least 128 GiB
(chunk size: ~159 GiB per drive including boot environment
and Reed-Solomon parity overhead)
Drives provided: /dev/sdb 256 GiB ✓
/dev/sdc 256 GiB ✓
/dev/sdd 256 GiB ✓
/dev/sde 256 GiB ✓
/dev/sdf 256 GiB ✓
/dev/sdg 128 GiB ✗ INSUFFICIENT — need 159 GiB
WARNING: /dev/sdg is too small. Aborting.
Galdralag token mode example:
Source: /dev/sda (476.9 GiB)
Scheme: 3-of-6 (any 3 tokens reconstruct the content)
Cipher: BrainpoolP384r1 + ChaCha20-Poly1305 [Mode A]
CESS suite_id: 0x0001
You will need: 6 × Galdralag tokens, each with SD card ≥ 159 GiB
Key shares fit in RRAM — no minimum RRAM size beyond firmware.
Tokens detected:
Token A SD: 256 GiB RRAM: OK ✓
Token B SD: 256 GiB RRAM: OK ✓
Token C SD: 256 GiB RRAM: OK ✓
Token D SD: 256 GiB RRAM: OK ✓
Token E SD: 256 GiB RRAM: OK ✓
Token F SD: 64 GiB RRAM: OK ✗ INSUFFICIENT — SD needs ≥ 159 GiB
WARNING: Token F SD card is too small. Aborting.
If all carriers are sufficient:
All 6 carriers are sufficient.
This operation will DESTROY all data on the 6 carriers listed above.
Type YES to continue, or press Ctrl-C to abort: _
Required size per carrier:
required_per_carrier = ceil(image_size / k)
+ rs_parity_overhead (~10% of chunk size)
+ boot_environment_size (512 MiB, USB drives only)
splitdisk-create performs enrollment interactively after pre-flight passes:
- Reads and BLAKE3-hashes the source image (streaming, reports progress).
- Generates 32-byte session key from hardware TRNG or OS CSPRNG.
- Encrypts image with chosen cipher suite (streaming, reports progress).
- Splits ciphertext into n chunks via Reed-Solomon.
- Splits session key into n shares via SSS (vsss-rs).
- For each drive in sequence:
a. Writes bootloader, kernel, initramfs (produced by
splitdisk-image; see §10). b. Writes data chunk. c. Prompts operator to set PIN for this drive (entered twice for confirmation, minimum 5 alphanumeric characters enforced before hashing). d. If--biometric: prompts to scan iris for this drive's holder. e. Encrypts key share and metadata with PIN-derived key. f. Writes encrypted share data. g. Reads back and verifies written chunk against source BLAKE3 hash. - Post-write verification — see §8.5.
- Source shredding — see §8.6.
The session key is zeroized from memory immediately after step 6 completes.
After all n drives are written, splitdisk-create performs a mandatory
reconstruction dry-run using exactly k drives to confirm the scheme is viable
before the operator is permitted to shred the source.
The operator is prompted to insert any k of the n drives one at a time. For each inserted drive:
Verification — insert any 3 of your 6 drives for reconstruction test.
Drive 1 of 3: insert a drive now...
✓ Drive accepted. Chunk hash verified.
Drive 2 of 3: insert a drive now...
✓ Drive accepted. Chunk hash verified.
Drive 3 of 3: insert a drive now...
✓ Drive accepted. Chunk hash verified.
Reconstructing session key... ✓
Decrypting and decoding... ✓
Output hash matches source: ✓ (BLAKE3: a3f9…c214)
Verification successful. All 3 tested drives can reconstruct the content.
The reconstructed output is written to a temporary memory-mapped buffer and its BLAKE3 hash is compared against the hash of the source image taken in step 1 of §8.4. The temporary output is zeroized and discarded after verification; it is never written to a persistent disk.
Verification is not optional and cannot be skipped.
Source shredding is offered only after §8.5 verification succeeds. It is
explicitly opt-in — the operator must pass --shred at invocation time, and
confirm again interactively before shredding begins.
Verification complete. The source drive or image can now be securely erased.
Target for shredding: /dev/sda (476.9 GiB)
WARNING: This will PERMANENTLY AND IRRECOVERABLY destroy all data on /dev/sda.
The content now exists only on the 6 USB drives.
Ensure all drives are physically secured before proceeding.
Type SHRED to overwrite with random data, or press Ctrl-C to skip: _
Shred procedure:
- Open the source block device or file for writing.
- Stream random bytes from the OS CSPRNG (
/dev/urandomviagetrandom(2)) over the entire device in 4 MiB blocks, reporting progress. - Call
fsync(2)after each block to ensure writes reach the storage medium. - On completion, call
ioctl(BLKFLSBUF)to flush the kernel block cache. - Report final status with bytes written and time elapsed.
Shredding /dev/sda...
████████████████████████████ 100% 476.9 GiB / 476.9 GiB
Time elapsed: 14m 32s
Bytes written: 512,110,190,592
Shred complete. The source has been overwritten with random data.
Notes on shred effectiveness:
- On HDDs: a single overwrite pass with random data is sufficient to prevent software-based recovery.
- On SSDs and flash storage: wear levelling may preserve some data in overprovisioned cells. The tool prints a warning for detected SSDs and recommends ATA Secure Erase or NVMe Format after the overwrite.
- On image files: the file is overwritten in place then unlinked. The tool warns that filesystem journalling or snapshots on the host may retain copies.
If assembly is interrupted mid-write, a journal file is written to the target disk tracking which chunks have been fully written, the byte offset of the last confirmed write, and a BLAKE3 hash of each written segment.
On next boot, if a partial journal is detected, the agent offers to resume rather than restart. Resume requires the same k drives to be re-inserted (key material is not cached between boots).
A dedicated Rust binary, splitdisk-image, is responsible for producing the
bootable per-drive USB image. It is invoked internally by splitdisk-create
during enrollment (step 6a of §8.4) and can also be run standalone for
testing. No external shell scripts, dracut, mkinitcpio, or manually
maintained cpio archives are required; the entire boot image is assembled
from Rust.
- Pure Rust toolchain: the image builder is a normal Rust binary that runs on the enrollment machine (Linux x86-64). It does not shell out to system tools beyond the minimum required for block device I/O.
- Reproducible output: given the same kernel blob and
splitdisk-assemblebinary, the output image is byte-for-byte identical across runs (except for the per-drive random UUID and share data). - Self-contained: the
splitdisk-assemblebinary embedded in the initramfs is statically linked against musl libc (x86_64-unknown-linux-musl) and requires no shared libraries in the initramfs environment.
splitdisk-image produces a raw disk image for a single share USB drive,
containing:
| Component | How produced |
|---|---|
| GPT partition table | Written directly using the gpt crate |
| EFI System Partition (FAT32, ~256 MiB) | Constructed using the fatfs crate; contains GRUB EFI binary and GRUB config |
| GRUB EFI binary | Bundled as a compiled-in byte slice (fetched at build time via build.rs from a pinned upstream release, verified by SHA-256 → BLAKE3 of the blob) |
| Linux kernel (bzImage) | Bundled as a compiled-in byte slice (pinned version, verified) |
| initramfs (cpio.gz) | Assembled entirely in Rust; see §10.3 |
| System partition (ext4) | Written using the ext4-rs or equivalent crate |
| Share data | Written to system partition by splitdisk-create after image base is laid down |
The initramfs is a gzip-compressed cpio archive assembled entirely in Rust
using the cpio crate (or equivalent). It contains the minimum set of
entries required for splitdisk-assemble to run:
/init → shell script or compiled init stub invoking
splitdisk-assemble
/usr/bin/splitdisk-assemble → statically linked, stripped Rust binary
(x86_64-unknown-linux-musl)
/usr/lib/pcsc/drivers/ → CCID driver shared objects (bundled)
/etc/reader.conf.d/ → pcscd reader config
/dev/ → empty; populated by the kernel at boot
/proc/ → empty; mounted by init
/sys/ → empty; mounted by init
/tmp/ → empty tmpfs mount point
pcscd is invoked as a subprocess by splitdisk-assemble at runtime (or
linked statically if a suitable no_std/musl build is available). The CCID
driver .so files are bundled as byte slices extracted at build time from a
pinned upstream release and verified by BLAKE3 hash before embedding.
The init entry is a minimal compiled Rust binary (part of the
splitdisk-image crate's output) that mounts /proc, /sys, and a tmpfs
on /tmp, then exec's splitdisk-assemble. It does not use a shell.
GRUB is configured with:
set timeout=0
set default=0
menuentry "SplitDisk" {
linux /boot/vmlinuz quiet loglevel=0 rd.udev.log_level=0
initrd /boot/initramfs.img
}
No menu is shown. The kernel command line suppresses all console output until
splitdisk-assemble takes control of the terminal for its TUI.
The bundled kernel is built from a minimal defconfig plus the following
additions, compiled and pinned as a byte slice in build.rs:
| Config option | Reason |
|---|---|
CONFIG_USB_SUPPORT |
USB host stack |
CONFIG_USB_XHCI_HCD |
USB 3.x host controller |
CONFIG_USB_EHCI_HCD |
USB 2.0 host controller |
CONFIG_USB_STORAGE |
USB mass storage (share drives) |
CONFIG_USB_SERIAL |
USB serial (CCID fallback) |
CONFIG_USB_CHIPIDEA |
CCID smartcard transport |
CONFIG_PCMCIA |
Smartcard reader support |
CONFIG_SCSI |
Required for USB storage |
CONFIG_EXT4_FS |
Read system partition |
CONFIG_VFAT_FS |
Read EFI partition |
CONFIG_TMPFS |
/tmp for runtime state |
CONFIG_PROC_FS |
/proc |
CONFIG_SYSFS |
/sys |
CONFIG_TTY |
Terminal for TUI |
CONFIG_VT |
Virtual terminal |
CONFIG_FB |
Framebuffer (TUI rendering) |
CONFIG_DRM |
GPU-neutral display |
CONFIG_CRYPTO_CHACHA20 |
Kernel-side not required; userspace only |
Network, sound, Bluetooth, Wi-Fi, and all non-essential drivers are disabled. The resulting bzImage is expected to be well under 10 MiB.
All bundled blobs (GRUB EFI binary, Linux kernel bzImage, CCID driver .so
files) are:
- Fetched at crate build time by
build.rsfrom pinned upstream URLs. - Verified against a BLAKE3 hash embedded in the source tree.
- Embedded as
include_bytes!()byte slices in the Rust binary.
This means splitdisk-image has no runtime dependency on GRUB, a kernel
build system, or any external initramfs tooling on the enrollment machine.
A fresh cargo build on any Linux x86-64 host with network access and the
Rust stable toolchain produces a functional splitdisk-image binary.
# Produce a base USB image (no share data yet) for testing:
splitdisk-image \
--output /tmp/base.img \
--size 512MiB
# Inspect the produced image:
fdisk -l /tmp/base.imgsplitdisk-create calls splitdisk-image internally and then writes share
data into the system partition of the produced image before flashing to the
target USB device.
splitdisk/
├── AGENTS.md (AI contributor guidelines; normative with README)
├── CLAUDE.md (pointer to AGENTS.md for Claude Code)
├── Cargo.toml (workspace)
├── .cursor/rules/ (Cursor IDE rules, split *.mdc files)
├── crates/
│ ├── splitdisk-create/ (enrollment tool)
│ ├── splitdisk-assemble/ (initramfs agent)
│ ├── splitdisk-image/ (boot image builder; see §10)
│ ├── splitdisk-core/ (shared: crypto, RS, SSS wrappers, metadata format)
│ ├── splitdisk-auth/ (PIN, biometric, Galdralag token integration)
│ └── splitdisk-tui/ (ratatui UI components)
└── docs/
├── SPEC.md (this document)
├── GLOSSARY.md (plain-language term definitions)
├── CRYPTO.md (detailed cryptographic rationale)
└── GALDRALAG.md (Galdralag integration guide)
When --galdralag is passed to splitdisk-create, each member receives only
a Galdralag Baochip-1x token. No separate USB drive is issued. The token
is the complete share — it holds both the encrypted data chunk (on SD card)
and the key share (in RRAM vault).
| Aspect | USB drive mode | Galdralag token mode |
|---|---|---|
| What the member carries | USB drive | Galdralag token only |
| Data chunk storage | USB drive partition | Token SD card |
| Key share storage | Encrypted in USB hidden partition | On-device RRAM vault |
| PIN enforcement | Software attempt counter | Hardware PIN counter with zeroization |
| Biometric | USB iris scanner + software | Optional; token PIN suffices |
| Encryption of key share in transit | HKDF-BLAKE3 + ChaCha20-Poly1305 | BrainpoolP384r1 ECDH on-device |
| Forward secrecy | Not applicable | Authenticated ephemeral ECDH session |
| TRNG | OS CSPRNG | Hardware TRNG on Baochip-1x |
| SD card required | No | Yes (for data chunk storage) |
| Mixed mode with USB shares | N/A | Yes — both types can coexist in one scheme |
- Enrollment machine must be air-gapped and have its memory securely erased
after creating drives.
splitdisk-createcallszeroizeon all key material, but physical RAM remanence is a risk. - Drive firmware attacks: an adversary with physical access to a drive before it reaches its holder could modify the bootloader or initramfs. Mitigations: tamper-evident packaging, and optionally signing the initramfs with Ed25519 (matching the pattern used by Galdralag firmware's boot0 verification).
- Rubber hose or 5$ hammer: a holder can be coerced into providing their PIN. The k-of-n threshold ensures one holder's coercion is insufficient for reconstruction. The holder genuinely does not know how many others exist.
- Share count leakage: the progress bar reveals how many shares have been collected, which after k successes reveals k to an observer watching the screen. This is an accepted trade-off; the total n remains hidden.
- Post-quantum: FrodoKEM-1344 is feature-gated and marked experimental.
Enable only when an independently audited
no_stdRust crate is available, following the same policy as Galdralag firmware and CESS §7.3. - Biometric template privacy: templates are stored only on the individual's own drive, encrypted at rest. No operator or other member can access another member's biometric data.
- Boot image integrity: all bundled blobs in
splitdisk-imageare BLAKE3- verified at build time (§10.6). Implementers SHOULD verify the BLAKE3 hashes of pinned blobs out-of-band before shipping drives.
- Network-based share distribution or retrieval
- Cloud key management
- Windows or macOS host support for
splitdisk-create(Linux only; assembly runs on the embedded Linux in initramfs on any x86-64 machine) - WebAuthn / FIDO2
- VeraCrypt or LUKS integration (SplitDisk manages its own encryption layer)
SplitDisk targets CESS-FULL conformance (CESS v0.2 ). The following
table maps --cipher + --bulk-cipher combinations to their CESS suite_id
values. Suite IDs are used internally and, in Mode A, are transmitted inside
the authenticated outer ChaCha20-Poly1305 envelope only — never in cleartext.
--cipher |
--bulk-cipher |
CESS suite_id |
Notes |
|---|---|---|---|
brainpool384 |
chacha20 |
0x0001 |
CESS-CORE default |
brainpool384 |
serpent |
0x0002 |
|
brainpool384 |
cascade-cs |
0x0003 |
CESS default cascade |
brainpool384 |
twofish |
0x0004 |
|
brainpool384 |
cascade-ct |
0x0005 |
|
brainpool384 |
cascade-ts |
0x0006 |
|
brainpool384 |
cascade-cst |
0x0007 |
Triple cascade |
brainpool512 |
chacha20 |
0x0011 |
|
brainpool512 |
serpent |
0x0012 |
|
brainpool512 |
cascade-cs |
0x0013 |
|
brainpool512 |
twofish |
0x0014 |
|
brainpool512 |
cascade-ct |
0x0015 |
|
brainpool512 |
cascade-ts |
0x0016 |
|
brainpool512 |
cascade-cst |
0x0017 |
Triple cascade |
brainpool256 |
chacha20 |
N/A (Mode B only) | Extension; outside CESS-FULL normative profile |
Note: suite_id values above are provisional pending final assignment in
ALGORITHM-REGISTRY.md. The lookup table in that file is authoritative.
# Core crypto
chacha20poly1305 = "0.10"
serpent = "0.1" # Serpent-256 CTR
twofish = "0.1" # Twofish-256 CTR
poly1305 = "0.8" # MAC for CTR-mode ciphers
hkdf = "0.12"
argon2 = "0.5"
blake3 = "1"
zeroize = { version = "1", features = ["derive"] }
subtle = "2"
# Curves
p384 = "0.13" # BrainpoolP384r1 / BrainpoolP512r1
# Key splitting
vsss-rs = "3" # Shamir, GF(2⁸)
# Data splitting
reed-solomon-erasure = "6"
# Boot image builder (splitdisk-image crate only)
gpt = "3" # GPT partition table construction
fatfs = "0.3" # FAT32 EFI partition construction
cpio = "0.2" # initramfs cpio archive assembly
flate2 = "1" # gzip compression for initramfs
# TUI
ratatui = "0.26"
# USB / block device I/O
nix = "0.29"
rusb = "0.9" # USB device enumeration
# OpenPGP (optional GPG envelope)
sequoia-openpgp = "1"
# Post-quantum (feature-gated, experimental)
pqcrypto-frodo = { version = "0.1", optional = true } # FrodoKEM-1344This specification is a design document. No warranty of correctness is made. Cryptographic choices and security decisions should be reviewed by a qualified cryptographer before implementation or deployment.