Real full disk encryption using GRUB on Void Linux for BIOS

Published on 2018-03-30. Modified on 2021-08-30.

In this tutorial we're going to take a look at setting up full disk encryption on the Void Linux base system using GRUB for a BIOS/MBR based setup. While the choice to install in UEFI mode is encouraging, vendor UEFI implementations still carry more bugs than their BIOS/MBR counterparts. Contrary to "modern" advice I still haven't found any compelling reason to use UEFI yet.

When we use GRUB as the boot loader we can setup a full disk LUKS encryption system without any use of a separated unencrypted boot partition. Normally a separate boot partition needs to remain unencrypted as the bootloader needs to be able to boot the kernel before invoking LUKS, but because GRUB can load encryption modules such as crypto.mod, luks.mod, and cryptodisk.mod we can use GRUB in various settings and still gain a real full disk encryption model without the need for an unencrypted boot partition. This setup is not possible using other boot loaders such as systemd-boot or syslinux, because neither of those support loading encryption modules (as of writing).

The benefits of running with a real full disk encryption rather than an unencrypted boot partition is that we can mitigate numerous attacks that can occur before and during the boot process, such as an attacker installing a modified kernel that is able to harvest your password phrase. This doesn't mean that the system isn't vulnerable to tampering with the BIOS or the UEFI boot loader itself, however it does provide yet another level of security that makes it a bit more difficult to gain access to the encrypted information.

To keep things as simple as possible we're not going to use LVM (Logical Volume Management). LVM is a system for partitioning and managing logical volumes, or filesystems, but it has nothing to do with encryption in itself. LVM is a much more advanced and flexible system than the traditional method of partitioning a disk. LVM is used for easy resizing and moving partitions. With LVM you can create as many Logical Volumes as you need and you can also use LVM to take snapshots of your filesystem. However, unless you actually need any of these features, adding the extra layer of complexity doesn't provide any benefits.

For BIOS our setup will simply consist of one disk partition, it will look like this:

sdX1 (LUKS with EXT4)

Everything will be fully encrypted with LUKS.

Instead of using a partition for swap we will use a swap file. Not only is this a much more simple setup, but it also eliminates the need to encrypt the swap partition independently. By using a swap file we automatically get the encryption we need.

If you need to be able to put your machine into hibernation, you need a proper swap partition. I never use hibernation so I am not going to go through that in this tutorial.

I'll use EXT4 as the filesystem, but you can change that into something else.

One minor downside to this setup with GRUB is that you have to enter your encryption password twice. Once for GRUB and another time during the system boot-up when the Linux initrd image is loaded. However, this can be avoided by adding a keyfile to mkinitcpio - which we'll do. When the system is booted the keyfile resides in the ramfs, unencrypted, but at this point, so does the LUKS master key, so if an attacker can get a hold of your keyfile in this situation, he might as well get your master key. In such a situation you will need to do a lot more to secure your system, something which is well beyond the scope of this tutorial.

