Skip to content

Instantly share code, notes, and snippets.

@Syderitic
Last active April 26, 2025 01:16
Show Gist options
  • Save Syderitic/23b9c22cb772e1b0e674ed4bb5a3abef to your computer and use it in GitHub Desktop.
Save Syderitic/23b9c22cb772e1b0e674ed4bb5a3abef to your computer and use it in GitHub Desktop.
Secure Encrypted Storage Setup with LUKS2, TPM2, FIDO2, and Btrfs

Secure Encrypted Storage Setup with LUKS2, TPM2, FIDO2, and Btrfs

Disclaimer: Use this guide at your own discretion. The author is not responsible for any data loss or security breaches that may occur. Always ensure you understand each step and its implications before proceeding.

This guide walks you through setting up a secure external drive, perfect for backing up sensitive data, securely handing over large data sets, or storing important files. It covers creating an encrypted drive that’s easy to unlock with trusted devices, such as a security key, while remaining highly secure if misplaced. Ideal for safeguarding personal data or business files, it offers practical recovery steps to ensure access even if you change devices.

By following this guide, you will have established a secure and user-friendly encrypted storage solution that leverages:

  • LUKS2 Encryption: Provides robust disk encryption.
  • TPM2 Integration: Enables automatic unlocking on trusted hardware.
  • FIDO2 Tokens: Offers flexible access across different systems.
  • Btrfs Filesystem: Utilizes modern filesystem features like compression and checksumming.

This setup balances security with convenience, allowing for seamless access on trusted devices while maintaining strong protection for your data at rest.


Table of Contents


Prerequisites

This guide uses several advanced tools to provide enhanced security for encrypted storage. Here’s a brief overview of each and why it's included:

  • LUKS2 (Linux Unified Key Setup): Provides robust encryption for disk partitions. It’s widely supported and offers flexible key management. Encryption is done with a multi-layer approach. First, the block device is encrypted using a master key. This master key is encrypted with each active user key. User keys are derived from passphrases, FIDO2 security keys, TPMs or smart cards. The multi-layer approach allows users to change their passphrase without re-encrypting the whole block device. Key slots can contain information to verify user passphrases or other types of keys. There is an unencrypted header at the beginning of an encrypted volume, which allows up to 8 (LUKS1) or 32 (LUKS2) encryption keys to be stored along with encryption parameters such as cipher type and key size.
  • TPM2 (Trusted Platform Module): A hardware security module that stores encryption keys securely, protecting them from software-based attacks. It enables automatic unlocking of encrypted disks when trusted hardware is present.
  • FIDO2: Adds an additional layer of security by allowing hardware-based two-factor authentication (e.g., USB keys) for disk encryption. This helps ensure only authorized users with the correct hardware can decrypt the drive.
  • Btrfs (B-tree File System): A modern file system chosen for its advanced features, such as copy-on-write, snapshots, and built-in data integrity checking, which make it ideal for secure and flexible storage solutions.

Each of these tools works together to provide a strong, hardware-backed encryption system that is both secure and user-friendly for trusted devices.

  • Operating System:

Linux with root or sudo access.

  • Installed Tools:

cryptsetup
systemd-cryptenroll
btrfs-progs

  • Hardware Requirements:

Trusted Platform Module 2.0 (TPM2) enabled and accessible.
Optional: FIDO2 token for additional security.

Important: Back up all important data before proceeding.


Securely Erase Existing Data

Optional: Erase Existing LUKS Header

If the device /dev/XXX has an existing LUKS header, erase it:

sudo cryptsetup luksErase /dev/XXX

Overwrite the Device with Random Data

Creating a temporary encrypted device with a random key and overwriting it ensures that any residual data is securely erased. It will also be of benefit when travelling after removing the header, since there is no way to tell if there is data on the device or if it was shredded.

  1. Create a Temporary Encrypted Device with a Random Key
sudo cryptsetup --cipher aes-xts-plain64 --key-file /dev/random --keyfile-size 32 create deviceToBeErased /dev/XXX
  1. Overwrite the Device
