Add full-disk encryption (FDE) to a Linux install you already have. LinuxLocker encrypts the root filesystem of an already-installed system in place — without reinstalling, and without a second copy of your data. One tool, any distro family it can chroot into, x86_64 and ARM alike: Fedora, RHEL, Rocky, AlmaLinux, Debian, Ubuntu, Linux Mint, Pop!_OS, Raspberry Pi OS, Arch, Manjaro, EndeavourOS and openSUSE. Converts LUKS1 to a LUKS2 container.
Use AsahiLocker instead — not this tool. Same job, with the Fedora Asahi Remix boot guards LinuxLocker deliberately drops. Apple Silicon boots iBoot → m1n1 → U-Boot → GRUB, and its stub partitions are read by the Apple firmware itself; nothing here understands that chain.
You do not have to remember this.
linuxlocker.shandluks-deploy.shboth detect Fedora Asahi Remix — fromVARIANT_ID, an Asahi kernel, m1n1 boot components, or an Apple Silicon device tree — and exit before touching any device, with a link to AsahiLocker. The check runs against the live environment and against the partition you select, because an Asahi disk can be read from another machine.
luks-deploy.sh converts your existing root (or data) partition into a LUKS2
container holding that same filesystem. Your files and the filesystem UUID
survive; the partition simply gains an encryption layer. It then rewrites every
piece of boot configuration that has to change — crypttab, fstab, GRUB
defaults and their grub.d drop-ins, BLS and systemd-boot entries wherever
the ESP is, /etc/kernel/cmdline and cmdline.d, cmdline.txt,
extlinux.conf, refind_linux.conf, limine.conf, initramfs generator
config, all initramfs images — and refuses to let you reboot until a full
verification gate passes. A root=PARTUUID= or root=/dev/… that names the
raw partition is rewritten to the mapper in every one of those.
/boot stays unencrypted, and that is a design decision, not a gap: a target
whose /boot lives on the root filesystem under GRUB is refused before
anything is touched, and a volume that GRUB already unlocks (an existing
encrypted /boot) is recognised and offered only what GRUB can take — see
Why is /boot left unencrypted?.
The front door, linuxlocker.sh, first identifies the live environment's OS
and package manager, installs any tools the run needs, and only then hands
off to the encrypter — which resolves filesystem-specific tools the moment it
knows what the target actually uses.
This is destructive-by-nature tooling. It rewrites a live root filesystem. Read
docs/INSTALL.mdbefore running anything, and have a verified backup. See Risks.
# 1. On the installed system: get the kit onto a USB drive
git clone https://github.com/doug445/LinuxLocker.git
# 2. Boot ANY live Linux for your machine (the target distro's own live ISO
# is the safest choice) with that USB plugged in.
# 3. From the live environment:
sudo ./LinuxLocker/bin/linuxlocker.sh # detects OS, installs deps,
# runs the encrypter
# 4. Reboot, enter your passphrase, then finish up on the encrypted system:
sudo ./LinuxLocker/bin/post-encryption-setup.shThe deploy script auto-detects your disk layout, target OS, filesystem and
boot machinery, and shows you what it found. You confirm the selection, type
ENCRYPT, and choose a passphrase. Everything after that is automated,
including recovery if a step fails partway.
--dry-run runs the entire read-only half — detection, cross-checks, KDF
benchmark, the exact cryptsetup command it would issue — and exits before
the point of no return.
Behaviour is keyed off what is actually installed on the target, never off the distro's name — so derivatives inherit support from their family.
| Family | Package manager | Initramfs | Boot config handled |
|---|---|---|---|
| Fedora / RHEL / Rocky / Alma | dnf / yum | dracut | GRUB defaults, BLS entries (grubby or direct patch), /etc/kernel/cmdline, rd.luks.* args |
| Debian / Ubuntu / Mint / Pop!_OS | apt | initramfs-tools | crypttab with initramfs flag, update-grub (installs cryptsetup-initramfs in the chroot if missing) |
| Raspberry Pi OS | apt | initramfs-tools | cmdline.txt root= rewrite, auto_initramfs=1, crypttab-driven unlock |
| Arch / Manjaro / EndeavourOS | pacman | mkinitcpio | HOOKS gets encrypt (busybox) or sd-encrypt (systemd), cryptdevice=/rd.luks.* args, grub-mkconfig |
| openSUSE | zypper | dracut | GRUB defaults + grub2-mkconfig, rd.luks.* args |
| ARM boards with U-Boot distro-boot | — | any of the above | extlinux.conf APPEND rewrite |
Alpine, Void and Gentoo are recognised by the dependency installer; their boot stacks are handled to the extent they use the machinery above.
Handled as of 1.2.0, having previously been refused outright.
| Boot stack | What happens |
|---|---|
| systemd-boot, Type #1 entries | Detected from the loader binary on the ESP, not guessed from /boot/loader/entries. Entries are patched; check V13 confirms something bootable points at the encrypted root. Works with the ESP at /efi, /boot/efi or /boot, with or without a separate XBOOTLDR partition. |
Unified Kernel Image (EFI/Linux/*.efi) |
The .efi is rebuilt with the post-encryption kernel command line, using whichever backend the target actually uses: an mkinitcpio preset with _uki=, kernel-install with layout=uki, dracut --uki-file, or ukify. Check V11 reads the rebuilt .cmdline section back and fails the run if the LUKS arguments are not in it. |
| Secure Boot + UKI | The rebuilt .efi is re-signed with sbctl or sbsign. Check V12 fails the run if Secure Boot is enabled and any UKI came out unsigned. |
| Secure Boot, no UKI (shim + GRUB, shim + systemd-boot) | No interaction. The initramfs is not part of the verified chain, and nothing here touches shim, the bootloader binary or vmlinuz. |
Signing has to be explicit, because distro automation does not fire from a
chroot: Arch's zz-sbctl.hook is a libalpm hook, so mkinitcpio -P run by
hand rebuilds the UKI and leaves it unsigned — which a Secure Boot machine then
refuses to load. LinuxLocker signs it itself and verifies the signature before
allowing the reboot.
A UKI target is still refused before the shrink in two cases: no rebuild
backend exists, or Secure Boot is on and no signing backend exists. Both are
overridable with LUKS_ALLOW_UKI=1.
This is verified by 45 fixture assertions in CI across every ESP layout, backend and signer combination — verified end to end on Manjaro w/ systemd-boot/UKI. BOOTLOADERS.md has the full detail, the environment knobs, and the hardware checklist.
Everything gnome-disks can format is classified; a filesystem is supported
in place when it can be shrunk by 32 MiB for the LUKS2 header (or already
has that much slack). Full matrix and workarounds:
docs/FILESYSTEMS.md.
| In place | How |
|---|---|
| ext4 / ext3 / ext2 | resize2fs shrink → encrypt → grow |
| btrfs | online btrfs filesystem resize (subvolume layouts auto-detected) |
| xfs | cannot shrink — accepted only with ≥ 32 MiB pre-existing slack |
| f2fs | resize.f2fs (needs f2fs-tools ≥ 1.16 to shrink) |
| ntfs | ntfsresize (data partitions) |
| vfat | fatresize (data partitions) |
| exfat / udf / swap | not in place — see docs/FILESYSTEMS.md |
- Detects partitions (scored menu, live-USB disk deprioritised), target
OS (
/etc/os-release), filesystem, subvolumes, and the boot partitions — resolved from the target's own fstab, not guessed. - Installs missing tools with the live distro's package manager (dnf, apt, pacman, zypper, apk, xbps, emerge).
- Shrinks the filesystem by 32 MiB (idempotently — a re-run detects the gap and skips).
- Encrypts in place:
cryptsetup reencrypt --encryptwith AES-256-XTS, argon2id, sha512, checksum resilience. - Verifies the LUKS header, opens the container, checks the inner filesystem type and UUID are unchanged, grows the filesystem back.
- Rewrites boot configuration for whatever this target uses, then
rebuilds every initramfs image in a chroot, self-repairing images that
come out without
cryptsetupordm-crypt. - Rebuilds and re-signs the Unified Kernel Image, if the target boots one —
after the kernel command line is final, then signed with
sbctl/sbsignwhen Secure Boot is enabled. - Gates the reboot behind a verification pass over every applicable component — a check that doesn't apply to this target is reported as SKIP, never silently passed.
| The script finds | What happens |
|---|---|
| plain filesystem | normal encryption run |
LUKS2 header with online-reencrypt flag |
a previous run was interrupted → finishes it (--resume-only, with automatic cryptsetup repair after a hard kill), then redoes config |
| complete LUKS2 header | shows a truncated luksDump, then offers: tune (launches luks-tune.sh to raise the KDF), config (redo boot config + verification), or quit |
| LUKS1 header | offers in-place conversion to LUKS2 (cryptsetup convert), then re-costs each keyslot to argon2id using the same three profiles. A volume that GRUB itself unlocks (encrypted /boot) is recognised from the GRUB images on the disk and the volume's own layout: conversion is refused unless that GRUB demonstrably reads LUKS2, and the argon2id re-cost is offered only when it embeds the argon2 module — at 1 GiB, with the unlock estimate multiplied by GRUB's slowdown |
no /etc/fstab inside |
offers data-partition mode: encrypt + recovery key + header backup, no boot config |
Three presets. All argon2id, without exception, without a flag, and without apology. argon2id is the strongest password-hardening function that exists in deployed software: the winner of the Password Hashing Competition, RFC 9106, the LUKS2 default since 2017 — and the only class of function whose cost an attacker cannot buy down with special silicon, because that cost is memory. Every guess against one of these keyslots has to hold 1, 2 or 4 GiB of RAM and sweep it eight to ten times, on a graphics card, on an ASIC, on anything. RAM does not get cheaper for an attacker than it does for you. That is the entire reason argon2id exists, and it is the entire reason this tool writes nothing else.
There is deliberately no pbkdf2 profile, and there never will be: pbkdf2
is a for loop from the year 2000 that a single mining ASIC runs a quarter
of a billion times a second (why, at length).
The fast profile is a hard floor — the tool refuses to write anything
cheaper, with no override flag. LinuxLocker hardens; it never weakens. It
does not write a KDF below the floor, and luks-tune.sh does not re-cost a
keyslot to anything cheaper than the keyslot already has. If you genuinely
want a weaker keyslot, that is a manual cryptsetup luksConvertKey you run
yourself, outside this tool — no script here will do it for you.
| Profile | Memory | Iterations | Unlock time* | argon2id CPU-seconds |
|---|---|---|---|---|
| aggressive | 4 GiB | 10 | ~16 s | 35.3 s (measured, 56 boots) |
| moderate (default) | 2 GiB | 8 | ~8 s | ~14.6 s (extrapolated) |
| fast | 1 GiB | 9 | ~4 s | ~9.4 s (extrapolated) |
* Reference machine: ASUS ZenBook UX534FTC — Intel Core i7-10510U
(4 cores / 8 threads, 1.80 GHz base, 4.90 GHz turbo), 16 GiB RAM, NVMe,
powersave governor, Linux 6.18 LTS, cryptsetup 2.8.7. AES-XTS throughput on
this box is 2188 MiB/s encrypt / 2267 MiB/s decrypt at a 512-bit key, so the
cipher is never the bottleneck — the KDF is the whole of the wait.
How these were measured. The aggressive row is not an estimate: that
machine runs aggressive in production, and systemd records the cost of every
unlock. Across 56 real boots, the journal's per-unit resource accounting for
systemd-cryptsetup@…service reports a mean of 35.73 CPU seconds (median
35.34, min 34.65, max 39.05) at a 4 GiB memory peak:
journalctl -g 'Consumed .* CPU time over' | grep cryptsetup
# ...service: Consumed 35.366s CPU time over 28.681s wall clock time, 4G memory peak.CPU time is the honest signal here. The unit's wall clock time also covers the human typing the passphrase, which is why it ranges from 22 s to over 3 minutes across those same boots and cannot be compared between machines. CPU time is pure argon2id — typing consumes none of it.
The unlock time column is wall clock: how long you actually wait after the
last keystroke. It was measured directly, by formatting loop-backed LUKS2
volumes with each profile's pinned parameters and timing
cryptsetup open --test-passphrase (best of three: 16.4 s / 8.2 s / 4.2 s).
That reconciles with the journal at a CPU-to-wall ratio of ~2.2 — argon2id at
4 GiB is memory-bandwidth bound, so four threads do not buy four times the
speed. The other two rows' CPU-second figures are extrapolated from the measured
aggressive cost, using ratios confirmed independently with the reference
argon2 CLI at the same parameters (32.3 s / 13.2 s / 8.5 s).
For scale: cryptsetup's own auto-tuning on this machine picks argon2id at
727 MiB and 4 iterations to hit its 2000 ms default target. The fast floor
is over three times that much work.
None of this is configuration. The installer benchmarks the machine it is running on and shows live estimates in the menu; these numbers exist so you know what shape of answer to expect, and they will differ on your hardware — roughly linearly with single-thread performance and memory bandwidth.
Two guards run before anything is written:
- The floor: nothing below
fast(1 GiB × 9) is accepted — cryptsetup's own default is argon2id at ~1 GiB tuned to 2000 ms, and this tool exists to beat a bareluksFormat, not to undercut it. - The stock-strength check: the installer measures what
cryptsetupwould have chosen unaided on this machine and, if a named profile falls short (very fast hardware), raises the iteration count 25% past stock. PinnedLUKS_PBKDF_*values below stock are fatal instead — pinning exists for fleet reproducibility, so the tool never silently edits pinned numbers.
The aggressive profile needs ~6 GiB of free RAM at every unlock — the
installer warns on small-memory machines (Raspberry Pi class) and refuses any
profile whose memory cost exceeds the machine's RAM outright.
Pick aggressive unless you have a reason not to. Here is the reason to.
You pay the KDF once per boot. The attacker pays it once per guess. An
unlock is not a screen unlock or a wake from sleep — it is the passphrase
prompt at power-on, and nothing else. On the reference ZenBook aggressive
costs ~16 s there; boot once a day and that is about an hour and a half a
year, and the disk runs at full AES-XTS speed (2.2 GB/s on that machine) for
every second in between, because argon2id never runs again until the next
boot. An attacker with an image of your disk pays those same seconds — on
their hardware, at their scale — for every single guess, and against a 40-bit
human-chosen passphrase they need about a trillion of them. That asymmetry is
the whole product, and aggressive is where it is steepest: 14× the work of
a stock luksFormat on that machine (727 MiB × 4, tuned to two seconds),
2.5× moderate, 4.4× fast. Against a thousand-GPU fleet that is the
difference between roughly 28 years and, for stock, about two.
4 GiB is the ceiling, and aggressive sits on it. Memory is the only thing
that makes an attacker's silicon expensive — a 24 GB GPU fits about six
concurrent 4 GiB guesses, twelve at 2 GiB, twenty-four at 1 GiB, thousands
against a memory-free loop like BitLocker's or FileVault's. cryptsetup
refuses argon2id memory above 4 GiB, so no setting anywhere makes a guess
dearer in memory than aggressive already does. Every profile below it hands
the attacker back some of that parallelism for a few seconds of your boot.
paranoid is the last 20 %. Past the memory ceiling only time raises the
price, and paranoid (4 GiB × 12) raises it 20 % on every guess, forever, for
about three more seconds per boot on that machine. It is not in the deploy
menu on purpose — it is a decision to make after the fact, with
luks-tune.sh, once you have lived with aggressive and found the wait
invisible. Most people do.
Who this is for. A laptop that leaves the house. A passphrase you chose yourself rather than rolled with dice — the rows where the KDF, not the passphrase, decides the outcome. Anyone who will never come back to re-cost the keyslot: the KDF you ship is the KDF the thief meets, and it costs nothing to make it the strongest one available. Above six diceware words every tier is past cosmic time and the choice stops mattering; below that it is the largest single security factor you control with one menu keystroke. The one machine that should not run it is one with less than ~6 GiB free at unlock — the installer says so before it lets you pick.
Already encrypted? sudo ./bin/luks-tune.sh (or linuxlocker.sh tune) is an
ncurses UI that re-costs existing keyslots — shows the measured unlock time
and what each cost buys against a GPU fleet, backs the header up first, and
never touches data, passphrases, or keyslot existence. It also converts
leftover pbkdf2 keyslots to argon2id.
On a volume that GRUB itself unlocks (an encrypted /boot you set up
elsewhere), luks-tune.sh recognises that and still gives it the strongest
KDF that GRUB can open: argon2id — memory-hard, GPU-resistant — whenever the
GRUB image embeds the argon2 module (stock GRUB 2.12 does not; a 2.14 build
does), at up to 1 GiB, which is what argon2id can take as one contiguous
allocation from the x86 UEFI firmware heap. The unlock estimate it shows is
honest about where that unlock runs: GRUB is single-threaded, so the figure is
the kernel-side estimate times 8.5 (measured on one machine;
LUKS_GRUB_KDF_FACTOR overrides it). Nothing here sets an encrypted /boot up
or re-embeds a GRUB image.
| Path | What it is |
|---|---|
bin/linuxlocker.sh |
Start here. Detects the live OS + package manager, installs core dependencies, dispatches to the other scripts (deploy / tune / post / bundle). |
bin/luks-deploy.sh |
The main event. In-place LUKS2 encryption, run from a live USB. Auto-detects everything, resolves boot partitions from the target's fstab, self-repairs failed initramfs steps, fixes SELinux labels, and gates the reboot behind verification. Fully resumable. |
bin/lib-deps.sh |
Shared OS / package-manager detection, dependency installer, and the Fedora Asahi Remix guard (sourced, not run). |
bin/lib-uki.sh |
UKI / systemd-boot / Secure Boot support (sourced, not run): finds EFI/Linux/*.efi across every ESP layout, picks the rebuild backend and the signer, rebuilds and signs, and reads the .cmdline and .initrd sections back out of a PE binary. UKI_DRY=1 prints the commands instead of running them. |
bin/lib-boot.sh |
Kernel command-line carriers and encrypted-/boot recognition (sourced, not run). One transform for every file format that carries a command line — BLS/systemd-boot entries in any ESP layout, extlinux.conf, cmdline.txt, /etc/kernel/cmdline, cmdline.d, refind_linux.conf, limine.conf, GRUB defaults and grub.d drop-ins — rewriting root=/resume= to the mapper, adding the unlock arguments, stripping and restoring the splash, without touching a line it does not understand. Also reads the GRUB images on a disk to tell whether GRUB itself unlocks a volume and whether it can read LUKS2 / open argon2id. |
bin/luks-tune.sh |
ncurses KDF re-costing for existing LUKS2 volumes. Pins --hash sha512 so a re-cost cannot walk a slot's AF hash back to cryptsetup's sha256 default. --dry-run prints the command and changes nothing. |
bin/post-encryption-setup.sh |
Run once on the newly-encrypted system: recovery bundle, snapper subvolumes (btrfs roots), splash-argument restore, verification. Idempotent. |
bin/save-luks-recovery-bundle.sh |
Labeled recovery bundle: a fresh, verified header backup of every LUKS volume on the machine (not just root), the public luksDump of each, the partition table of every disk holding one (sfdisk --dump, so a header backup is never a puzzle about offsets), crypttab/fstab/every command-line carrier/boot entries/EFI boot variables, sha256 sums, and a README with the repair steps for this machine's initramfs style and the checks to make before any header is restored. Refreshes a stale /boot emergency copy (keeping the old one). Key files named in crypttab are listed, never copied. --dry-run writes nothing. Key material — never attach it to a bug report. |
bin/linuxlocker-diag.sh |
Read-only diagnostic bundle for bug reports, as Markdown you can paste straight into an issue: tool inventory, lsblk -f, Secure Boot state, the target's boot configuration, the baked-in .cmdline of every UKI and its signature status, what LinuxLocker's own detection thinks it found, public LUKS header metadata, and the relevant journal lines. UUIDs truncated by default. Also reachable as linuxlocker.sh diag. |
extras/ |
Optional: luks-fetch-cache, a disk-encryption status readout for fastfetch (LUKS and BitLocker volumes — KDF, cipher, protectors; public header metadata only). Its install.sh also installs fastfetch itself if missing. |
tests/loopback-core-test.sh |
CI-safe loopback test of the whole core: shrink guards, reencrypt, resume, hard-kill repair, recovery keys, the ext4 path, and LUKS1→LUKS2 conversion. Touches no real disk, and runs on every push against x86_64 and aarch64 runners. |
tests/uki-fixture-test.sh |
CI-safe fixture test of the UKI / systemd-boot / Secure Boot logic: every ESP layout, every rebuild backend, every signer, the refusal matrix, .cmdline round-trips through a real PE binary, and the Asahi guard. Needs no root and no disk. Run it as root on a UKI machine and section 10 also self-checks detection against that real system. |
tests/cmdline-fixture-test.sh |
CI-safe fixture test of lib-boot.sh: every command-line carrier format round-trips, root=PARTUUID=/root=/dev/… rewriting, idempotency, grub.d overrides, CRLF and comment tolerance, and the encrypted-/boot recogniser against stock-2.12 and argon2-2.14 image signatures. Needs no root and no disk. |
docs/ |
ABOUT · INSTALL · FILESYSTEMS · RECOVERY |
SECURITY.md |
What is in scope, what is not, and what never to attach to a bug report — the artefacts this tool produces can be the keys themselves. |
.github/rulesets/ |
Branch and tag protection as JSON, not as settings someone clicked once: main and every v* tag are protected from deletion and force-push. |
- Cipher:
aes-xts-plain64, 512-bit key (AES-256-XTS) — the modern default for disk encryption, hardware-accelerated on both x86_64 (AES-NI) and aarch64 (ARMv8 Crypto Extensions). - KDF: argon2id, pinned per profile (
--pbkdf-force-iterations, no time-benchmark drift between fleet machines). - Hash: sha512 (AF splitter + LUKS2 digest).
- Encryption sector: the filesystem's block size — 4096 bytes for
ext4, btrfs, xfs and f2fs (512 for ntfs and vfat). cryptsetup's default of
512 made every 4096-byte filesystem block eight XTS blocks with eight IVs.
cryptsetup refuses 4096-byte sectors on a partition whose size is not a
multiple of 4096 — and on a 512-byte-sector GPT disk the last partition never
is, because GPT reserves 33 sectors at the end of the disk; that is every
installer-made root partition. The script then asks: type
ALIGNto move the partition's end down by those few bytes (the table is backed up first; type, name, GUID and attributes kept; the filesystem, already 32 MiB smaller, loses nothing), or press Enter for 512-byte sectors.LUKS_ALIGN_PARTITION=yes|noanswers it non-interactively;LUKS_SECTOR_SIZE=512pins the old value. Volumes made by earlier releases keep their 512-byte sectors — as secure, just slower;luks-tune.shshows which you have. - Resilience:
checksum— the in-place re-encryption is journaled, so power loss mid-run is recoverable by re-running the script.
Most machines LinuxLocker runs on share a disk with Windows or macOS, so the comparison is not academic: the same passphrase habits, the same drive, three key-derivation designs. They differ in kind, and it pays to be exact about which is stronger at what.
| BitLocker (Windows) | FileVault (macOS) | LinuxLocker (LUKS2) | |
|---|---|---|---|
| Key derivation | None, by default. TPM-only mode releases the volume key when the boot measurements match — no passphrase is ever stretched. With a PIN, the TPM's anti-hammering carries it. Password mode (no TPM) and the recovery path stretch with a chained SHA-256 (2^20 rounds, as the tools that open BitLocker volumes on Linux implement it) — a loop with no memory cost | PBKDF2 with SHA-256 — a loop, the year-2000 design, no memory cost; 41,000 iterations documented for the CoreStorage era (Choudary, Grobert and Metz, Infiltrate the Vault, 2012), no figure published since | argon2id (RFC 9106, the Password Hashing Competition winner): 1–4 GiB of memory per guess, 8–10 passes, sha512 throughout — the profiles above, benchmarked on the machine being encrypted |
| Where the secret is stretched | Inside the TPM (measurement, PIN throttling) — or nowhere | Inside the Secure Enclave on T2 and Apple Silicon Macs, entangled with the chip's UID and throttled there; on the CPU, offline-attackable, on Intel Macs without a T2 | In the initramfs, on the CPU, from the disk alone |
| An attacker with the disk alone | TPM modes: cannot start without the TPM. Password mode: the SHA-256 chain is all that stands in the way, on hardware of the attacker's choosing | T2 / Apple Silicon: cannot start without that Secure Enclave. Pre-T2 Intel: PBKDF2 alone, offline | Can start at once: the LUKS header holds everything, and each guess costs argon2id's full memory-bound work — the only defence, and a real one |
| An attacker with the machine | The famous case: the key crosses a bus. Discrete TPMs have had their key sniffed off the SPI/LPC bus in minutes; TPM-only mode then hands over the disk with no passphrase ever typed | Secure Enclave throttling; behind it a PBKDF2 a GPU would eat in days if it ever ran offline | Exactly the disk-alone case: the machine adds nothing and takes nothing away |
| What carries a weak passphrase | Hardware — or, in TPM-only mode, there is no passphrase to carry | Hardware, on the Macs that have it | Nothing. LinuxLocker enrols no TPM by design (FAQ), so the KDF's cost per guess and your passphrase are the whole wall |
| What carries a strong passphrase | Both, where a passphrase exists at all | Both | The passphrase — past six diceware words every column of luks-tune.sh's strength readout reads past the age of the universe; the KDF decides the short-passphrase rows, not those |
| If the hardware promise fails | A sniffed bus, a firmware measurement that matches when it should not, a vendor key: back to a memory-free hash, or to nothing | A Secure Enclave flaw or a vendor order: back to PBKDF2, offline | Nothing to fall back from — the design never rested on hardware, and it is the same wall on every machine, today and after this one is replaced |
Said plainly: as a key-derivation function, LinuxLocker's argon2id exceeds
BitLocker's SHA-256 chain and FileVault's PBKDF2 by orders of magnitude, for
one reason — memory. Both of those are loops that ask a graphics card for
nothing but arithmetic; argon2id at 4 GiB lets a 24 GB card run about six
guesses at once. Against a 40-bit human-chosen password and a thousand-GPU
fleet, that is the difference between roughly 28 years and ten days;
against six diceware words both are past cosmic time and the passphrase is
what saves you (the model and the full table are in
AsahiLocker's README,
same tool family, same numbers; luks-tune.sh prints your own volume's
figures with the same cosmic-era anchors). Microsoft and Apple know their
functions are weak and do not rely on them: they rely on a TPM or a Secure
Enclave to keep the weak function out of an attacker's reach. That is a
legitimate design with a single point of trust that is not yours, and one
that has failed in public more than once. LinuxLocker takes the other road:
make every guess genuinely expensive on any silicon, and let the passphrase do
the rest — which is why the passphrase advice here is not decoration. Under
Linux it is the TPM you chose not to trust.
Strip the acronym off and this is what PBKDF2 is:
u = HMAC(password, salt)
for i in 2..N:
u = HMAC(password, u) # keep a running XOR; that is the key
That is the whole function. One hash, fed back into itself N times, with a few hundred bytes of state — a working set that fits in a register file, which is to say a working set designed, by accident of its era, for the attacker's convenience. It was standardized in September 2000 (PKCS #5 v2.0, RFC 2898), when the fastest graphics card you could buy had four pixel pipelines and no programmable shaders, and it has not had an idea since. Its only knob is N, and N buys you linear time on a machine that has nothing but time: a modern GPU holds sixteen thousand cores, each of which runs this loop on its own password with no need to talk to any other, and a SHA-256 mining ASIC runs the same loop in silicon, a quarter of a billion complete guesses a second, from one wall socket. Raising N by ten makes the attacker wait ten times longer, and makes you wait ten times longer, and the attacker has ten thousand times more cores than you do and bought them by the container. That is the entire negotiating position of a memory-free KDF: a function whose only defence is to ask the attacker to please spend a little more of the one resource the attacker has in unlimited supply. It was lost before it started, and it has been lost more decisively every year since — each generation of silicon has been a ruling against it, and no appeal was ever filed.
Be precise about the charge: pbkdf2 is not broken. It is obsolete, which is worse. A broken function gets a CVE, a headline, a patch and a deadline; it is gone from production within a year because something forced it out. An obsolete function does exactly what it promised in 2000, passes every test written for it, throws no error and files no bug — and so it stays. Nothing forces it out. It is simply outrun: the hardware it was designed against no longer exists, the hardware that exists was built to eat it, and the function keeps reporting success while the wall it was supposed to be sinks below the attacker's knees. Broken gets fixed. Obsolete gets shipped, for another twenty-five years, by people who can point to the standard. That is why LinuxLocker treats a keyslot still on pbkdf2 as a defect to be converted, not a working configuration to be respected.
Password hashing moved on, and it moved on because of pbkdf2. The Password Hashing Competition ran from 2013 to 2015 for one reason: the field had watched a decade of GPU cracking and understood that the only cost a parallel machine cannot parallelize away is memory. A guess that must occupy 4 GiB and sweep it ten times is a guess that needs 4 GiB of DRAM for as long as it runs — not a core, not a shader, not a hash unit, but capacity and bandwidth, sold by the gigabyte at the same price to everyone. argon2 won that competition outright. Its id variant, argon2id, closed the side-channel and time-memory-tradeoff arguments that were left, became RFC 9106, and LUKS2 made it the default in cryptsetup 2.0 in 2017. Every Linux distribution's installer has been writing argon2 keyslots for the better part of a decade. It is not exotic. It is not new. It is not a hardening option for enthusiasts. It is the floor of what a disk encryptor does now, and everything below that floor is a decision to lose.
Apple's FileVault stretches your login password with PBKDF2-SHA256 — the loop above, verbatim. The iteration count was last seen in public in 2012, when three researchers had to reverse-engineer it to find out (41,000 rounds, in Infiltrate the Vault); Apple has not published a number since, does not document the function at all, and has never, in fourteen years of security white papers, written the word argon2. On an Intel Mac without a T2, that loop is the entire wall between your data and a graphics card, and a graphics card walks through 41,000 rounds of SHA-256 two hundred thousand times a second. On newer Macs the Secure Enclave takes over, which is Apple's tacit admission of the point: the function is too weak to be left anywhere an attacker can reach it, so it is locked inside a chip, throttled by that chip, and you are asked to trust the chip. The company that re-engineered the CPU rather than accept Intel's roadmap could not be moved to replace a hash from the year 2000. It chose a chaperone instead.
Microsoft's BitLocker does not even reach PBKDF2. Its password and recovery-key paths stretch with a home-grown chained SHA-256, 2^20 rounds — a loop of the one primitive on Earth with a zettahash per second of purpose-built silicon already switched on, chosen by the company that employs more cryptographers than most universities. And that is the good mode. The default mode stretches nothing at all: TPM-only BitLocker releases the volume key when the firmware measurements match, and no passphrase is ever typed, so there is no secret to derive a key from and no function, weak or strong, in the path. The key then crosses a bus. Discrete TPMs have had it sniffed off the SPI lines with a logic analyser in under a minute, on camera, more than once, by people who bought the laptop that morning. Microsoft's answer to "what if the attacker has a GPU" was to move the problem into a chip and hope the wires hold. They did not hold. The answer to that was more chips.
Both companies own more silicon than most governments. Both employ people
who know exactly what argon2 is; some of them reviewed it. Both ship a
for loop from the year 2000 and put a hardware chaperone in front of it,
so that the loop is never caught outside alone — and both, when the
chaperone has failed in public, have patched the chaperone and kept the
loop. That is not a security architecture. It is a habit with a chip
attached. Linux ships argon2id and lets it stand in the open, on every
machine, with nothing in front of it, because a function that charges 4 GiB
a guess does not need anyone standing in front of it. The difference is
not a matter of taste or of threat model. One of these is the state of the
art, and the other is the state of 2000 with a security guard.
The scorecard, since both companies like to publish one:
| Apple, FileVault | Microsoft, BitLocker | A Linux laptop | |
|---|---|---|---|
| Function between your password and your disk | PBKDF2-SHA256, standardized 2000 | a chained SHA-256 designed for Vista in 2006 — or, by default, nothing | argon2id, RFC 9106 |
| Parameters published | once, in 2012, by three outsiders who reverse-engineered it | in a white paper from the Vista era | in the header of every volume: cryptsetup luksDump prints them |
| Memory per guess | a few hundred bytes | a few hundred bytes | 1, 2 or 4 GiB |
| A typical password, one RTX 4090 × 1,000 | 42 minutes | 15 hours | decades |
| The same, one 3.6 kW Bitcoin miner | minutes, on silicon built for its primitive | 35 minutes | cannot run: the chip has no memory |
| Six diceware words, every Bitcoin ASIC on Earth | 72 days | 2.5 years | past the age of the universe |
| What stops the attacker in practice | a Secure Enclave, on the Macs that have one | a TPM, whose key has been read off the wires on camera | the function |
| Major OS releases shipped since argon2 won, each launched as the most secure yet | eleven | two | — |
| Cost of the fix | a library call; libsodium has shipped argon2id since 2016 | the same | done in 2017 |
| Market value | trillions | trillions | free |
Two of the largest engineering organizations in history, with the literature on their desks and the function in the libraries they already link, have each shipped a decade of "most secure ever" releases around a hash that a hobbyist's mining rig chews through before the kettle boils — and each has answered every public demonstration by reinforcing the box the hash hides in, never the hash. It is not that they cannot do better. A volunteer with a laptop did better in 2017, and it has been the default on that laptop ever since. It is that nobody at either company has been made to.
An epitaph, in advance. There is a place where old cryptography goes, and it is not a museum. It is a furnace, and it is fed by graphics cards. DES went in when a $250,000 machine read a key in 56 hours; MD5 went in when a laptop could forge a certificate over lunch; SHA-1 went in when two PDFs collided on a rented cluster. Each was retired with a paper, a date and a deadline, and nobody argues for them now. PBKDF2 is already in the furnace. It has been in there since the first time a GPU ran it, which was before the iPhone existed, and the two functions built on it or beside it — FileVault's PBKDF2 and BitLocker's home-brewed SHA-256 chain — have been burning alongside it for the whole of the 2010s and the whole of the 2020s, kept alive in production by nothing but the fact that a fire does not file a bug report. Every ASIC that comes off a Bitmain line is another shovel of coal. Every RTX launch is another. The two richest companies in the history of commerce watch the temperature, and their response has been to build a box around the flames and call the box the product. FileVault and BitLocker do not need to be broken. They need to be retired, with the paper and the date and the deadline that DES and MD5 and SHA-1 were given, and they need to stay retired, and the people who kept them in service through ten "most secure ever" releases apiece owe the world the sentence that every other obsolete primitive eventually got: we knew, and we should have replaced it, and we are sorry it took this long. Until that sentence is written, let the record be plain about where they sit. They sit in the furnace, and argon2id — a function that costs 4 GiB a guess, that no chip has ever discounted, that a Linux laptop has run in the open since 2017 — stands outside it, on every disk this tool has ever touched, and is not going in.
Cosmic time. luks-tune.sh states the cost of every keyslot it writes
as the years a thousand 24 GiB GPUs would need to search half the passphrase
space, anchored to what the universe is doing by then. Run the same model
over the three functions — the GPU rates for the two loops are a
published RTX 4090 hashcat run
(BitLocker: 10,025 guesses/s per card; PBKDF2-SHA256: 8.87 billion
iterations/s per card, so ~216,000 guesses/s at 41,000 rounds); argon2id is
LinuxLocker's aggressive profile at the reference machine's 16 s per guess,
six guesses per card because 24 GiB holds six 4 GiB working sets:
| Passphrase | BitLocker SHA-256 chain | FileVault PBKDF2 | LinuxLocker argon2id (4 GiB) |
|---|---|---|---|
| a typical human password (~40 bits) | 15 hours | 42 minutes | decades |
| 6 diceware words (77 bits) | 10^8 years — less than the age of the universe | 10^7 years — less than the age of the universe | 10^13 years — long past the age of the universe |
| 8 diceware words (103 bits) | 10^16 years — every star has burned out | 10^15 years — every star has burned out | 10^21 years — galaxies have evaporated |
Read the top row. That is the password most people actually have, and
against the two loops it is a lunch break. The passphrase rows are where the
loops look respectable — until you notice that on those rows argon2id is a
million times further out, and that the universe is 1.4 × 10^10 years old:
six good words behind a for loop still fall inside its lifetime; behind
argon2id they do not. The KDF sets the price of one guess; the passphrase
sets how many guesses. Microsoft and Apple chose to keep the price low and
guard the till with a chip. LinuxLocker charges 4 GiB at the door, on any
silicon, and the chip is not invited.
And against an ASIC farm. A graphics card is a general-purpose machine being polite. The real adversary of a memory-free hash is the ASIC: a chip that does one hash and nothing else, and the hash it does, by the exahash, is SHA-256 — the exact primitive inside BitLocker's chain and FileVault's PBKDF2. This is not hypothetical hardware. It is the most mass-produced special-purpose silicon on the planet, and there is roughly a zettahash per second of it running right now: the Bitcoin network sat at about 1,000 EH/s of SHA-256d through 2026, which is two thousand billion billion SHA-256 compressions every second, all day, for a reward that happens to be a coin. One Antminer S21 XP does 270 TH/s from a 3,645 W wall socket; on BitLocker's 2^20-round chain that is about 260 million guesses a second, or twenty-five thousand RTX 4090s in a box the size of a shoebox. Point the network at the loops and the table above collapses:
| Passphrase | BitLocker chain, all of Bitcoin's ASICs | FileVault PBKDF2, the same | argon2id 4 GiB, a thousand memory-bound ASICs* |
|---|---|---|---|
| a typical human password (~40 bits) | 0.6 milliseconds | 45 microseconds | two months |
| 6 diceware words (77 bits) | 2.5 years | 72 days | 10^10 years — long past the age of the universe |
| 8 diceware words (103 bits) | 10^8 years — less than the age of the universe | 10^7 years — less than the age of the universe | 10^18 years — galaxies have evaporated |
* There is no argon2id ASIC to buy, and the reason is the point. argon2id at 4 GiB and ten passes moves about 80 GiB through memory per guess, so a chip built for nothing else is bounded by DRAM bandwidth and DRAM capacity, and those cost the attacker exactly what they cost you. The column assumes a thousand parts each with 8 TB/s of HBM — twice the best accelerator on sale — and 400 GiB resident to keep a hundred guesses in flight, so a hundred guesses a second each. That is a generous upper bound on a machine nobody has built, and six words are still past the age of the universe behind it.
Six diceware words against Microsoft's function, on hardware that exists and is switched on today, is a project of two and a half years. Against Apple's, ten weeks. Nobody will redirect Bitcoin at your laptop; but the number says what the function is worth on its own, which is why neither company lets it stand on its own. argon2id's answer to the ASIC is not a bigger N. It is a bill for 4 GiB of RAM per guess that no foundry can discount — the only currency a hash can charge that special silicon does not devalue.
LUKS_PROFILE=aggressive|moderate|fast pick a KDF preset, skip the menu
LUKS_PBKDF_MEMORY / _ITER / _PARALLEL pin exact KDF numbers (floor-checked)
LUKS_TARGET_ROOT / _BOOT / _EFI pin partitions, skip the menus
LUKS_PASSPHRASE_FILE=<path> non-interactive passphrase
LUKS_RECOVERY_KEY=yes|no recovery keyslot without prompting
LUKS_MAPPER_NAME=<name> device-mapper name (default root_crypt)
LUKS_KEEP_SPLASH=1 don't strip rhgb/quiet/splash
LUKS_SECTOR_SIZE=512|1024|2048|4096 encryption sector (default: the fs block size)
LUKS_ALIGN_PARTITION=yes|no move the partition end to a 4096-byte multiple, or keep 512-byte sectors
LUKS_DRY_RUN=1 (or --dry-run) plan only, change nothing
LUKS_SKIP_VERSION_CHECK=1 bypass the cryptsetup >= 2.4 floor
LUKS_ALLOW_UKI=1 proceed past the two UKI refusals
LUKS_UKI_REGEN=<backend> force the UKI rebuild backend:
mkinitcpio|kernel-install|dracut|ukify
LUKS_UKI_SIGN=<backend> force the signer: sbctl|sbsign|none
LUKS_SB_KEY / LUKS_SB_CERT explicit sbsign key + certificate
LUKS_SKIP_UKI_SIGN=1 rebuild the UKI but do not sign it
LUKS_SB_STATE=enabled|disabled override Secure Boot autodetection
LUKS_GRUB_KDF_FACTOR=<x> unlock-time multiplier for a volume GRUB
itself unlocks (default 8.5, measured)
LUKS_GRUB_ARGON2_MAX_KIB=<n> argon2id ceiling for such a volume
(default 1 GiB — the x86 UEFI heap)
LUKS_CONFIRM=ENCRYPT|CONFIGURE|CONVERT the typed point-of-no-return gate of the
mode the run reaches; a mismatch is fatal
LUKS_STALE_MAPPER=keep|close a mapper left open by an earlier run
LUKS_LIVE_OVERRIDE=LIVE run from a root that is not a live environment
LUKS_BATTERY_OVERRIDE=BATTERY run on battery below 50%
LUKS_UNMOUNT=yes|no unmount a target a live desktop mounted
LUKS_RESUME=yes|no finish an interrupted encryption
LUKS_EXISTING=tune|config|quit what to do with a finished LUKS2 volume
LUKS_PROFILE=...|skip also answers the re-costing menu after a
LUKS1 conversion ('skip' defers it —
pbkdf2 is never an option)
LUKS_DATA_PARTITION=yes|no encrypt a volume with no fstab as data
LUKS_MISMATCH_OVERRIDE=MISMATCH keep a pinned /boot or EFI the target's
fstab disagrees with
LUKS_CROSS_DISK=yes|no proceed with /boot or EFI on another disk
LUKS_SUBVOL_MISMATCH=yes|no proceed when BLS and fstab disagree on subvol
LUKS_FSCK=yes|no run the read-only integrity check first
LUKS_FSCK_FORCE=FORCE continue past filesystem errors
LUKS_ALREADY_SHRUNK=yes|no f2fs/vfat: shrunk by an interrupted run?
Without a terminal, a prompt that has no pin stops the run and names the variable it wanted; nothing hangs, and nothing is answered by default. The one prompt with no pin at all is the inner-UUID-changed override: that state means the wrong device is open, and no fleet should answer it blind.
The UKI and Secure Boot knobs are documented in full, with detection order and key-discovery paths, in BOOTLOADERS.md.
- A live/rescue Linux environment for the target machine (the target distro's own live ISO is the safest bet). The script hard-refuses to run from the installed system without a typed override.
cryptsetup≥ 2.4 in the live environment (in-place reencryption).- On a UKI target:
binutils(forobjcopy) in the live environment, so the.cmdlineand.initrdsections can be read back and verified. Without it those checks report SKIP rather than passing silently. - AC power for laptops — the script checks and warns.
- A verified backup. In-place encryption is irreversible the moment it starts.
The manual route — cryptsetup reencrypt followed by editing crypttab,
fstab, the kernel cmdline and your bootloader's config yourself — works, and
there are guides for it. What this kit adds is the part those guides leave to
you, and the part that differs on every distro:
Manual cryptsetup reencrypt |
LinuxLocker | |
|---|---|---|
| Partition selection | You identify root/boot/EFI yourself | Auto-detected and fstype-checked; boot and ESP resolved from the target's own fstab, and your pick cross-checked against it |
| Filesystem shrink | You pick the right resize tool and the right size | Per-filesystem handler (ext2/3/4, btrfs, xfs, f2fs, ntfs, vfat), 32 MiB gap, idempotent re-run detection |
| Which distro is this? | You already know, and follow the matching guide | Detected from the target's own artefacts — derivatives inherit support without being named |
| KDF parameters | cryptsetup auto-benchmarks — machine-dependent, and picks sha256 |
Pinned argon2id + sha512, identical on every box, chosen from a menu benchmarked on your hardware, with a hard floor |
| Boot config | You edit crypttab, fstab, cmdline, GRUB defaults, BLS entries, cmdline.txt or extlinux.conf by hand |
All of them rewritten, for whichever ones this target actually uses |
| Initramfs | dracut -f / mkinitcpio -P / update-initramfs -u, and hope the module is in |
Every image rebuilt in a chroot, then checked for cryptsetup/dm-crypt and self-repaired if missing |
| UKI / Secure Boot | You discover after the reboot that the .efi still has the old command line baked in, or that rebuilding it broke the signature |
The .efi is rebuilt with the right backend after the cmdline is final, re-signed with sbctl/sbsign, and its .cmdline section read back to prove it |
| Did it work? | You find out at reboot | Verification gate refuses the reboot until it passes; a check that does not apply reports SKIP, never a silent pass |
| Interrupted run | You debug the header state yourself | Detected and resumed automatically; cryptsetup repair path handled |
| Undo / recovery | Whatever you thought to save | Header backups plus a labeled bundle of every changed file, with distro-specific repair commands |
If you want to understand what it changes before trusting it, --dry-run
prints every action, including the exact cryptsetup invocation, and changes
nothing.
- Power loss or a crash mid-encryption leaves a half-encrypted disk. The LUKS2 journal + checksum resilience make this recoverable — re-run the script — but only if you don't panic-format anything first.
- A wrong partition selection is catastrophic. The script cross-checks your
selection against the target's own fstab and makes you type
ENCRYPT, but the final authority is you. - Forgetting the passphrase — or setting it with Caps Lock on, which
cryptsetup's type-it-twice check cannot catch — with no recovery key enrolled means the data is gone. The script guards the second case by requiring a lower-caseyesimmediately before the passphrase prompt: it cannot be typed with Caps Lock on. That is the feature working as designed. - On systems where GRUB itself unlocks the disk (encrypted
/boot,cryptomount), a LUKS2 header its GRUB cannot read or an argon2id keyslot its GRUB cannot open makes the system unbootable. Such volumes are recognised from the GRUB images and refused or capped accordingly; nothing here sets up an encrypted/boot.
Yes — that is the entire point of this tool. cryptsetup reencrypt --encrypt
converts an existing filesystem into a LUKS2 container in place. LinuxLocker
automates that plus every boot-configuration change it forces, and refuses to
let you reboot until the result verifies.
Yes. The partition gains an encryption layer; the filesystem inside it is the same filesystem, with the same UUID. btrfs subvolumes and snapshots come through untouched. The script re-reads the inner filesystem type and UUID after encryption and fails the run if either changed.
Support is keyed off what is actually installed on the target, never off the distro's name, so derivatives inherit it from their family: Fedora / RHEL / Rocky / AlmaLinux (dnf, dracut, BLS), Debian / Ubuntu / Mint / Pop!_OS (apt, initramfs-tools), Arch / Manjaro / EndeavourOS (pacman, mkinitcpio), openSUSE (zypper, dracut), and Raspberry Pi OS. Alpine, Void and Gentoo are recognized by the dependency installer. See Supported systems.
Yes. Raspberry Pi OS is handled as a first-class target — the root= line in
cmdline.txt is rewritten and auto_initramfs=1 set — and ARM boards using
U-Boot distro-boot get their extlinux.conf APPEND line rewritten. Note that
the aggressive KDF profile wants ~6 GiB of free RAM at every unlock; the
installer warns on small-memory machines and refuses outright any profile whose
memory cost exceeds the machine's RAM.
Because it rewrites the filesystem it would otherwise be running from. The
script hard-refuses to run from the installed system without a typed override.
Any live or rescue Linux for your machine works; the target distro's own live
ISO is the safest choice, since its cryptsetup and filesystem tools already
match the target.
Because pbkdf2 is a simple for loop — hash the passphrase, feed the
result back in, repeat — and a for loop is the one thing a cracking rig
was built to run. It holds no state larger than one hash, so it needs no
memory, so sixteen thousand GPU cores each run their own private copy, and a
SHA-256 mining ASIC runs a quarter of a billion of them a second from one
wall socket. Its only defence is the iteration count, and every iteration
you add to slow the attacker down slows you down by exactly as much, on a
machine with ten thousand times fewer cores. It was standardized in 2000
against hardware that no longer exists; the hardware that does exist was
designed, in industrial quantity, to eat it. It is not broken. It is
obsolete, which is worse, because nothing ever forces an obsolete function
out of production.
argon2id is the answer the field gave to exactly that problem. It is memory-hard: every guess must occupy gigabytes of RAM and sweep them repeatedly, so an attacker has to buy DRAM per concurrent guess, not cores — and DRAM is the one component no ASIC discounts, no GPU multiplies, and no foundry makes cheaper for the attacker than for you. It is the winner of the Password Hashing Competition, it is RFC 9106, it has been LUKS2's default since cryptsetup 2.0, and it is the strongest password-hardening function deployed anywhere. On this tool's profiles a single guess costs 1 to 4 GiB and seconds of wall time on any silicon, and six diceware words behind it sit past the age of the universe.
pbkdf2 is never an option here. LinuxLocker never writes it, never
offers it, and treats a keyslot still on it as a defect awaiting repair, not
a configuration: the skip choice after a LUKS1 conversion only defers the
re-costing, and the script says so, loudly, before it lets you leave. All
three profiles are argon2id, and the cheapest of them is a hard floor with
no override flag — the tool exists to beat a bare luksFormat, not to
undercut it. luks-tune.sh converts leftover pbkdf2 keyslots on volumes you
encrypted earlier. For what the same loop looks like in BitLocker and
FileVault, what those rely on instead, and what it all comes to in cosmic
time against a GPU fleet and against every Bitcoin ASIC on Earth, see
How this compares with BitLocker and FileVault.
Because the root keyslot is where the strength lives. The initramfs unlocks
root with the full argon2id cost — up to 4 GiB of memory per guess, which is
what holds a GPU fleet to a handful of parallel attempts. An encrypted /boot
would have to be opened by GRUB instead, and GRUB cannot deliver that: it takes
argon2id's memory as one contiguous allocation from the firmware heap,
which on x86 UEFI has never reliably meant more than 1 GiB (4 GiB overflows
GRUB outright), and whether it can open argon2id at all depends on the build
(stock GRUB 2.12 prints Argon2 not supported). A /boot keyslot would
therefore be the cheapest way into the machine — a quarter of the memory cost
of an aggressive root — and every unlock would run single-threaded in GRUB,
about 8.5× the initramfs time for the same parameters. Fitting the KDF to the
bootloader trades a strong defence for a partial one; leaving /boot clear
keeps the strong one.
So LinuxLocker recognises encrypted /boot and never sets it up:
- A target whose
/bootlives on the root filesystem under GRUB or extlinux is refused before the shrink, with the reason and the fix (a separate, unencrypted/bootpartition — 1 GiB is plenty). The old behaviour encrypted it and reported success on a machine whose GRUB could no longer find a kernel. - A volume GRUB already unlocks is detected from the GRUB images on the disk
(the embedded early config names the UUID it
cryptomounts) and from the volume's own layout. On it,luks-tune.shand the LUKS1 conversion offer only what that GRUB can take: LUKS2 only if the image reads it, argon2id only if the image embeds the argon2 module, 1 GiB at most, and unlock estimates multiplied by GRUB's slowdown.
Apple Silicon under U-Boot's EFI has been measured allocating 2 GiB; that is AsahiLocker's territory and out of scope here. Details: BOOTLOADERS.md.
LUKS2 re-encryption is journaled with checksum resilience. Re-run the script:
it detects the online-reencrypt flag, runs cryptsetup repair first if the
journal is dirty, finishes the re-encryption, and then redoes the configuration
work. The one way to lose the disk here is to panic-format it before re-running.
Yes, both, as of 1.2.0.
systemd-boot is detected from the loader binary on the ESP rather than
inferred from /boot/loader/entries, so it works with the ESP mounted at
/efi, /boot/efi or /boot, with or without a separate XBOOTLDR partition.
Type #1 entries get the LUKS arguments, and check V13 refuses the reboot unless
something systemd-boot can actually boot points at the encrypted root.
A UKI is rebuilt. The kernel command line and the initramfs are baked into
the .efi, so editing /etc/kernel/cmdline and rebuilding the standalone
initramfs — which is all any generic guide does — changes nothing about what the
firmware loads. LinuxLocker detects which tool owns the .efi on your target
(an mkinitcpio preset with _uki=, kernel-install with layout=uki,
dracut --uki-file, or ukify), rebuilds it after the kernel command line
is final, and then reads the .cmdline section back out to prove the LUKS
arguments are in there. That last check, V11, is the one whose absence made the
old failure silent.
It still refuses before touching the disk if no rebuild backend exists, or if Secure Boot is on and nothing can sign the result. See BOOTLOADERS.md.
Yes, and it handles the part that is easy to get wrong.
Without a UKI — shim + GRUB, or shim + systemd-boot — there is nothing to do:
the initramfs is not part of the verified chain, and LinuxLocker never touches
shim, the bootloader binary or vmlinuz. Rebuilding the initramfs is invisible
to Secure Boot.
With a UKI, the .efi is the signed object, so rebuilding it invalidates the
signature and the firmware will refuse to load the result. LinuxLocker re-signs
it with sbctl or sbsign, then verifies the signature before allowing the
reboot (check V12). This matters more than it sounds: on Arch and Manjaro,
signing normally happens through zz-sbctl.hook, which is a libalpm hook —
it fires on pacman transactions, not when something runs mkinitcpio -P
directly. Any tool that rebuilds your UKI without being a package manager leaves
it unsigned unless it signs it deliberately.
pesign/NSS signing is detected but not implemented: the certificate nickname
and NSS database cannot be discovered reliably from a chroot, and a silently
unsigned .efi is exactly the failure this is meant to prevent. Those targets
are refused with an explanation.
No — not the part of the system this tool builds. The quantum threat is specific, and none of it lands on a LUKS2 volume at rest:
- Shor's algorithm breaks public-key cryptography — RSA, ECDH, ECDSA. LUKS2 contains none: the header holds keyslots, salts and a digest, and the data is symmetric AES. There is no public key to factor, and nothing was ever transmitted, so "harvest now, decrypt later" — which is about recorded TLS key exchanges — has no analogue for a disk in a drawer.
- Grover's algorithm halves symmetric key strength, and that is the whole of its effect on AES. This tool uses AES-256-XTS (a 512-bit XTS key), so the theoretical quantum brute force is 2^128 operations — the same class as brute-forcing AES-128 classically, which is considered infeasible for the age of the universe. NIST rates AES-256 as quantum-safe at its highest category; it is the recommendation for the post-quantum era, not a victim of it.
- The sha512 in the header (anti-forensic splitter, volume-key digest) is a preimage target, not a collision target; Grover leaves it at 2^256.
- The passphrase is the only lever, and argon2id guards it. Grover could in principle square-root a passphrase search — but every one of those guesses is a full argon2id evaluation, 1 to 4 GiB of memory-hard, sequential work, and nobody has a design for running that inside a quantum computer at any useful rate. The memory cost that stops a GPU fleet stops this too. If you want margin anyway, the fix is available today and costs nothing: ten diceware words instead of eight.
What quantum will eventually change, for context: TLS and SSH key exchange, GPG or age files encrypted to RSA/ECC public keys, and the RSA signatures that Secure Boot verifies — including on a UKI this tool re-signs. Those are the firmware and protocol ecosystems' migrations, they are not a way into the data on this disk, and the disk does not need to wait for them.
No. LinuxLocker enrolls a passphrase and, optionally, a recovery keyslot. TPM2 enrollment, PCR sealing and network-bound unlock are features it does not implement — you type the passphrase at boot. Secure Boot signing (above) is a separate thing and is handled. What a TPM buys BitLocker, what it costs, and what stands in for it here: How this compares with BitLocker and FileVault.
No — use AsahiLocker. It does the
same job with the Apple Silicon boot guards LinuxLocker deliberately drops.
Both linuxlocker.sh and luks-deploy.sh detect Fedora Asahi Remix and exit
with a link before touching any device, so running the wrong one by mistake
costs you nothing.
Yes. Every prompt has an LUKS_* environment variable behind it, except the
inner-UUID-changed override, which means the wrong device is open and is
never answered blind — see
Environment knobs. Run
without a terminal, a prompt that has no pin stops the run and names its
variable rather than hanging or dying on a failed read. Pin
LUKS_PBKDF_MEMORY / _ITER / _PARALLEL for reproducible KDF cost across a
fleet rather than per-machine benchmark drift; pinned values below the floor are
fatal rather than silently raised, precisely so the numbers you pinned are the
numbers you get.
Yes. If the selected partition has no /etc/fstab inside it, the script offers
data-partition mode: encrypt, enroll a recovery key, back up the header, and
skip all boot configuration.
Only if it already has at least 32 MiB of free space at the end of the
partition, because xfs cannot be shrunk. The script checks for that slack and
declines rather than guessing. docs/FILESYSTEMS.md has
the full matrix and the workarounds for xfs, exfat, udf and swap.
Yes, without re-encrypting anything. sudo ./bin/luks-tune.sh re-costs existing
keyslots to a stronger argon2id profile, backs the header up first, and never
touches data, passphrases or keyslot existence. A LUKS1 volume is offered an
in-place conversion to LUKS2 first. If GRUB itself unlocks the volume, both
tools say so and stay within GRUB's ceiling (above).
Handled. Every file that carries a kernel command line is found and rewritten:
a root= or resume= that names the raw partition — PARTUUID=,
PARTLABEL=, /dev/nvme0n1p3, /dev/disk/by-id/… — becomes
/dev/mapper/root_crypt, while root=UUID=<filesystem uuid> is left alone
because the filesystem keeps its UUID inside the container. Ubuntu's
/etc/default/grub.d/*.cfg drop-ins, which override /etc/default/grub when
grub-mkconfig runs, get the same edit, and GRUB_FORCE_PARTUUID (cloud
images) is disabled because it boots root=PARTUUID= straight past the mapper.
A UKI target with no /etc/kernel/cmdline gets one seeded from the UKI's own
.cmdline, so the rebuild cannot bake in the live USB's /proc/cmdline. Check
V14 then reads every carrier back. Nothing in this stage aborts the run: a file
that cannot be rewritten is reported and the gate refuses the reboot.
sudo cryptsetup luksDump /dev/<your-root-partition> prints the cipher, the
KDF and its parameters. It shows no key material and is safe to paste into a
bug report — unlike --dump-master-key, which is not. extras/luks-fetch-cache
turns the same information into a one-line-per-volume fastfetch readout.
| Doc | Covers |
|---|---|
| ABOUT.md | What the project is, who it is for, what it deliberately does not do |
| TESTED-SYSTEMS.md | The machines behind the ✅ claims — hardware, layout, what was verified |
| INSTALL.md | Step-by-step install, start to finish, with what each prompt means |
| FILESYSTEMS.md | Every filesystem classified: shrinkable in place, slack-only, or not at all — with workarounds |
| RECOVERY.md | Interrupted encryption, unbootable system, corrupt header, undoing a shrink |
| BOOTLOADERS.md | Which boot machinery is actually written, and where systemd-boot, UKIs and Secure Boot stand |
| SECURITY.md | Scope, reporting, and what never to attach to a bug report |
| CODE_OF_CONDUCT.md | Expected conduct in issues and pull requests |
| CONTRIBUTING.md | What this project accepts, the diagnostic bundle, and what a filesystem handler must do |
This project takes serious bugs and new filesystem support, and nothing else. Refactors, new options and general feature requests will be declined regardless of quality — every added code path here is a path that can lose someone's disk, and there is one maintainer to be sure it does not. CONTRIBUTING.md has the full scope, the read-only diagnostic bundle, and what a filesystem handler has to do to merge.
Test reports are wanted as much as bug reports. Every UKI, systemd-boot and Secure Boot path is verified by fixtures in CI but has not been through a full encrypt-and-reboot cycle on real hardware, and neither have xfs, f2fs, ntfs, the Raspberry Pi and U-Boot boot stacks, or aarch64 hardware. CONTRIBUTING.md has the table of what is still unconfirmed; a report that a configuration worked is the only way a row gets ticked.
Because this tooling rewrites a live root filesystem and its bootloader, a description of what went wrong is rarely enough to act on. Say which distro you ran it from and which distro you ran it against — those are different code paths through the same script — and attach the diagnostic bundle:
sudo ./bin/linuxlocker-diag.sh -o linuxlocker-report.mdIt is read-only, it redacts UUIDs by default, and it deliberately excludes everything that would be key material. Read it before posting anyway.
Before opening a pull request, run what CI runs:
shellcheck -S warning $(git ls-files '*.sh') extras/bin/luks-fetch-cache
sudo bash tests/loopback-core-test.sh
bash tests/uki-fixture-test.sh
bash tests/cmdline-fixture-test.shExpect 31 passed, 0 failed from the loopback suite and 0 failed from the
fixture suites (bash tests/cmdline-fixture-test.sh is the third). The loopback suite exercises the whole encryption core — shrink
guards, in-place re-encryption, --resume-only, a hard kill mid-re-encrypt
followed by cryptsetup repair, recovery keys, the ext4 path and LUKS1 → LUKS2
conversion — against file-backed loop devices. The fixture suite exercises the
UKI, systemd-boot and Secure Boot decision logic against synthetic target trees.
Neither touches a real disk, and both run on every push against x86_64 and
aarch64 runners. A SKIP in loopback stage 5b is normal and lowers the count
without any failure; 0 failed is the invariant.
Every shell script in the repository is shellcheck -S warning clean and must
stay that way — this is a pure shell-script project with no runtime
dependencies beyond the tools a sysadmin already has on a rescue USB, and
that is deliberate: a recovery tool that needs a language runtime installed is a
recovery tool you cannot use from a minimal live environment.
Security issues do not belong in the issue tracker. Read SECURITY.md first: the recovery key, the header backups and the recovery bundle are key material, not diagnostics.
Audited in full on 2026-09-05 by Claude Fable 5.1 (Anthropic) for code
excellence: every script, the test suites and the documentation, with each
finding reproduced before it was fixed. The results shipped as v1.4.0 through
v1.4.3 — five real bugs, the command-line carrier layer, encrypted-/boot
recognition, the harden-only guarantee — and v1.5.0 marks the audited state.
Nothing in this tool has changed hands: the design decisions are the
author's, the audit checked that the code keeps them.
Audited again on 2026-09-21 by Claude Fable 5.1, against v1.6.0: every
script read in full, the three test suites run, and each finding reproduced
before it was reported. Five bugs came out of it and were fixed in the same
pass — the diagnostic script shipped without its execute bit, the fstab
device-path branch could never match a real line, the chroot phase was fed
to bash on stdin where any child that reads it would have swallowed the rest,
the summary claimed a GRUB_ENABLE_CRYPTODISK=y the tool never writes, and
the unattended-use claim was not true until every prompt was given a pin.
The design gaps it named that are still open — an end-to-end boot of the
Fedora, Ubuntu and Arch GRUB paths, a console-keymap check for the initramfs,
sgdisk in the dependency map — are recognized here so that nobody mistakes
the passing suites for a boot.
MIT — see LICENSE.
- Version: 1.6.3
- Author: William MacKinnon (doug445)
- Email: spilled-bowline0j@icloud.com
- Repository: https://github.com/doug445/LinuxLocker
Copyright (c) 2026 William MacKinnon <spilled-bowline0j@icloud.com>