Let's get started.

  1. Boot the Void Linux install medium. And login as root with the password voidlinux.

  2. Verify your network is up and running with the command:

    # ip a

    And try to ping something:

    # ping voidlinux.org
  3. If you prefer Bash you can change the shell to that before you begin:

    # bash

    Setup your keyboard:

    # loadkeys KEYMAP

    Where "KEYMAP" corresponds to your keyboard layout. For a list of all the available keymaps, use the command:

    # ls /usr/share/kbd/keymaps/i386/qwerty/

    In my case I am using a Danish keyboard layout so I am using the following command:

    # loadkeys dk

    If you find that your keymap is represented with and without a "latin" version, like in dk and dk-latin, then the latin version is the one that enables dead keys while the one without the latin part doesn't.

    Dead keys are those keyboard keys that do not type anything until you hit the key twice or a combination of two keys. Tildes and umlauts are like this by default under plain Linux. This is the default behavior for these keys under Microsoft Windows as well.

    Remember that the above keyboard setup might change if you use a graphical window system (such as Xfce4 or Gnome for example) because such systems often has their own keyboard layout setup. The above setup is for usage in the terminal without the X Window System running.

    Also if you use another keyboard type than QWERTY, then take a look in /usr/share/kbd/keymaps/i386/ for the supported types.

  4. Locate your harddrive:

    # fdisk -l
  5. Before you begin partitioning your disk you may want to write random data to the drive first with something like the following:

    # dd if=/dev/urandom of=/dev/sdX

    Please note that this can be a very time-consuming process, depending on the speed of your CPU and disk, as well as the size of the disk. If you don't write random data to the whole device, it may be possible for an adversary to deduce how much space is actually being used.

    Personally I don't write random data to the disk as I just want to protect the disk from being accessed in case the computer is ever stolen.

    Next, partition the disk. If you're not comfortable using fdisk then cfdisk is a nice partitioning tool. We're going to create one partition for BIOS or two partitions for UEFI.

    # cfdisk /dev/sdX

    Create the relevant partitions.

    Since this is a BIOS setup choose "dos" as the label type and remember to make the root filesystem bootable.

    If you're using a disk that already has a GPT label, you can change that with fdisk /dev/sdX, then choose o, choose dos, and then w.

  6. Format the root partition using LUKS:

    # cryptsetup luksFormat --type luks1 /dev/sdX1

    cryptsetup currently defaults to v2 of the LUKS header. There has been great work at getting GRUB version 2.06 to support LUKS2, but there still is a bug that prevents this from working. Make sure you specify --type luks1 when creating the encrypted partition. See bug 55093 and the Encrypted boot section at the Arch Linux wiki for details.

  7. Open the newly formatted LUKS partition:

    # cryptsetup open /dev/sdX1 cryptroot

    In this case I have chosen the name "cryptroot" for the encrypted partition, but you can name it whatever you want, just remember to change it everywhere where I have used "cryptroot" in this tutorial.

  8. Format the root volume with the filesystem of your choice (EXT4, XFS, Btrfs, etc.) In this case we're going to use EXT4.

    # mkfs.ext4 /dev/mapper/cryptroot
  9. Mount the root filesystem and dev, proc, and sys:

    # mount /dev/mapper/cryptroot /mnt
    # mkdir /mnt/dev /mnt/proc /mnt/sys
    # mount --rbind /dev /mnt/dev
    # mount --rbind /proc /mnt/proc
    # mount --rbind /sys /mnt/sys
  10. Now we're going to install the base system and a couple of other packages. I use Vim as my text editor, so I also install that. You can change that into something else. You can search on the Void Linux Packages page to see if your favorite editor is available.

    # xbps-install -Sy -R https://alpha.de.repo.voidlinux.org/current -r /mnt base-system cryptsetup grub vim

    Answer yes to import the key and install the packages.

  11. Chroot into the newly created system and set it up (change the zoneinfo to your location):

    # chroot /mnt /bin/bash
    # passwd
    # chsh -s /bin/bash
    # ln -sf /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime
    # echo LANG=en_US.UTF-8 > /etc/locale.conf
    # echo "en_US.UTF-8 UTF-8" >> /etc/default/libc-locales
    # xbps-reconfigure -f glibc-locales
    # echo my-hostname > /etc/hostname
  12. Edit /etc/rc.conf to suit your needs.

  13. Create the keyfile (in order to avoid having to type the encryption password twice). Let's use a persistent block device naming. I prefer the UUID.

    # blkid -o value -s UUID /dev/sdX1
    a3269d46-cf1b-46da-89bb-ec4ee3007432

    Then create the keyfile:

    # dd bs=512 count=4 if=/dev/urandom of=/crypto_keyfile.bin
    # cryptsetup luksAddKey /dev/disk/by-uuid/a3269d46-cf1b-46da-89bb-ec4ee3007432 /crypto_keyfile.bin
    # chmod 000 /crypto_keyfile.bin
    # chmod -R g-rwx,o-rwx /boot
  14. Setup crypttab. In order to avoid typing the UUID manually, you can direct the output of blkid into the crypttab file and then edit it afterwards.

    # blkid -o value -s UUID /dev/sdX1 >> /etc/crypttab

    Then edit crypttab and set it up correctly.

    # vim /etc/crypttab
    cryptroot UUID=a3269d46-cf1b-46da-89bb-ec4ee3007432 /crypto_keyfile.bin luks
  15. Then we need to setup Dracut:

    # vim /etc/dracut.conf.d/10-crypt.conf
    install_items+=" /crypto_keyfile.bin /etc/crypttab "

    Notice the surrounding white spaces, you need those!

  16. Enable encryption support in GRUB:

    Again I like to direct the output of blkid in order to get the UUID.

    # blkid -o value -s UUID /dev/sdX1 >> /etc/default/grub
    # vim /etc/default/grub

    Edit lines to match your UUID:

    GRUB_CMDLINE_LINUX="cryptdevice=UUID=a3269d46-cf1b-46da-89bb-ec4ee3007432:cryptroot"
    GRUB_ENABLE_CRYPTODISK=y

    Add "rd.auto=1" to the default command line.

    GRUB_CMDLINE_LINUX_DEFAULT="loglevel=4 slub_debug=P page_poison=1 rd.auto=1"

    The rd.auto option enables autoassembly of special devices like LUKS, dmraid, mdraid or lvm. Default is off as of dracut version >= 024.

    I also prefer to set the timeout to something less than 5:

    GRUB_TIMEOUT=1

    If you're using an SSD disk you need to consider whether you want to add the "discard" option in order to enable continues TRIM support.

    On traditional magnetic drives, deleted files are not completely removed from the disk at the time of deletion (this is why you can recover deleted files), instead the filesystem references the location of a file on the disk, and when a file is deleted, that reference is erased, allowing you to write new data over old data in these spaces.

    With SSDs this is different. New data can only be written on completely new or erased cells of the drive. Because the space must be cleared prior to a write, if enough free space is not already available at the time a file is being written, it must be erased first. This can negatively affect performance.

    TRIM allows the SSD to erase unused cells in the background so that the SSD does not have to erase the cell later when it has to write, thus speeding up the write process.

    Most recent SSDs have their own internal garbage collection process that does this very effectively, so TRIM isn't necessary to maintain write performance anymore.

    Without enabling TRIM (either periodic TRIM or continues TRIM) garbage collection can become write-amplified in the edge case where your hard drive is almost full. This problem can be mostly mitigated by over-provisioning the SSD's unused space (leave about 20% of the drive free).

    Please see the Relevant reading section for further information regarding TRIM, especially the links regarding OpenBSD.

    From a security point of view, enabling TRIM allows an attacker to get an idea of how full the volume is.

    If you're using a SSD disk and you want to enable continues TRIM you need to add "allow-discards":

    GRUB_CMDLINE_LINUX="cryptdevice=UUID=a3269d46-cf1b-46da-89bb-ec4ee3007432:cryptroot rd.luks.allow-discards"
  17. Install GRUB to the disk and generate the configuration file:

    # grub-install /dev/sdX --recheck
    # grub-mkconfig -o /boot/grub/grub.cfg

    Ensure an initramfs is generated:

    # xbps-reconfigure -fa

    Exit the chroot, unmount the filesystems, and reboot the system:

    # exit
    # umount -R /mnt
    # reboot
  18. You should now be presented with the GRUB prompt for the password. Once the system is finished booting, log in.

    Now you can setup the network, add users, and install additional packages.

    First I setup the network with dhcpcd. The dhcpcd package is included in the base-system meta package, so it is already installed.

    # ln -s /etc/sv/dhcpcd /var/service/

    This will automatically start the service in your current runlevel. Once a service is linked it will always start on boot and restart if it stops. If you need to keep an enabled service from starting automatically at boot, create a file named "down" in the service directory like this:

    # touch /etc/sv/service_name/down

    Then I'll add a normal user (called foo in this example) and add him to the wheel group:

    # useradd -m -g wheel -s /bin/bash foo
    # passwd foo

    I also like to install the chrony daemon to keep my clock in sync and socklog for logging and to enable ACPI events:

    # xbps-install -Su
    # xbps-install chrony socklog-void
    # ln -s /etc/sv/chronyd /var/service/
    # ln -s /etc/sv/socklog-unix /var/service/
    # ln -s /etc/sv/nanoklogd /var/service/
    # ln -s /etc/sv/acpid /var/service

    You probably wont need all the six running tty's so you can remove some of them from /var/service/.

  19. Then it's time to setup the relevant entries to the /etc/hosts file:

    # vim /etc/hosts
    127.0.0.1        localhost
    ::1              localhost
    127.0.1.1        myhostname.localdomain myhostname
    
  20. Last we need to create a swap file (if you want that). Use dd to create a file the size of your choosing. E.g. creating a 4GB swap file:

    # dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress

    Set the right permissions:

    # chmod 600 /swapfile

    Format the file to swap:

    # mkswap /swapfile

    Activate the swap file:

    # swapon /swapfile

    Finally, edit the /etc/fstab configuration to add an entry for the swap file:

    # vim /etc/fstab
    /swapfile none swap defaults 0 0

    You can check that the swapfile is up and running with:

    # free

That's it. You can now proceed and install all the rest of your favorite programs :)

Relevant reading

If you have any comments or corrections please feel free to email them to me. Also, if you found this content useful consider supporting me on Patreon