sudo dd if=/dev/zero of=/dev/mapper/deviceToBeErased bs=4M status=progress oflag=direct
  1. Close the Temporary Device
sudo cryptsetup close deviceToBeErased

Create a New LUKS2 Encrypted Container

+-----------------------+
|   Encrypted LUKS2     |
|  (Full Disk/Partition)|
+-----------------------+
        |
        V
+--------------------+
|     Btrfs          |
| (Files, Subvolumes)|
+--------------------+

  1. Initialize LUKS2 on the Device with a Temporary Password
sudo cryptsetup luksFormat --type luks2 /dev/XXX
  1. Open the LUKS Container
sudo cryptsetup luksOpen /dev/XXX secure_storage

Format the Encrypted Device with Btrfs

  1. Format with Btrfs and Optional Compression
sudo mkfs.btrfs -L secure_storage -m dup -d single -n 32k --csum xxhash -O compression=zstd:3 /dev/mapper/secure_storage

This mkfs.btrfs command initializes a Btrfs filesystem with specific settings optimized for security and performance:

  • -L secure_storage: Labels the filesystem as "secure_storage."
  • -m dup: Duplicates metadata for added resilience against data loss.
  • -d single: Configures a single data profile, ideal for single-disk setups.
  • -n 32k: Sets node size to 32 KB, balancing performance and overhead.
  • --csum xxhash: Uses xxhash for checksum, which is a fast and lightweight 64bit hash.
  • -O compression=zstd:3: Enables zstd compression at level 3, reducing storage needs without sacrificing speed.
  1. Close the Encrypted Device
sudo cryptsetup luksClose secure_storage

Enroll Recovery and Hardware Keys

+-------------------------------------------------+
|                LUKS2 Encrypted Container        |
|          (Full Disk or Partition Encryption)    |
|                                                 |
|   +-----------------------------------------+   |
|   |          LUKS Header (Key Slots)        |   |
|   |          (Backup for Recovery)          |   |
|   +-----------------------------------------+   |
+-------------------------------------------------+
             |
             v
    +-----------------+     +-----------------+     +-----------------+     +------------------+
    |   TPM2 Module    |    |    FIDO2 Key    |     |    Passphrase   |     |   Recovery Key   |
    |  (Hardware-based |    | (Security Token)|     | (Temporary,     |     | (Emergency Use)  |
    |     Unlock)      |    |                 |     |   user-defined) |     |                  |
    +-----------------+     +-----------------+     +-----------------+     +------------------+
             |                     |     |                    |               |
             |                     |     | with pin           |               |
             |                     |     -------------------------------------+
             |              no pin |                                          |
             v                     v                                          v
    +----------------------------------+                +-------------------------------+
    | Automatic Unlock (Trusted Device)|                |   Manual Unlock (User Input)  |
    |  (For TPM2/FIDO2 only)           |                |  (For Passphrase/Recovery Key)|
    +----------------------------------+                +-------------------------------+

Enroll a recovery key

sudo systemd-cryptenroll /dev/XXX --recovery-key
  • Save the recovery key in a secure location. It's the least user friendly method here. But it's also a good one to have in case everything else is not available. Consider it a last resort option. Recovery keys already have high entropy making password key derivation not as important.

Enroll TPM2 Key

   +------------------+             
   |    TPM2 CHIP      |  <-- TPM2: A dedicated hardware module for storing 
   +------------------+      and protecting encryption keys securely.

          |
          v
   +-------------------+
   |   Secure Storage   |  <-- Encrypted keys, credentials stored 
   +-------------------+      in a secure manner.

          |
          v
   +-----------------------+
   |  LUKS2 Encrypted Disk  |  <-- TPM2 supplies keys for unlocking
   +-----------------------+      LUKS2 encryption on your storage.

          |
          v
   +-----------------------+
   |    Filesystem         |  <-- Once decrypted, your system functions
   +-----------------------+      normally while data remains protected.
