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

Published on 2021-02-10. Modified on 2022-07-18.

In this tutorial we're going to take a look at setting up full disk encryption on the Artix Linux base system using GRUB for both a BIOS/MBR based setup and a UEFI based setup. While the choice to install in UEFI mode is encouraging, vendor UEFI implementations still 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 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.

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

sdX1 (LUKS with EXT4)

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

sdX1 (GRUB UEFI)
sdX2 (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.

As of writing Artix Linux supports three different init systems, mainly OpenRC, runit and s6. I am most familiar with runit, so that is what I have decided to use, but you can use any of the other two if you want.

Let's get started.

  1. Boot the relevant Artix Linux base install medium, either "artix-base-openrc", "artix-base-runit" or "artix-base-s6".

  2. Once you have booted up and logged in, become root:

    $ su

    Then verify that your network is working:

    # ip a

    And try to ping out:

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

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

    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.

    Remember to make the root filesystem bootable and choose "dos" as the label type if you're using a BIOS/MBR setup.

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

  6. Format the root partition using LUKS.

    I am using the BIOS setup, so in my case it's the first and only partition on the device. In the rest of the tutorial you need to change that if you're using UEFI. If sdX1 is your boot partition, then you'll properly need to change my examples from sdX1 to sdX2 as that will be your root partition.

    # 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 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 I'm using EXT4.

    # mkfs.ext4 /dev/mapper/cryptroot

    Use the command lsblk -fp to list all the UUIDs of your system. Write down the one for /dev/mapper/cryptroot as you're going to use it more than once.

    If you're using UEFI you need to format the UEFI boot partition with the Fat filesystem.

    # mkfs.vfat -F32 /dev/sdXY

    Where sdXY is your UEFI boot partition.

  9. Mount the root filesystem:

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

    # vi /etc/pacman.d/mirrorlist
  11. Now we're going to install the base system. In this step you have to decide which init system you want.

    If you choose OpenRC:

    # basestrap /mnt base openrc elogind-openrc

    If you choose runit:

    # basestrap /mnt base runit elogind-runit

    If you choose s6:

    # basestrap /mnt base s6 elogind-s6
  12. Then we need to install a kernel and some microcode and firmware. In this setup I am using an Intel CPU, so I use the intel-ucode. If you run with an AMD CPU, you need amd-ucode. We also need to install the GRUP bootloader.

    Artix Linux provides three kernels linux, which is the vanilla Linux kernel and modules, with a few patches applied. linut-lts, which is long-term support (LTS) Linux kernel and modules. And linux-zen, which is a result of a collaborative effort of kernel hackers to provide what they consider the best Linux kernel possible for everyday systems.

    If you use an UEFI setup you also need to add the efibootmgr package to the following command:

    # basestrap /mnt grub linux linux-firmware intel-ucode
  13. Generate and verify fstab (use -U for UUID and -L for partition labels):

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

    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 want to enable continues TRIM in fstab, you need to add the discard option:

    # /dev/mapper/cryptroot
    UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx / ext4 rw,relatime,discard 0 1
  14. Now, chroot into the newly created system and set the basic stuff up (change the zoneinfo to your location):

    # artix-chroot /mnt
    # echo KEYMAP=dk > /etc/vconsole.conf
    # ln -sf /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime
    # hwclock --systohc

    Before we continue we need to install a text editor and cryptsetup. I prefer to use Vim or Neovim, so I will install that.

    # pacman -S vim cryptsetup

    Then we can proceed with the setup. First uncomment your locales:

    # vim /etc/locale.gen

    Then generate the locales and setup your hostname:

    # locale-gen
    # echo LANG=en_US.UTF-8 > /etc/locale.conf
    # echo my-hostname > /etc/hostname
  15. 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/disk/by-uuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /crypto_keyfile.bin
    # chmod 000 /crypto_keyfile.bin
  16. Add encrypt to the mkinitcpio HOOKS and the keyfile to the files section:

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

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

    # passwd
  19. Enable cryptdisk support in GRUB:

    # vim /etc/default/grub

    Add the following line:

    GRUB_CMDLINE_LINUX="cryptdevice=UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:cryptroot"

    Where the "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" matches the UUID of the partition you unencrypt when you use the cryptsetup command. Use the command ls -l /dev/disk/by-uuid to verify that you are using the correct UUID for sdXY and not the dm-0 device.

    In my specific case it is the UUID that macthes sda2:

    # ls -l /dev/disk/by-uuid
    9bbca4ad-9942-452f-9c8d-e042818398eb -> ../../dm-0 a3269d46-cf1b-46da-89bb-ec4ee3007432 -> ../../sda2

    Then uncomment:

    GRUB_ENABLE_CRYPTODISK=y

    If you're using an SSD disk and you have enabled continues TRIM you need to add "allow-discards" to that:

    GRUB_CMDLINE_LINUX="cryptdevice=UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:cryptroot:allow-discards"
  20. Install GRUB and reboot:

    For a BIOS setup

    # grub-install /dev/sdX --recheck

    For an UEFI setup

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

    As mentioned, motherboard vendors implement UEFI differently and for some the way GRUB stores its EFI file isn't working. In one previous setup of mine I was running with a MSI motherboard and I had to rename the file and the path once GRUB was done. The one I had looked 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 Artix Linux install medium and then mount the UEFI partition and then rename the directory and GRUB UEFI file.

  21. Generate grub.cfg:

    # grub-mkconfig -o /boot/grub/grub.cfg
  22. Now you can setup the network and install additional users and packages:

    # useradd --create-home foo
    # passwd foo

    I prefer to run with dhcpcd so I'll set that up.

    With Artix Linux each service file is installed separately with the package name and the init system name at the end. E.g. dhcpcd-runit or dhcpcd-openrc.

    # pacman -S dhcpcd dhcpcd-runit
    # vim /etc/dhcpcd.conf (what ever changes you may need to make)

    Don't try to enable any services until you have rebooted (unless you're using OpenRC). It's easier as runit and s6 rely on a /run (a tmpfs) directory to be created.

    So we'll just reboot the system now:

    # exit
    # reboot
    
  23. You should now be presented with the GRUB prompt for the password. Once the system is finished booting, log in.

    Continue the setup by enabling dhcpcd as a service (I am using runit, but look up how to do this with the other init systems on the Artix Linux wiki):

    # ln -s /etc/runit/sv/dhcpcd/ /run/runit/service/

    Then check the status with:

    # sv status dhcpcd
  24. Now it's time to add 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
    
  25. 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