Real full disk encryption using GRUB on Arch Linux for BIOS and UEFI

Posted on 2017-05-30. Last updated on 2018-12-24.
In this tutorial we're gonna take a look at setting up full disk encryption on Arch Linux using GRUB for both a BIOS/MBR based setup and a UEFI based setup. While the choice to install in UEFI mode is encouraging, early vendor UEFI implementations carry more bugs than their BIOS/MBR counterparts. You are advised to do a search relating to your particular motherboard model before proceeding. Contrary to "modern" advice I haven't personally found any compelling reason to use UEFI.

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.

It is very difficult to prevent tampering with hardware components if you leave your computer out-of-sight, however if you use a BIOS based setup you can dump your MBR and take a look at it with a hexedecimal editor and compare it to an old secure dump. If you use UEFI you can use a custom signature key and Secure Boot (if your motherboard supports it), or you can boot from another medium, such as a CDROM or a USB stick.

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.

Before we begin take a look at the Arch Linux wiki page about UEFI.

For BIOS 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)

For UEFI we'll add yet another partition to hold the GRUB UEFI file:

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

Important: Keep a note of how your setup will look like once you get started. In this tutorial I am using "sdX" to represent the hard drive and "sdXY" to represent different partitions. You need to keep track of which is what.

Both the swap and the normal partition will be fully encrypted with LUKS.

If you have plenty of memory you can omit the swap partition if you want.

In this example we'll use EXT4 as the filesystem, but you can change it 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 Arch Linux install medium.

  2. Verify your network using ip a and ping archlinux.org.

  3. Setup your keyboard:

    # loadkeys KEYMAP

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

    # localectl list-keymaps

    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.

    Also 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 console without X Window System running.

  4. Locate your harddrive:

    # fdisk -l
  5. 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.

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

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

    # cfdisk /dev/sdX

    Create the partitions.

    Note: If you're going to use UEFI then the general recommendation is 500 MB or more for your UEFI partition. Please also note that the GUID Partition Table is mandatory for UEFI.

    Note: Remember to make the root filesystem bootable if you're using a BIOS/MBR setup.

  6. Format the root partition using LUKS:

    # cryptsetup luksFormat /dev/sdXY
  7. Open the newly formatted LUKS partition:

    # cryptsetup open /dev/sdXY cryptroot

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

  8. Install the filesystem of your choice on the encrypted root partition (EXT4, XFS, Btrfs, etc.) In this case we're going to use EXT4.

    # mkfs.ext4 /dev/mapper/cryptroot

    If you're using UEFI you need to format the UEFI parition using the Fat filesystem.

    # mkfs.vfat -F32 /dev/sdXY
  9. Mount the root filesystem:

    # mount /dev/mapper/cryptroot /mnt
  10. Select the Pacman mirrors by placing the ones you prefer in the top:

    # vi /etc/pacman.d/mirrorlist
  11. Bootstrap the system:

    # pacstrap /mnt base grub intel-ucode efibootmgr (only install "efibootmgr" if you use an UEFI setup!)
    

    Note: Use amd-ucode instead of intel-ucode for an AMD CPU.

  12. Generate and verify fstab:

    # genfstab -U /mnt >> /mnt/etc/fstab
    # cat /mnt/etc/fstab

    If you're using an SSD disk you need to add "discard" in order to enable continues TRIM support. It will then look something like this:

    # /dev/mapper/cryptroot
    UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx / ext4 rw,relatime,discard 0 1

    Currently the swap partition isn't setup. We won't bother with that yet.

  13. chroot into the newly created system and set the basic stuff up:

    # arch-chroot /mnt
    # echo KEYMAP=dk > /etc/vconsole.conf
    # ln -sf /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime
    # hwclock --systohc
    # vi /etc/locale.gen (uncomment your locales)
    # locale-gen
    # echo LANG=en_US.UTF-8 > /etc/locale.conf
    # echo my-hostname > /etc/hostname
  14. Create the keyfile (in order to avoid having to type the encryption password twice):

    # dd bs=512 count=4 if=/dev/urandom of=/crypto_keyfile.bin
    # cryptsetup luksAddKey /dev/sdXY /crypto_keyfile.bin
    # chmod 000 /crypto_keyfile.bin
  15. Add encrypt to the mkinitcpio HOOKS and the keyfile to the files section:

    # vi /etc/mkinitcpio.conf
    FILES=(/crypto_keyfile.bin)
    HOOKS=(base udev autodetect modconf block encrypt filesystems keyboard fsck)
  16. Create the initial ramdisk environment:

    # mkinitcpio -p linux
  17. Set the root password:

    # passwd
  18. Enable cryptdisk support in GRUB:

    # vi /etc/default/grub

    Add the following line:

    GRUB_CMDLINE_LINUX="cryptdevice=/dev/sdXY:cryptroot"

    And uncomment:

    GRUB_ENABLE_CRYPTODISK=y

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

    GRUB_CMDLINE_LINUX="cryptdevice=/dev/sdXY:cryptroot:allow-discards"
  19. Install GRUB and reboot:

    For a BIOS setup

    # grub-install --target=i386-pc /dev/sdX --recheck

    For an UEFI setup

    # mkdir /boot/efi
    # mount /dev/sdXY /boot/efi (mount the UEFI partition you created)
    # grub-install --target=x86_64-efi /dev/sdX --recheck

    Warning: As mentioned in the abstract, motherboard vendors implement UEFI differently and for some the way GRUB stores its EFI file isn't working. In this setup I am running with a MSI board and I have to rename the file and the path once GRUB is done. Mine has to look like this:

    # cd /boot/efi
    # tree
    .
    └── EFI
        └── boot
            └── bootx64.efi

    The setup varies from vendor to vendor, but you can go ahead and test with your board, if the default isn't working you can always just boot up on the Arch Linux install medium and then mount the UEFI partition and then rename the directory and GRUB UEFI file.

  20. Generate grub.cfg:

    # grub-mkconfig -o /boot/grub/grub.cfg
  21. Update crypttab with the relevant swap information (if you use a swap partition):

    # vi /etc/crypttab
    swap /dev/sdXY /dev/urandom swap,cipher=aes-cbc-essiv:sha256,size=256
  22. Reboot the system now. After login verify that the encrypted swap partition is mapped correctly:

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

    # swapon /dev/mapper/swap

    You can verify a last time with:

    # free
  24. Update fstab:

    # vi /etc/fstab
    # /dev/mapper/cryptroot
    UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx / ext4 rw,relatime 0 1
    /dev/mapper/swap swap swap defaults,noatime 0 0

    Remember to add the "discard" option if you're using an SSD disk:

    # vi /etc/fstab
    # /dev/mapper/cryptroot
    UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx / ext4 rw,relatime,discard 0 1
    /dev/mapper/swap swap swap defaults,noatime,discard 0 0
  25. Now you can setup the network and install additional users and packages:

    # useradd --create-home foo
    # passwd foo

    I prefer to run with systemd-networkd, so I'll set that up.

    # vi /etc/systemd/network/wired.network
    [Match]
    Name=enp*
    
    [Network]
    DHCP=yes

    In case you don't want to use the DNS servers provided by the DHCP server you can disable that and provide you own:

    [DHCP]
    UseDNS=no

    Then:

    # systemctl enable systemd-networkd.service
    # systemctl start systemd-networkd.service
    # echo "nameserver 0.0.0.0" > /etc/resolv.conf (use whatever nameserver you then want to use instead of 0.0.0.0)

That's it.

Feel free to email me any suggestions or comments.