sudo systemd-cryptenroll /dev/XXX --tpm2-device=auto --tpm2-pcrs=7
  • Using --tpm2-pcrs=7 binds the encryption to specific system states (like Secure Boot). Be cautious, as system updates or configuration changes can alter PCR values, potentially locking you out. Consider using a combination of PCRs that reflect your security needs without risking unintended lockouts. PCRs 0, 2, and 4 are commonly used, but they tie the encryption more closely to the system firmware and bootloader. You can also just not use this option and use the defaults of systemd-cryptenroll

Optional: Enroll FIDO2 Token(s)

   +----------------------+
   |   FIDO2 Device (USB) |  <-- A security key, like Yubikey, that 
   +----------------------+      provides strong authentication.

            |
            v
   +----------------------------+
   | User Authentication Input  |  <-- User presence and authentication 
   +----------------------------+      (e.g., fingerprint or PIN).

            |
            v
   +-----------------------+
   |  LUKS2 Encrypted Disk |  <-- FIDO2 helps unlock the encrypted
   +-----------------------+      LUKS2 storage, using information
                                  in the header, securing the key.
                                  The FIDO2 device never stores any key.
            |
            v
   +------------------------+
   |   Filesystem           |  <-- After authentication, access to the
   +------------------------+      system and data is granted.
sudo systemd-cryptenroll /dev/XXX --fido2-device=auto --fido2-with-client-pin=no
  • Enabling --fido2-with-client-pin=yes adds an extra layer of security by requiring a PIN when using the FIDO2 token - not very friendly. But if user convenience is a priority and the risk is acceptable, leaving the PIN disabled is fine. The idea is to be able to access the device in another machine by having the hardware key. This should be a good, user friendly, alternative in case the trusted devices are not available (e.g. malfunction, stolen, etc.).

Configure Key Slot Priorities

When setting up key slots for TPM and FIDO2, prioritizing based on usage scenarios can optimize security and convenience:

  • Primary Slot: Use TPM for automatic unlock on trusted systems. This ensures your device boots seamlessly when secure hardware is available.
  • Secondary Slot (Backup/Challenge): Configure FIDO2 as a challenge-response backup. This slot protects against tampering or TPM failure, adding resilience.
  • Manual Key Slot: For emergencies, reserve a manual passphrase slot. Ensure this slot is accessible only when other keys fail, maintaining high security. Prioritizing slots this way balances automation with robust fallback options.

To ensure that the TPM2 key is used by default when unlocking the encrypted device, you need to set its key slot to have the highest priority.

  1. Identify the TPM2 Key Slot Use systemd-cryptenroll to display the status of all key slots
sudo systemd-cryptenroll /dev/XXX
  • Note:The output will list all key slots with their types (e.g., password, tpm2, fido2, recovery). Take note of the number corresponding to the tpm2 key slot.
  1. Set the Priority of the TPM2 Key Slot Replace <tpm_key_slot> with the number of the TPM2 key slot identified in the previous step
sudo cryptsetup config /dev/XXX --priority prefer --key-slot <tpm_key_slot>
  • This command sets the specified key slot as the preferred one. When the system attempts to unlock the device, it will first try using the TPM2 key. Alternatively, you can make it try to unlock with the FIDO2 token by default. If you didn't setup a pin, then all you need to do is touch it.

Remove Temporary Password

After enrolling the recovery and hardware keys, it's crucial to remove the temporary password used during the initial setup. This step minimizes the attack surface by eliminating an extra authentication method that could be exploited. Examine the output of the previous step to identify the key slot associated with the temporary password. Look for a key slot with the type password and note its slot number (e.g., Key Slot 0).

systemd-cryptenroll /dev/XXX --wipe-slot=<slot with password>

Confirm the action when prompted. This command securely removes the temporary password from the LUKS header.

  • Note: Always ensure you have at least one valid key (preferably the recovery key) before removing any key slots. Losing all keys will render your data permanently inaccessible.

Backup the LUKS Header

Backing up the LUKS header is crucial. If the header becomes corrupted or lost, you won't be able to access your data, even if you have all the correct keys. Regular backups of the LUKS header ensure that you can recover your encrypted data in case of header damage.

  1. Backup the LUKS Header to a Secure Location Replace header_secure_storage.img with your desired filename. Ensure you store this backup in a secure and separate location, such as an encrypted external drive or a secure cloud storage service.
