Real full disk encryption using GRUB on Void Linux for BIOS

Posted on 2018-03-30. Last updated on 2020-03-21.
In this tutorial we're gonna take a look at manually setting up full disk encryption on a BIOS MBR based system using GRUB on Void Linux - the KISS way.

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 boot loaders support loading encryption modules (at least 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 bootloader itself, however it does provide yet another level of security that makes it a bit more difficult to gain access to the encrypted information.

It is very difficult to prevent tampering with the BIOS and/or other hardware components if you leave your computer out-of-sight, however you can dump your MBR and take a look at it with a hexedecimal editor and compare it to an old secure dump.

Other more secure options exist such as using UEFI with custom signature keys and Secure Boot instead of MBR, or booting from another medium, but this tutorial is about a legacy BIOS setup.

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.

Our setup will simply consist of two disk partitions, one for swap, and one for our normal filesystem. It will look like this:

sdX1 (LUKS encrypted swap)
sdX2 (LUKS with EXT4, XFS, Btrfs or something else)

Both partitions will be fully encrypted with LUKS.

In this example we'll use EXT4 as the filesystem, but you can easily change it into XFS or Btrfs or 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 the crypttab file. 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.

  2. Verify your network using ip a.

  3. I prefer to use Bash, so I change my shell to that, then setup the keyboard:

    # bash
    # loadkeys dk
  4. Locate your hard drive:

    # fdisk -l
  5. Overwrite your disk with some random data and partition the disk:

    In this case my drive is sda.

    Before you begin partitioning your disk it's a good idea to overwrite the disk with random data. You can do this with the dd command. Please notice that this takes considerable time with a large disk, and you can skip this step if you want to.

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

    Remember to verify that you're using the correct disklabel type! Check and change it with fdisk.

    Next, let's partition the disk. cfdisk is a nice partitioning tool. We're going to create two partitions.

    # cfdisk /dev/sda

    Since this is a BIOS setup choose "dos" as the label type and create the 2 partitions. One for swap and the other for / (the root filesystem). Remember to make the root filesystem bootable.

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

    I have made a "sda1" at 8GB with the id type "82 Linux swap / Solaris" and a "sda2" with the rest of the space with the id type "83 Linux" and bootable. Remember to "write" before you quit cfdisk.

  6. Format the root partition using LUKS:

    # cryptsetup luksFormat --type luks1 /dev/sda2

    Warning: cryptsetup currently defaults to v2 of the LUKS header. Until GRUB version 2.06 is released GRUB only supports LUKS1. Make sure to specify --type luks1 when creating the encrypted partition using cryptsetup luksFormat. See bug 55093 for details.

  7. Open the newly formatted LUKS partition:

    # cryptsetup luksOpen /dev/sda2 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. Bootstrap the system (ignore any complaints from dracut):

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

    Answer yes to import the key and install the packages.

  11. Chroot into the newly created system and set it up:

    # 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 the rc.conf. Don't set the hostname in it.

    # vi /etc/rc.conf
    TIMEZONE="Europe/Copenhagen"
    KEYMAP="dk"
  13. Create the decryption keyfile:

    Let's use a persistent block device naming. I prefer the UUID.

    # lsblk -f
    sda                                                                                          
    ├─sda1
    └─sda2        crypto_LUKS 1              a3269d46-cf1b-46da-89bb-ec4ee3007432                
      └─cryptroot ext4        1.0            9bbca4ad-9942-452f-9c8d-e042818398eb

    It's the UUID that is listed besides the "sda2 crypto_LUKS 1" part we need to use, not the one for the cryptroot.

    # 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:

    It is a really bad idea to use the kernel's simple naming for a swap device, because the naming order (e.g. /dev/sda, /dev/sdb) changes upon each boot. The best option in IMHO is to use the UUID, but by default that does not work because dm-crypt and mkswap would simply overwrite any content on that partition which would remove the UUID, that goes for LABELs too.

    However, it is possible to specify a swap offset. This allows us to create a very small, empty, filesystem with no other purpose than providing a persistent UUID for the swap encryption.

    Create a filesystem:

    # mkfs.ext2 /dev/sda1 1M

    The parameter after the device name limits the filesystem size to 1 MiB, leaving room for encrypted swap behind it. Now we have a UUID we can use for the swap partition, we just have to remember to use the offset option in crypttab.

    In order to make it a bit easier to get the UUID's for crypttab we can use lsblk in combination with grep and then output the result to crypttab. Afterwards we open crypttab and edit the content.

    # lsblk -f | grep sda >> /etc/crypttab
    # vi /etc/crypttab
    swap UUID=fedd0941-f63a-4343-9446-088546843791 /dev/urandom swap,offset=2048,cipher=aes-cbc-essiv:sha256,size=256
    cryptroot UUID=a3269d46-cf1b-46da-89bb-ec4ee3007432 /crypto_keyfile.bin luks
  15. Setup dracut:

    # vi /etc/dracut.conf.d/10-crypt.conf
    install_items+="/crypto_keyfile.bin /etc/crypttab"
  16. Enable encryption support in GRUB:

    Again I like to pipe the output of lsblk in order to get the UUID.

    # lsblk -f | grep LUKS >> /etc/default/grub
    # vi /etc/default/grub

    Edit lines to match:

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

    If you're using a SSD disk you need to add "allow-discards" in order to enable TRIM support:

    GRUB_CMDLINE_LINUX="cryptdevice=UUID=a3269d46-cf1b-46da-89bb-ec4ee3007432:cryptroot rd.luks.allow-discards"

    Add "rd.auto=1" to the default command line (this isn't needed on Arch or Debian because they are not using dracut).

    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
  17. Install GRUB to the disk, generate the configuration file, and setup the kernel hooks (ignore grup complaints about sdc or similar):

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

    Check the kernel version by looking in /boot:

    # ls /boot
    ...
    vmlinuz-5.4.24_1

    Then setup the kernel hook:

    # xbps-reconfigure -f linux5.4
  18. Time to reboot:

    # exit
    # reboot
  19. After login verify that the encryptet swap partition is mapped correctly:

    # ls -l /dev/mapper/
    ...
    swap -> ../dm1
  20. Enable the swap:

    # swapon /dev/mapper/swap

    You can verify a last time with:

    # free
  21. Update fstab:

    # vi /etc/fstab
    /dev/mapper/swap swap swap defaults 0 0

    You can also verify that TRIM support is enabled with:

    # dmsetup table

    You should see allow_discards if it is enabled.

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

    First I setup the network using dhcpcd.

    # 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. 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:

    # 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/services/.

That's it.

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

Comments

Thank you William Skeith for emailing me about the "rd.luks.allow-discards" part!