Over the past month, I've re-built a couple different Gentoo Linux systems of mine with LUKS disk encryption. The first time, I stumbled through it and made many mistakes. The next time, I made some of the same mistakes again. Oops. In response to that, I figured I'd help myself out by writing a post that I can refer back to on the process.
As already mentioned above, this will be written from the perspective of a new install of Gentoo Linux.
I'll be using:
- Passphrase-based disk encryption with cryptsetup and LUKS
- A very simple disk partition scheme. No LVM / separate partitions for the system files.
- A distribution kernel
- Dracut to build the initramfs which is the default for distribution kernel's anyway.
It should be noted that the use of rEFInd does prevent us from also encrypting our
/boot files which is where our
kernel images will be stored. I don't believe there's a way to get rEFInd to boot such a system, but I would love to
be proven wrong, so if you're reading this and you know of a way, please let me know! In the mean time, if this is
important to you, check out this extremely detailed guide
covering an alternative method of setting up LUKS disk encryption (that includes
/boot too) but with Grub instead.
Optional Step for NVMe Drives
Before we even begin, I'm going to assume that in 2023 and beyond, that you're probably using an SSD, probably even an NVMe SSD. If so, then this may be a great time to check on your NVMe drive's sector size and whether changing it might be beneficial to you for performance reasons.
Install the sys-apps/nvme-cli package so you can use the
# emerge -av sys-apps/nvme-cli
Then we can check what our NVMe drive's current LBA format is, and what others may be available:
# nvme id-ns -H /dev/nvme0n1 | grep "Relative Performance" LBA Format 0 : Metadata Size: 0 bytes - Data Size: 512 bytes - Relative Performance: 0x2 Good (in use) LBA Format 1 : Metadata Size: 0 bytes - Data Size: 4096 bytes - Relative Performance: 0x1 Better
In this case, my NVMe drive is a WD Black SN850X 2TB. We can see there's another 4KB size LBA format that offers better performance.
To give you an idea of the performance difference that is possible, here's a simple
hdparm read test on this same NVMe
drive with the default LBA format with 512 byte sector size, performed on a LUKs encrypted partition on that same drive
(so, measuring the decryption and read speed together):
# hdparm -tT /dev/mapper/myCryptRootfs /dev/mapper/myCryptRootfs: Timing cached reads: 26614 MB in 2.00 seconds = 13330.47 MB/sec Timing buffered disk reads: 3802 MB in 3.00 seconds = 1267.30 MB/sec
Here's the same test performed again on the same NVMe drive, but after re-formatting the drive with the other LBA format with a 4KB sector size:
# hdparm -tT /dev/mapper/myCryptRootfs /dev/mapper/myCryptRootfs: Timing cached reads: 26232 MB in 2.00 seconds = 13138.91 MB/sec Timing buffered disk reads: 6002 MB in 3.00 seconds = 2000.40 MB/sec
Not a bad boost, honestly!
To switch the drive's LBA format, we unfortunately need to completely re-format the drive!
# nvme format --lbaf=1 /dev/nvme0n1 You are about to format nvme0n1, namespace 0x1. WARNING: Format may irrevocably delete this device's data. You have 10 seconds to press Ctrl-C to cancel this operation. Use the force [--force] option to suppress this warning. Sending format operation ... Success formatting namespace:1
--lbaf=1 parameter specifies the number of the LBA format we saw in the previous output from
nvme. That is, the
"LBA Format 0" and "LBA Format 1" lines. After running the
nvme format command like in the above example, you can
re-run the previous command to check that the LBA format you specified was set.
Some drives don't have alternate LBA formats that can be set via the
nvme tool. For example, I also have an OEM
Samsung 980 Pro NVMe drive, and I only see a single LBA format available:
# nvme id-ns -H /dev/nvme2n1 | grep "Relative Performance" LBA Format 0 : Metadata Size: 0 bytes - Data Size: 512 bytes - Relative Performance: 0 Best (in use)
I've heard that NVMe drives from certain manufacturers may require the use of their own special tools to change these settings. But I've never checked into this for myself, so I cannot say for certain.
There's nothing special here. I'm going with the following scheme:
/bootwhich will contain the EFI stuff too at
- The remainder of the disk will be a single partition. For my own personal desktops/laptops these days I don't ever
feel the need to bother with LVM, or even just plain separate partitions for things like
So in the end, using your disk partition tool of choice, you'd end up with something that looks like:
# fdisk -l /dev/nvme0n1 Disk /dev/nvme0n1: 1.82 TiB, 2000398934016 bytes, 488378646 sectors Disk model: WD_BLACK SN850X HS 2000GB Units: sectors of 1 * 4096 = 4096 bytes Sector size (logical/physical): 4096 bytes / 4096 bytes I/O size (minimum/optimal): 4096 bytes / 4096 bytes Disklabel type: gpt Disk identifier: 8183DFBE-8C57-0744-80E2-C0B958764776 Device Start End Sectors Size Type /dev/nvme0n1p1 256 131327 131072 512M EFI System /dev/nvme0n1p2 131328 488378623 488247296 1.8T Linux filesystem
Cryptsetup and LUKS Volume Creation
We need sys-fs/cryptsetup so we can use the
to manage our LUKS volumes.
# emerge -av sys-fs/cryptsetup
Of course, if you're preparing this new Gentoo install at this point from another Linux system that isn't Gentoo, then install cryptsetup as per whatever package manager you have available.
Also, we'll need to make sure that the "dm-crypt" kernel module is loaded.
# modprobe dm-crypt $ lsmod | grep dm_crypt dm_crypt 57344 0
Ok, now we're ready to set up our LUKS encrypted volume.
If you run
cryptsetup --help first you can see what some default settings are, shown at the bottom of the help output:
Default compiled-in metadata format is LUKS2 (for luksFormat action). LUKS2 external token plugin support is compiled-in. LUKS2 external token plugin path: /usr/lib64/cryptsetup. Default compiled-in key and passphrase parameters: Maximum keyfile size: 8192kB, Maximum interactive passphrase length 512 (characters) Default PBKDF for LUKS1: pbkdf2, iteration time: 2000 (ms) Default PBKDF for LUKS2: argon2id Iteration time: 2000, Memory required: 1048576kB, Parallel threads: 4 Default compiled-in device cipher parameters: loop-AES: aes, Key 256 bits plain: aes-cbc-essiv:sha256, Key: 256 bits, Password hashing: ripemd160 LUKS: aes-xts-plain64, Key: 256 bits, LUKS header hashing: sha256, RNG: /dev/random LUKS: Default keysize with XTS mode (two internal keys) will be doubled.
So, LUKS2 is the default,
aes-xts-plain64 is the default cipher, and the key size is 256 bits. It doesn't actually
say in this snippet of the output I've shared here (if you scroll up a bunch in the help text you can find it in the
description of the
--sector-size parameter), but the default encryption sector size is 512 bytes. We'll want to
adjust that to match our 4KB sector size of the NVMe drive we're using for best performance.
Now then, lets create the LUKS volume on our
# cryptsetup luksFormat --sector-size=4096 --key-size=512 /dev/nvme0n1p2
Note we explicitly set the sector size to match our NVMe drive's LBA format. And we bump up the key size to 512 for a bit of extra security.
You'll be asked to enter a passphrase here. Make sure it's secure! That's the whole point of this after all ...
Now we can open and map the LUKS volume so that we can access the volume as decrypted data.
# cryptsetup luksOpen /dev/nvme0n1p2 myCryptRootfs
The "myCryptRootfs" bit at the end is the name for the mapped device that will appear under
/dev/mapper. You can use
any name you like here. But for the rest of this post I will be using this name.
Here we can verify that our LUKS volume was created to our desired specifications:
# cryptsetup status myCryptRootfs /dev/mapper/myCryptRootfs is active. type: LUKS2 cipher: aes-xts-plain64 keysize: 512 bits key location: keyring device: /dev/nvme0n1p2 sector size: 4096 offset: 32768 sectors size: 3905945600 sectors mode: read/write
As you can see here, the device is available at
/dev/mapper/myCryptRootfs. At this point, you can proceed with the
rest of your Gentoo install as per the handbook. When you need to
create a filesystem on the rootfs partition and then mount it, just use
/dev/mapper/myCryptRootfs for the device. For
# mkfs.ext4 -L my_rootfs /dev/mapper/myCryptRootfs # mount /dev/mapper/myCryptRootfs /mnt/gentoo
When you're done with a LUKS volume, you need to unmount the mapped device and then close the LUKS volume.
# umount /dev/mapper/myCryptRootfs # cryptsetup luksClose myCryptRootfs
In the actual Gentoo system that you're installing (and by now are probably chroot'ed into it if you're following the
handbook) you'll want to add
USE="cryptsetup" to your
/etc/portage/make.conf (or I guess you could just add it for
sys-apps/systemd, but I've just been adding it globally every time I've done this).
Then you'll want to rebuild systemd with this new
cryptsetup use flag. This ensures that the necessary cryptsetup
bits are copied into the kernel's initramfs.
# emerge -avuDN world
Once you've installed your distribution kernel (e.g.
sys-kernel/gentoo-kernel), we'll need to update our Dracut
# mkdir /etc/dracut.conf.d # echo 'add_dracutmodules+=" crypt dm rootfs-block "' > /etc/dracut.conf.d/luks.conf
Then we need to rebuild our kernel's initramfs using a command similar to the below (substitute the package with your own choice for distribution kernel that you used):
# emerge -av --config sys-kernel/gentoo-kernel
/etc/fstab, be sure to specify attributes (such as
UUID) for the
device, not the encrypted partition itself.
One of the trickest parts for me was getting the kernel command line arguments correct. Since we're using Dracut, the arguments we must use are different than if we were not when it comes to specifying LUKS volume UUIDs.
- rd.luks.uuid should specify the UUID of the raw encrypted disk partition.
- root should specify the UUID of the opened / decrypted LUKS mapped device.
So, to put this all together. In my system if I check the
# blkid /dev/nvme0n1p1: LABEL_FATBOOT="BOOT" LABEL="BOOT" UUID="D792-8C94" BLOCK_SIZE="4096" TYPE="vfat" PARTUUID="31f9a04a-7803-1540-929e-4ec2bd4c33d6" /dev/nvme0n1p2: UUID="bbcea224-b849-47b2-a958-9d27007dfe33" TYPE="crypto_LUKS" PARTUUID="321e5973-13ca-f543-a5a1-d156efc8b576" /dev/mapper/myCryptRootfs: LABEL="ROOTFS" UUID="bffff17b-7948-43cf-9b35-f9b1b745db0d" BLOCK_SIZE="4096" TYPE="ext4"
So, my kernel command line arguments should have something like:
ro rd.luks.uuid=bbcea224-b849-47b2-a958-9d27007dfe33 root=UUID=bffff17b-7948-43cf-9b35-f9b1b745db0d
bbcea224-b849-47b2-a958-9d27007dfe33 refers to the raw encrypted disk partition
bffff17b-7948-43cf-9b35-f9b1b745db0d refers to the opened / decrypted LUKS mapped device
If you're using a swapfile that exists directly on the encrypted
partition and you're also intending on using it to hibernate
your system with, you'll need to also point the
resume kernel command line argument to the opened / decrypted LUKS
mapped device (
/dev/mapper/myCryptRootfs in our example case here).
So, a more complete set of kernel command line arguments that includes the necessary bits for system hibernation:
ro rd.luks.uuid=bbcea224-b849-47b2-a958-9d27007dfe33 root=UUID=bffff17b-7948-43cf-9b35-f9b1b745db0d resume=UUID=bffff17b-7948-43cf-9b35-f9b1b745db0d resume_offset=268404736
resume_offset is obviously replaced with the value you get yourself via usage of the
swap-offset utility as
the above linked article on hibernation explains.
Now you can place your final set of kernel command line arguments in your
/boot/refind_linux.conf file and finalize
your new Gentoo install.
That's all there is to it. Quite a bit actually once I see it all written out like this. Maybe one day the process will get refined to where it's as simple as clicking a button à la Mac OS's FileVault. One day ...