sudo cryptsetup luksHeaderBackup /dev/XXX --header-backup-file header_secure_storage.img
  1. Optionally create a sha256 of the header
sha256sum header_secure_storage.img > header_secure_storage.img.sha256

Automate Unlocking on Trusted Devices

For convenience, you can configure your trusted computer to automatically unlock and mount the encrypted device when connected. This is achieved using systemd services and udev rules.

Create a systemd Service Unit

sudo nano /etc/systemd/system/[email protected]
[Unit]
Description=Unlock LUKS device %i
After=dev-disk-by\x2duuid-%i.device
BindsTo=dev-disk-by\x2duuid-%i.device

[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/bin/sh -c 'UUID=$$(systemd-escape $$(systemd-escape -u %I)); /usr/bin/cryptsetup luksOpen /dev/disk/by-uuid/$$UUID cryptvolume-$$UUID'
ExecStop=/bin/sh -c 'UUID=$$(systemd-escape $$(systemd-escape -u %I)); if [ -b /dev/mapper/cryptvolume-$$UUID ]; then /usr/bin/cryptsetup luksClose cryptvolume-$$UUID; fi'

[Install]
WantedBy=multi-user.target
  • Note: It's a bit messy. If someone finds a better way, please submit it.

Reload systemd Daemon

sudo systemctl daemon-reload

Create a udev Rule

  1. Find Your Device's UUID
sudo blkid /dev/XXX
  • Note the UUID value (e.g., 123e4567-e89b-12d3-a456-426614174000).
  1. Create the udev Rule File Replace ########-####-####-####-############ with the actual UUID of your device:
sudo nano /etc/udev/rules.d/99-luks-unlock.rules
ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}=="########-####-####-####-############", TAG+="systemd", ENV{SYSTEMD_WANTS}+="luks-unlock@########\\x2d####\\x2d####\\x2d####\\x2d############.service"
  • Note: You can do this for as many devices setup with an auto unlock option like TPM2 or FIDO2. Just add another line to the file with corresponding UUID.
  1. Reload udev Rules
sudo udevadm control --reload-rules && sudo udevadm trigger

Data Protection During Transit

When transporting the encrypted device, you can enhance security by erasing the LUKS header. Without the header, the data on the device appears as random data, making it extremely difficult for unauthorized parties to recognize or access.

  1. Erase the LUKS Header
dd if=/dev/urandom of=/dev/XXX bs=512 count=32768 oflag=direct status=progress
  • Note: Adjust the count parameter based on the size of the LUKS header. For LUKS2 this should be the right size. If in doubt check the size of the backup of the header.
  1. Transport the Device Now, the device can be safely transported. Even if intercepted, the data remains inaccessible.
  2. Restore the LUKS Header at the Destination
cryptsetup luksHeaderRestore /dev/XXX --header-backup-file header_secure_storage.img
  1. Unlock and Access Your Data Just replug the device and it should auto mount if it is on a trusted device. Otherwise, sudo cryptsetup luksOpen /dev/XXX secure_storage and sudo mount /dev/mapper/secure_storage /mnt/secure_storage
  • Note: Always ensure you have a valid backup of the LUKS header before erasing it. Losing the header without a backup will render your data permanently inaccessible.

Additional Considerations

Regular Backups

  • Maintain regular backups of your data and LUKS header in multiple secure locations.
  • Test your backups periodically to ensure data integrity.

Security Updates:

  • Keep your system and cryptographic tools updated to protect against vulnerabilities.

Key Management:

  • Periodically review and manage your key slots.
  • Remove any unused or compromised keys immediately.

TPM and FIDO2 Tokens:

  • Secure your hardware tokens physically.
  • Be aware of the implications if the TPM is reset or the motherboard is replaced.

Disaster Recovery Plan:

  • Document your recovery procedures.
  • Ensure that trusted individuals know how to access the recovery keys in case of emergencies.

Physical Security:

  • Protect your devices from unauthorized physical access.
  • Consider using tamper-evident seals or enclosures.

License

This guide is licensed under the MIT License.

Copyright 2024

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment