Real full disk encryption using GRUB on Void Linux for BIOS

Posted on 2018-03-30.
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 /dev/sda2
  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 -r /mnt base-system cryptsetup grub
  11. Chroot into the newly created system and set stuff 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
  13. Create the decryption keyfile:

    # dd bs=512 count=4 if=/dev/urandom of=/crypto_keyfile.bin
    # cryptsetup luksAddKey /dev/sda2 /crypto_keyfile.bin
    # chmod 000 /crypto_keyfile.bin
    # chmod -R g-rwx,o-rwx /boot
  14. Setup crypttab:

    # vi /etc/crypttab
    swap /dev/sda1 /dev/urandom swap,cipher=aes-cbc-essiv:sha256,size=256
    cryptroot /dev/sda2 /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:

    # vi /etc/default/grub

    Add the following lines:


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


    Add "" 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"

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

  17. Install GRUB to the disk, generate the configuration file, and setup the kernel hooks (ignore grup complaints about sdc or similar):

    # grub-install --target=i386-pc /dev/sda
    # grub-mkconfig -o /boot/grub/grub.cfg
    # xbps-reconfigure -f linux4.15

    You can check the kernel version by looking in /boot:

    # ls /boot
  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/cryptroot / ext4 defaults 0 0
    /dev/mapper/swap swap swap defaults 0 0

    If you prefer to use UUIDs you can use the "blkid" command to get them. Remember to also add "noatime" and "discard" if you're using an SSD.

  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:

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

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

That's it!

Feel free to email me any suggestions, correction, or comments.


From William Skeith

I think there is one mistake regarding enabling TRIM support: the grub command line you gave should be updated as follows (diff-ish format):

- GRUB_CMDLINE_LINUX="cryptdevice=/dev/sda2:cryptroot:allow-discards"
+ GRUB_CMDLINE_LINUX="cryptdevice=/dev/sda2:cryptroot rd.luks.allow-discards"

With the former, TRIM was not enabled according to dmsetup table, but it is with the latter.

I set up the partition table (GPT) as follows:

Device         Start       End   Sectors   Size Type
/dev/sda1       2048      6143      4096     2M BIOS boot
/dev/sda2        ...       ...       ...     8G Linux swap
/dev/sda3        ...       ...       ...   ...G Linux filesystem

The first partition is only used by grub and will never be mounted by the OS. I also set the "bootable" flag in the protective MBR as follows:

# fdisk /dev/sda

Now run the following commands in fdisk:

That's it! Your guide could be followed identically (modulo sda2 -->sda3) from that point onward. Lastly, I'm not sure setting the legacy boot flag was necessary, but I heard it was a good idea for some BIOSes. In case it matters, this worked for me on coreboot/SeaBIOS.

One other minor suggestion, regarding /crypto_keyfile.bin: on one hand, 2048 bytes seems like overkill to protect what will likely be a 256 bit AES key. However, /dev/urandom is pseudorandom and determined by a much smaller seed. Given that this process will likely be run from an installation disk, a more cautious approach might be to initialize from /dev/random as follows:

Throw some of your own entropy into the pool:

# echo '[manually type a long random string here...]' > /dev/random

Make sure the read won't block (above does not increase counter, btw):

# cat /proc/sys/kernel/random/entropy_avail # make sure >= 1024

Generate 1024 bit key:

# dd bs=128 count=1 if=/dev/random of=/crypto_keyfile.bin

If prior random seed was saved, it will be publicly known, leaving the initial state of the urandom PRG potentially predictable.

I'm an academic cryptographer, and not really an expert on practical stuff, but FWIW I am paranoid and the steps above are the ones I took. : )