1. Foreword
  2. Volume Configuration
    1. Creating a Volume
    2. Working with an Existing Volume
  3. Generating and Storing the Keyfiles
    1. Key Generation
    2. Secure Storage of Keys
  4. Applying the Keyfile to a LUKS Volume
    1. Key Application
  5. Automatic Unlock and Mount
    1. crypttab method (runit / systemd)
    2. rc.local method (runit)
    3. mountscript method (runit)
    4. dmcrypt method (openrc)
  6. Further Reading

Foreword

This guide assumes no prior knowledge. The end state of this guide is to create/alter a LUKS volume on a separate device that will be automatically unlocked/mounted upon entering your main encryption key. This guide assumes you have a system with full disk encryption, such as one described in my other guide. To impliment a system like this on a machine not utilizing full disk encryption would be pointless, as an attacked could simply mount whatever volume your keys reside in and use them to access your encrypted volumes.


Volume Configuration

If you wish to perform this on any device without LUKS encryption already, the device must be re-partitioned and thus wiped. It’s still a good idea, I’d recommend you move the data off, apply the encryption and put the data back on once it’s all done.

Creating a Volume

If you already have a device with LUKS, proceed to the next section. The process we will utilize to create the volume is near identical to the one in the void install guide, however with vastly fewer steps.

First, identify the block device you want to work with via lsblk -f

$ lsblk -f
└─HardDrive fat HardDrive abcd-1234-qwop  419.2G /dev/sda

The above command will output a lot more, however, in this example the device is /dev/sda, a 419.2GB volume with a UUID of abcd-1234-qwop It is currently unencrypted and formatted as “fat”, let’s fix that

utilize cfdisk to target the device

$ cfdisk /dev/sda
...
# Delete all volumes and write to the disk
# Create a single, maximum size "Primary" "Linux" partition
# Write to the disk and exit the program

Now that the disk has been partitioned, let’s encrypt it with cryptsetup. This will encrypt it and create a static password. We’ll then mount it and format it to be ext4

$ cryptsetup -c aes-xts-plain64 -y -s 512 luksFormat /dev/sda
...
$ cryptsetup luksOpen /dev/sda CryptDrive
...
$ mkfs.ext4 /dev/mapper/CryptDrive
...
$ umount -R /dev/mapper/CryptDrive

Now that it’s ext4 and encrypted, move on to the next section.

Working with an Existing Volume

Working with a current device is relatively straightforward. First, we need to identify the device we want to add the key to. This assumes it’s currently unlocked. If it’s not unlocked, unlock it with cryptsetup luksOpen as demonstrated above

$ lsblk -f
└─/dev/sda   crypto_LUKS 2              abcd-123-qwop

Now, let’s see how many keyslots it has.

$ cryptsetup luksDump /dev/sda
...
Keyslots:
	Key Slot 0: ENABLED
	Iterations:				... 	
	Salt:               	...
	Key material offset:	...
	AF stripes:            	...

This is where we will be working. Move on to the next step.


Generating and Storing the Keyfiles

Key Generation

For this, we will be generating a strong key utilizing the output of /dev/random into a file of bit soup. This file will technically be plain-text and thus human readable, but it’s strength will be the fact that it will be stored securely within an already encrypted device, more on that later.

The below command will make a hidden key directory within /root. replace /abcd-123-qwop.key with whatever the UUID of your device is. This isn’t strictly required, however, it will make it easier to keep track of what keys go to what devices should you do this to multiple.

$ mkdir /root/.keys/
$ dd if=/dev/random of=/root/.keys/abcd-123-qwop.key bs=4096 count=1

Secure Storage of Keys

As stated above, the key will be stored in a human readable format, however, it will be in the root directory of a volume that’s already encrypted. This will render it inaccessable to the data when it’s at rest. Even in the event you leave your computer powered on and unlocked, it would be difficult for an attacker to take these keys as they would need the root password.

It is recommended to take backups of these keys in another LUKS container, I.E an external USB. This goes doubly if you decide to delete the manual key (password) of the volume. Such a thing is possible, but goes beyond the scope of this guide.


Applying the Keyfile to a LUKS Volume

We’re almost done. Now, we will finally apply the key to the volume and configure it to automount after you unlock your system. Up to this point, ensure your device is unmounted and locked.

Key Application

If you’ve up to this point unmounted/unplugged/hotswapped storage devices and/or restarted your PC, it’s likely that your /dev mapper has rearranged it’s name scheme. UUIDs, however, never change. Before we apply the key, let’s ensure the target is the correct one.

$ lsblk -f
└─/dev/sda   crypto_LUKS 2              abcd-123-qwop

That’s the target drive. Ensure the UUID is the same as the one we were working with earlier. Let’s apply our key

$ cryptsetup luksAddKey /dev/sda /root/.keys/abcd-123-qwop.key
# You will be prompted to enter your manual key

Let’s verify the key has been applied.

$ cryptsetup luksDump /dev/sda
...
Keyslots:
	Key Slot 0: ENABLED
	Iterations:				... 	
	Salt:               	...
	Key material offset:	...
	AF stripes:            	...
Key Slot 1: ENABLED
	Iterations:				...     	
	Salt:               	...
	Key material offset:	...
	AF stripes:            	...

Success. The keyfile has been applied. At this point, you may wish to test the usage of the key. To do so, lock it via cryptsetup close and run the following command

$ cryptsetup luksOpen UUID=abcd-1234-qwop CryptDrive --key-file /root/.keys/abcd-1234-qwop.key

