Deferred Disk Encryption
Deferred Disk Encryption
Section titled “Deferred Disk Encryption”Deferred disk encryption allows creating unencrypted disk images that are encrypted on first boot when the user provides credentials. This approach provides better security by avoiding pre-set encryption keys in distributed images.
Overview
Section titled “Overview”Standard Encryption Flow
Section titled “Standard Encryption Flow”graph LR A[Build Image] --> B[Apply LUKS<br/>with default key] B --> C[Distribute Image] C --> D[First Boot] D --> E[Enroll device credentials<br/>and rotate volume key] E --> F[Encrypted System]Issues:
- Image contains LUKS container with default/empty key
- Security risk if image is compromised before first boot
- Replacing a password slot alone does not rotate the shared LUKS volume key
Deferred Encryption Flow
Section titled “Deferred Encryption Flow”graph LR A[Build Image] --> B[Plain LVM<br/>No encryption] B --> C[Distribute Image] C --> D[First Boot] D --> E[User sets password] E --> F[Apply LUKS in-place] F --> G[Enroll TPM/FIDO2] G --> H[Reboot] H --> I[Encrypted System]Benefits:
- ✅ Image is completely plaintext (no default keys)
- Interactive setup lets the user choose a password; automated debug setup uses
ghaf - ✅ Better security posture
- ✅ No key management for image distribution
Architecture
Section titled “Architecture”Initrd Execution
Section titled “Initrd Execution”The encryption service runs in the initial RAM disk (initrd) before the root filesystem is mounted. This design ensures:
- ✅ No mount conflicts: Device is not in use during encryption
- ✅ Early execution: Runs before systemd main system services
- ✅ Clean state: System can reboot after encryption without complex unmounting
- ✅ Security: Service runs in minimal environment
Boot Sequence:
1. Bootloader → Kernel → Initrd2. Initrd systemd starts3. first-boot-encrypt checks LUKS metadata and the installer marker: ├─ Plain LVM with installer marker? → Apply encryption ├─ Installer marker remains? → Recover/resume encryption and finish enrollment ├─ Incomplete encryption without installer marker? → Refuse to boot └─ Setup complete? → Skip setup4. Unlock the completed LUKS device on normal boots5. Mount root filesystem6. Switch to main systemDisk Layout
Section titled “Disk Layout”The layouts below describe the ordinary disko image. The secure A/B canary has
a Type-2-only ESP and two root/verity pairs; use its
testing guide and resolve the actual
partition with cryptsetup status crypted rather than copying /dev/sda3.
Before First Boot (Unencrypted):
/dev/sda1 - boot (1M) - GRUB bootloader/dev/sda2 - ESP (500M) - EFI System Partition/dev/sda3 - lvm (rest) - Plain LVM Physical Volume └─ VG "pool" ├─ LV swap (12G default) - Swap space ├─ LV root_0 (50G default) - Root filesystem (ext4) ├─ LV verity_0 (1G default) - Reserved verity space └─ LV persist (2G initially, grows on boot) - Persistent data (btrfs)After First Boot (Encrypted):
/dev/sda1 - boot (1M) - GRUB bootloader/dev/sda2 - ESP (500M) - EFI System Partition/dev/sda3 - LUKS container "crypted" └─ /dev/mapper/crypted - LVM Physical Volume └─ VG "pool" ├─ LV swap (12G default) - Swap space ├─ LV root_0 (50G default) - Root filesystem (ext4) ├─ LV verity_0 (1G default) - Reserved verity space └─ LV persist (2G initially, grows on boot) - Persistent data (btrfs)First Boot Process
Section titled “First Boot Process”- Early Boot (Initrd): Service runs in initrd before root filesystem is mounted
- Detection: Inspect LUKS reencryption metadata and the ESP installer marker
- User Interaction:
- Debug mode: Automatic encryption with the known development passphrase
ghaf - Release mode: User prompted for password/PIN (min 4 characters)
- Debug mode: Automatic encryption with the known development passphrase
- In-Place Encryption:
cryptsetup reencryptconverts LVM partition to LUKS - Hardware Enrollment:
- TPM2: Automatic unlock with optional PIN
- FIDO2: Unlock with hardware key
- Recovery key: Enrollment is attempted after successful hardware enrollment; verify success
- Cleanup: LVM deactivated, LUKS container closed
- Reboot: System restarts with encrypted disk
- Second Boot: Unlock with an enrolled TPM/FIDO2 token, recovery key or retained password
Configuration
Section titled “Configuration”Basic Setup
Section titled “Basic Setup”{ # Deferred encryption module is automatically imported with disko-debug-partition ghaf.partitioning.disko.enable = true;
# Configure encryption with deferred mode ghaf.storage.encryption = { enable = true; deferred = true; # Apply encryption on first boot, not at build time backendType = "tpm2"; # or "fido2" };}Debug vs Release Mode
Section titled “Debug vs Release Mode”ghaf.storage.encryption.interactiveSetup controls prompting. The profile
defaults below select interactive release setup and automated debug setup.
Debug Mode (Development/Testing):
{ ghaf.profiles.debug.enable = true;
# Behavior: # - Encryption applied automatically with development password "ghaf" # - TPM enrolled without PIN requirement # - No user interaction needed # - Recovery enrollment attempted after hardware enrollment succeeds}Release Mode (Production):
{ ghaf.profiles.debug.enable = false;
# Behavior: # - User prompted for password on first boot # - Password minimum 4 characters # - Interactive setup requests a TPM PIN when TPM enrollment succeeds # - User must enter PIN on each boot (with TPM) # - Recovery key generated and displayed}Backend Options
Section titled “Backend Options”TPM 2.0 (Recommended):
{ ghaf.storage.encryption.backendType = "tpm2";
# Features: # - Automatic unlock if platform unchanged # - Optional PIN for additional security # - Measures PCR 7 (Secure Boot state) # - Recovery key as fallback}FIDO2/U2F:
{ ghaf.storage.encryption.backendType = "fido2";
# Features: # - Requires physical hardware key # - User presence verification # - Client PIN support # - Works with YubiKey, Nitrokey, etc.}Building Images
Section titled “Building Images”Build with Deferred Encryption (Default)
Section titled “Build with Deferred Encryption (Default)”# Build the image - deferred encryption is default in mvp-user-trial targetsnix build .#intel-laptop-debug
# The resulting image will be plain LVM (unencrypted)# File: result/ghaf-image.raw.zst (compressed)# Size depends on the selected configurationBuild with Immediate Encryption (Optional)
Section titled “Build with Immediate Encryption (Optional)”# To build with LUKS at build time instead (not recommended):# Override the configuration to set deferred = false
# Result: LUKS container with empty password# Encrypted content compresses poorlyFlash to Device
Section titled “Flash to Device”# Decompress if neededzstd -d result/ghaf-image.raw.zst -o /var/tmp/ghaf-image.raw
# Flash to the confirmed USB/SSD devicesudo dd if=/var/tmp/ghaf-image.raw of=/dev/sdX bs=4M status=progress conv=fsync
# Or use a more user-friendly toolsudo gnome-disks # Select image file and target deviceFor ordinary laptop images, prefer ghaf-installer -e, which creates the
.ghaf-installer-encrypt marker on the ESP. A raw flash does not add that marker;
confirm it exists before expecting deferred encryption. Secure A/B images
already include the marker and require the signing/install procedure in their
testing guide.
First Boot Experience
Section titled “First Boot Experience”The screens below are illustrative. Hardware enrollment and recovery-key creation can fail; confirm the resulting tokens and retain a working credential.
Debug Mode
Section titled “Debug Mode”Ghaf System Boot================
🔧 Debug mode: Applying encryption automatically...🔄 Preparing system for encryption...📦 Deactivating logical volumes...
🔐 Encrypting partition... This will take several minutes depending on disk size. Please do not power off the system!
[Progress bar or spinning indicator]
✅ Encryption complete!🔓 Opening encrypted device...📦 Activating logical volumes...🔑 Enrolling TPM 2.0 for automatic unlock...✅ TPM enrollment complete!📝 Updating system configuration...
╔════════════════════════════════════════════════════════╗║ Encryption Setup Complete! ║╚════════════════════════════════════════════════════════╝
Debug mode: System will reboot automatically in 5 seconds...🔄 Rebooting system...Release Mode
Section titled “Release Mode”Ghaf System Boot================
╔════════════════════════════════════════════════════════╗║ First Boot - Disk Encryption Setup ║╚════════════════════════════════════════════════════════╝
This system will now apply full disk encryption to protectyour data. This process is irreversible and required forsystem security.
You will be prompted to set a PIN or password.This will be required on every boot to unlock the system.
Requirements: - Minimum 4 characters - Cannot be empty
Enter encryption PIN/password: ****Confirm PIN/password: ****✅ Password entered
🔄 Preparing system for encryption...📦 Deactivating logical volumes...
🔐 Encrypting partition... This will take several minutes depending on disk size. Please do not power off the system!
[Progress bar - 15 minutes for 70GB typical]
✅ Encryption complete!🔓 Opening encrypted device...📦 Activating logical volumes...
🔑 Enrolling TPM 2.0 for automatic unlock...🔑 Adding recovery key...
[Recovery key displayed - save this!]Recovery Key: xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx
✅ TPM enrollment complete!📝 Updating system configuration...
╔════════════════════════════════════════════════════════╗║ Encryption Setup Complete! ║╚════════════════════════════════════════════════════════╝
Your disk is now fully encrypted and protected.
On next boot: • TPM will automatically unlock the disk • You may be prompted for your PIN as additional security
Press Enter to reboot and complete setup...
🔄 Rebooting system...Subsequent Boots
Section titled “Subsequent Boots”After first boot encryption, the system will boot normally with LUKS:
With TPM (No PIN):
Loading kernel...Loading initrd...[TPM unlocks automatically]Starting Ghaf...With TPM + PIN:
Loading kernel...Loading initrd...Please enter PIN for disk /dev/sda3: ****[TPM verifies PIN and unlocks]Starting Ghaf...Without TPM (Password required):
Loading kernel...Loading initrd...Please enter passphrase for disk /dev/sda3: ********[Password verified and unlocks]Starting Ghaf...Troubleshooting
Section titled “Troubleshooting”Encryption Fails
Section titled “Encryption Fails”Symptom: Service fails, system won’t encrypt
Check:
# In the failed boot initrd/emergency shell, inspect service statusjournalctl -b -u first-boot-encrypt --no-pager
# Check if LVM partition existslsblkpvdisplay
# A LUKS header alone does not prove encryption finishedcryptsetup isLuks /dev/sda3 && cryptsetup luksDump /dev/sda3Common causes:
- Partition table mismatch
- Insufficient disk space (32 MiB reserved for the LUKS header)
- Corrupted filesystem
- Setup already completed
Recovery:
# Encryption happens in initrd, so if it fails:# 1. Capture the console and current-boot service status.# 2. Keep the installer marker and reboot with the existing credential.# 3. Setup repairs/resumes unfinished encryption before enrollment.# Never run a fresh --encrypt operation over a partially encrypted device.The service sends its detailed output directly to its TTY, not to the journal.
An early crash may leave no persistent journal on disk, and journalctl -b -1
only works if that boot was retained. Capture the display or configured console
before retrying; do not assume a missing journal means encryption completed.
The resume path requires the ESP marker. If it is missing while reencryption is
incomplete, setup refuses to boot; investigate the marker and disk identity
instead of bypassing the check.
TPM Enrollment Fails
Section titled “TPM Enrollment Fails”Symptom: TPM enrollment error, password required every boot
Check:
# Check TPM availabilityls -la /dev/tpm*
# Check TPM statussystemd-cryptenroll --tpm2-device=listSolutions:
- Ensure TPM is enabled in BIOS/UEFI
- Check Secure Boot status:
bootctl status - Re-run enrollment manually:
Terminal window systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/sda3
Cannot Boot After Encryption
Section titled “Cannot Boot After Encryption”Symptom: System fails to boot, drops to emergency shell
Recovery:
- Boot from USB/recovery media
- Open encrypted device:
Terminal window cryptsetup open /dev/sda3 cryptedvgchange -ay pool - For a conventional writable-root image, mount the root and inspect its
configuration. For secure A/B, the store is immutable and verity-protected:
select the surviving signed healthy entry or rebuild/sign an update. Do not
apply the conventional
nixos-enter/nixos-rebuildrepair procedure to a root/verity slot.
Lost Password/PIN
Section titled “Lost Password/PIN”Recovery with recovery key:
- Boot system (will prompt for unlock)
- Enter the enrolled recovery key at the LUKS passphrase prompt
- Use the complete key exactly as displayed during enrollment
- System will unlock
If recovery key also lost:
- A working TPM/FIDO2 token or retained password slot may still unlock the disk.
- If no enrolled credential works, the encrypted data cannot be recovered by resetting a password.
Security Considerations
Section titled “Security Considerations”Image Distribution
Section titled “Image Distribution”✅ Safe to distribute:
- Unencrypted images don’t contain sensitive data
- No default keys to compromise
- User controls encryption credentials
⚠️ Best practices:
- Provide checksum/signature for image verification
- Use HTTPS for distribution
- Document first-boot process clearly
Key Management
Section titled “Key Management”TPM Keys:
- Bound to specific hardware
- Unlock depends on the enrolled PCR policy and optional PIN
- Firmware/Secure Boot changes can require recovery unlock
Recovery Keys:
- Generated by
systemd-cryptenrollafter successful hardware enrollment - Must be saved by user during first boot
- Store securely (password manager, physical safe)
FIDO2 Keys:
- Requires physical hardware token
- User presence verification required
- Can enroll multiple tokens
Protection boundaries
Section titled “Protection boundaries”Authenticate the distributed image before first boot: an attacker who modifies an unencrypted image can change the provisioning code. Encryption alone does not authenticate the boot chain. A plaintext build must contain no secrets. TPM enrollment protects an unlock credential according to its PCR/PIN policy; it does not guarantee that a stolen device cannot boot and unlock automatically.
The current deferred setup retains its password slot. In debug mode that
password is ghaf, so hardware enrollment does not remove the known development
unlock path. Verify hardware tokens and recovery enrollment rather than inferring
success from the encryption-complete screen.
Comparison with Build-Time Encryption
Section titled “Comparison with Build-Time Encryption”| Aspect | Build-Time Encryption | Deferred Encryption (Default) |
|---|---|---|
| Image state | LUKS with empty password | Plain LVM, no LUKS |
| Volume key | Shared across copies until reencryption | Created independently on each device |
| First boot | Rotate shared volume key and enroll device credentials | Apply LUKS and enroll device credentials |
| Distribution | Encrypted bytes compress poorly | Plain bytes usually compress better |
| Recovery | Depends on enrollment policy | Recovery enrollment attempted after hardware enrollment |
| Configuration | deferred = false |
deferred = true |
Removing a bootstrap password slot is not equivalent to replacing a shared volume key. Both approaches still require authenticated images and verification of the final credential policy.
Implementation Details
Section titled “Implementation Details”Modules
Section titled “Modules”Partitioning: modules/partitioning/disko-debug-partition.nix
- Defines disk layout with or without LUKS
- Creates LVM on LUKS (encrypted) or plain LVM (deferred)
- Checks
config.ghaf.storage.encryption.deferredflag - Compatible with disko image builder
Encryption Service: modules/partitioning/deferred-disk-encryption.nix
- Initrd systemd service for first-boot encryption
- Runs in
boot.initrd.systemd.servicesbefore root mount - Checks the installer marker and LUKS reencryption state, including interrupted setup
- Handles user interaction (release mode) via TTY
- Performs in-place encryption with
cryptsetup reencrypt - Enrolls TPM2/FIDO2 devices
- Deactivates LVM and closes LUKS before reboot
- Automatically imported with
disko-debug-partition
Reference Profile: modules/reference/profiles/mvp-user-trial.nix
- Enables deferred encryption by default
- Shows recommended configuration
- Used by most laptop targets
Technical Details
Section titled “Technical Details”Encryption Method:
- LUKS2 format (modern, flexible)
- AES-XTS-PLAIN64 cipher (default, secure)
- Argon2id PBKDF (memory: 1GB, parallelism: 4; secure against GPU attacks)
- 32MB reserved for LUKS header
In-Place Encryption:
- Fresh setup uses
cryptsetup reencrypt --encrypt; interrupted setup repairs and uses--resume-only - Journal-based resilience (crash-safe)
- Minimal data movement
- Approximately 15-20 minutes for 70GB
Boot Integration:
- First boot: Service runs in initrd before root mount
- State detection:
cryptsetup isLuksplus LUKS2 JSON reencryption metadata and the ESP marker - systemd-cryptsetup skipped on first boot (no LUKS yet)
- systemd-cryptenroll for TPM/FIDO2 enrollment
- Second boot: systemd-cryptsetup unlocks with TPM/FIDO2
- Automatic LVM activation after unlock
See Also
Section titled “See Also”- TPM 2.0 - TPM specification
- LUKS - Linux Unified Key Setup
- cryptsetup-reencrypt - In-place encryption tool
- systemd-cryptenroll - Key enrollment