It should unlock with no password input using solely the key we generated earlier via /dev/random output.

Automatic Unlock and Mount

There are three different ways in which we can automate the process of automating the unlocking and mounting of encrypted devices. Each one functions slightly differently, however the method I prefer most is the mountscript method, as it is just as secure as the other 2 and will allow you to begin using your computer the fastest of the 3 methods.

crypttab method (runit / systemd)

The first method we will go over is regarding cryptab and fstab. As discussed in the install guide, the boot image generated via dracut must have the “crypt” module added. Please ensure that was done prior to proceeding with this method, or move on to the next.

One primary benefit of this method is that is is not dependant on any particular init system. crypttab and it’s contents will be parsed both by runit and systemd, so the config will work regardless.

note: openrc does NOT parse crypttab. If you use a distro with openrc, these definitions must be placed in /etc/conf.d/dmcrypt

rc.local does not exist in systemd, and the process to get something similar working on a distro with systemd is a bit more involved. This solution may be the best if you plan on utilizing the configuration across multiple distros, as it’s implimentation can be used anywhere.

crypttab is the file in which we are able to tell our system what encrypted devices need to be known about, as well as the name it will mount it to in /dev/mapper. Let’s do that now.

$ nano /etc/crypttab
...
<name of device in mapper>	<actual device>  <password/keyfile>  <options>
...
BigStorage	UUID=unencrypted-UUID-of-target-device	/root/.keys/123-abcd.key

Now that we have that, we must tell our fstab where to actually mount the drive we named BigStorage. By default, fstab will not automatically create the mount point. You may mount it wherever you want, however ensure the mountpoint is empty and the mountpoint actually exists. In this example, I created the mountpoint with sudo mkdir /media/BigStorage and granted access to myself with sudo chown -hR $USER:$USER /media/BigStorage

$ nano /etc/fstab
...
<mapped device>		<mountpoint>	<filesystem>	<options>
...
/dev/mapper/BigStorage	/media/BigStorage	ext4	defaults	0	0

At this point, after a system reboot, the devices will be automatically mapped at the defined location. The only drawback is that this will all occur during runit stage 1 alongside all other most basic system initialization tasks, meaning you may not be able to interrupt it if something goes awry. Additionally, this will attempt to unlock and mount all defined devices in sequential order, meaning that if you have many and/or large volumes, this may take quite some time (20+ seconds)

rc.local method (runit)

Unlike the above method, this method will only work with any distro that supports an rc.local script. This will function very similarly to the above in terms of speed, however a key difference is that it’s done in simple shell script in the same exact way you’d do it if you were to do it manually in your shell. rc.local defines optional things you want done in runit stage 2 concurrent to the execution of daemons and services you’ve enabled prior. This method is extremely simple and only requires you to keep track of one file, however it’s not portable like the above method, and still just as slow.

This is as simple as inputting the cryptsetup and mount commands as your normally would, but in order. As this is all done programatically, ensure the cryptsetup lines are above your mount lines, or they won’t be able to mount as they won’t be unlocked

$ nano /etc/rc.local
...
cryptsetup luksOpen UUID=abcd-1234-qwop CryptDrive --key-file /root/.keys/abcd-1234-qwop.key
mount /dev/mapper/CryptDrive /media/CryptDrive
# Save and exit the file

mountscript method (runit)

With this method, we’re going to use the same exact syntax as the above method, however, we’ll have it sitting elsewhere as an independant script named mountscript and having rc.local run it silently in the background. This method will allow you to almost instantly be able to log in after unencrypting your master drive, as all of the slow cryptsetup and mount work is being done in the background. The only downside is that once you’re in, it may take a few seconds to actually access the data you have mounted in these locations.

First, let’s move all of the lines defined above from /etc/rc.local from there to a new file we’ll create, /etc/mountscript

$ nano /etc/mountscript
...
cryptsetup luksOpen UUID=abcd-1234-qwop CryptDrive --key-file /root/.keys/abcd-1234-qwop.key
mount /dev/mapper/CryptDrive /media/CryptDrive
# Save and exit the file

At this point, let’s go back to rc.local and tell it to run the script in the background. Ensure the cryptsetup and mount lines from before are deleted from rc.local.

$ nano /etc/rc.local
...
/etc/mountscript > /dev/null 2>&1 &

dmcrypt method (openrc)

In a distribution using openrc such as artix or gentoo, crypttab is not parsed by default, instead it’s configuration file exists as /etc/conf.d/dmcrypt.

First, you must define your desired devices in the manner below. Your distro should ship a default config which has similar examples.

...
target=HDD5000B
source=UUID="85f3c48c-8205-4606-bd7d-412ebf0e4724"
key="/etc/keys/85f3c48c-8205-4606-bd7d-412ebf0e4724.key"
...

As a note, we will configure our fstab in the same manner as with crypttab.

Once the values have been added to /etc/conf.d/dmcrypt, we must add dmcrypt to the boot runlevel

$ sudo rc-config add dmcrypt boot

Now, on every boot it will iterate through the entries in /etc/conf.d/dmcrypt prior to executing fstab.


Further Reading

The links below go into great depth regarding the process above, including processes we didn’t touch on such encryption of the boot partition, encrypting swap and much more.

Red Hat: LUKS Setup

Red Hat: Replacing rc.local in systemd

Arch Wiki: dm-crypt

Gentoo Wiki: dm-crypt