Gord Stephen

Bits and things

Full disk encryption on Alpine Linux

After several years of desktop-only computing for personal use, I recently acquired a new-to-me laptop and figured it was time I finally figured out what the deal was with full-drive encryption on Linux.

The Alpine Linux wiki includes a page stepping through an “LVM on LUKS” install, but my partitioning needs are pretty simple - I usually just use a catch-all root partition with the EFI system partition mounted at /boot/efi. The more general Alpine wiki page on setting up disks manually mentions that you can set up an encrypted LUKS partition and just pass the mapped mountpoint into setup-disk, but that will give you the default Alpine partitioning scheme, with seperate root, boot, and swap partitions.

What follows is the final sequence of steps I took to achieve the simpler partition scheme I wanted (with UEFI and GPT). Most of this is adapted from the aforementioned LVM on LUKS guide, but there were enough differences/simplifications/corrections involved that I figured it was worth writing them down. You may want to consult that page as well for additional information. Know that this is mainly the result of combining fragments from the Alpine and Arch wikis, plus some educated guessing, and lots of trial-and-error - I’m not an expert in any of this. You results may vary…

Basic Setup

To start, manually run the various Alpine setup scripts and rc commands:

# setup-keymap
# setup-hostname
# setup-interfaces
# rc-service networking start
# passwd
# setup-timezone
# rc-update add networking boot
# rc-update add urandom boot
# rc-update add acpid default
# rc-service acpid start

Edit /etc/hosts appropriately:

127.0.0.1   <hostname> <hostname>.localdomain localhost localhost.localdomain
::1     <hostname> <hostname>.localdomain localhost localhost.localdomain

Run some more setup scripts:

# setup-ntp
# setup-apkrepos
# apk update
# setup-sshd

Partitioning, Encryption, and Formatting

At this point you should have a functional internet connection and package database, which will let you install some additional packages to perform the disk partitioning and encryption:

# apk add util-linux cryptsetup e2fsprogs dosfstools parted mkinitfs

util-linux gives you the lsblk command which you can use to figure out the name of the storage device you want to install to. Here we’ll assume it’s /dev/sda, and that your desired partition scheme looks like:

Partition             Filesystem  Note
============================================================
 /dev/sda1             fat32       EFI system partition
 /dev/sda2             LUKS        LUKS container
  ↳ /dev/mapper/crypt  ext4        Encrypted root partition

Nice and simple :) Let’s create the two partitions on /dev/sda, using parted:

# parted -a optimal /dev/sda
(parted) mklabel gpt
(parted) mkpart primary fat32 0% 200M
(parted) name 1 esp
(parted) set 1 esp on
(parted) mkpart primary ext4 200M 100%
(parted) name 2 crypto-luks

Now we can set up encryption on our newly-created /dev/sda2 partition. Note that with LUKS2, cryptsetup defaults to using the argon2id PBKDF, which doesn’t seem to work with GRUB. So we need to manually specify pbkdf2 when formatting the partition:

# cryptsetup --pbkdf pbkdf2 luksFormat /dev/sda2

We can now open, format, and mount our newly-encrypted partition:

# cryptsetup luksOpen /dev/sda2 crypt
# mkfs.ext4 /dev/mapper/crypt
# mount -t ext4 /dev/mapper/crypt /mnt/

We can also format and mount the EFI system partition:

# mkfs.fat -F32 /dev/sda1
# mkdir -p /mnt/boot/efi
# mount -t vfat /dev/sda1 /mnt/boot/efi

lsblk -f is handy for checking your work.

At this point we’re ready to install Alpine Linux to our mounted partitions:

# setup-disk -m sys /mnt/

Congratulations, Alpine Linux is now installed! Of course, that doesn’t mean it will boot yet…

Bootloader, initial RAM disk, and decryption

It’s worth noting that when we boot, our encrypted partition will need to be decrypted twice: once for GRUB to access the kernel and initramfs, and a second time to actually launch the OS. Providing the encryption password twice is a bit of a pain, so instead we can define an alternate decryption keyfile for the partition, which can be stored in the initramfs (only accessible after the initial decryption) and used to decrypt the drive the second time.

Obviously, to do this you need to generate the keyfile before you create the initramfs. (For some reason the Alpine wiki page covers these topics in the opposite order…)

To create the file and use it as a decryption key:

# touch /mnt/crypto_keyfile.bin
# chmod 600 /mnt/crypto_keyfile.bin
# dd bs=512 count=4 if=/dev/urandom of=/mnt/crypto_keyfile.bin
# cryptsetup luksAddKey /dev/sda2 /mnt/crypto_keyfile.bin

Now we need to configure the initramfs to do decryption and use the keyfile. This is done by editing /mnt/etc/mkinitfs/mkinitfs.conf and appending cryptsetup and cryptkey to the features parameter. The Alpine wiki mentions some other modules (kms, etc) you may need to add as well.

With the keyfile in place and the configuration set up to use it, we can regenerate the initial RAM disk:

# mkinitfs -c /mnt/etc/mkinitfs/mkinitfs.conf -b /mnt/ $(ls /mnt/lib/modules/)

If you want, you can inspect the contents of the initramfs file to confirm that it contains the keyfile:

zcat /mnt/boot/initramfs-lts | cpio -t | less

Next we need to configure the bootloader. The wiki provides a neat tip for writing the encrypted partition UUID into a file that you can easily read into vi later:

blkid -s UUID -o value /dev/sda2 > /mnt/root/uuid

At this point we’re almost ready to chroot into our new filesystem, which is always exciting. First, mount a few more devices:

# mount -t proc /proc /mnt/proc
# mount --rbind /dev /mnt/dev
# mount --make-rslave /mnt/dev
# mount --rbind /sys /mnt/sys

Here we go! The wiki also suggests changing the prompt to make the chroot environment more explicit:

# chroot /mnt
# source /etc/profile
# export PS1="(chroot) $PS1"

Now we can install GRUB in our new filesystem, and remove syslinux if it’s there:

(chroot) # apk add grub grub-efi efibootmgr
(chroot) # apk del syslinux

We’re ready to start configuring GRUB. First we edit /etc/default/grub to make sure the following are provided in the GRUB_CMDLINE_LINUX_DEFAULT parameter:

cryptroot=UUID=<UUID> cryptdm=crypt cryptkey

<UUID> is the UUID of the encrypted partition, which you can insert into the file with the :r /root/uuid command in vi if you wrote it to a file as discussed above.

In that same file, add the following additional parameters:

GRUB_PRELOAD_MODULES="luks cryptodisk part_gpt"
GRUB_ENABLE_CRYPTODISK=y

Next, create /root/grub-pre.cfg and populate it with:

set crypto_uuid=<UUID>
cryptomount -u $crypto_uuid
set root='crypto0'
set prefix=($root)/boot/grub
insmod normal
normal

Here, <UUID> is the encrypted partition’s UUID again, but with the hyphens removed this time.

We’re almost done now. At this point it’s worth checking your /boot/efi mount point to see if the Alpine installation put anything there - in my case I had existing GRUB images in EFI/boot and EFI/alpine. You could delete or move these to avoid confusion, since we’re about to re-install GRUB (to EFI/grub) with the new configuration applied.

Speaking of which… Let’s do a vanilla grub-install first, which will create the necessary ancillary files in /boot and configure an EFI boot variable. Then we’ll install our customized GRUB image and config file:

(chroot) # grub-install --target=x86_64-efi --efi-directory=/boot/efi
(chroot) # grub-mkimage -p /boot/grub -O x86_64-efi -c /root/grub-pre.cfg \
                        -o /tmp/grubx64.efi luks2 part_gpt cryptodisk \
                           ext2 gcry_rijndael pbkdf2 gcry_sha256
(chroot) # install -v /tmp/grubx64.efi /boot/efi/EFI/grub/
(chroot) # grub-mkconfig -o /boot/grub/grub.cfg

To finish, exit the chroot and do some final cleanup:

# umount -l /mnt/dev
# umount -l /mnt/proc
# umount -l /mnt/sys
# umount /mnt/boot/efi
# umount /mnt
# cryptsetup luksClose crypt

Now, brave soul, you can reboot and see if it all worked.

If all goes well you should get an early password prompt from GRUB, before it proceeds to its boot menu and, if the second keyfile decryption works, the usual Linux startup process and login prompt.

If it doesn’t work, there’s a good chance the problem is somewhere in the custom initramfs or bootloader configuration. You can repeat the “Basic Setup” steps above to fully initialize the installer, decrypt /dev/sda2 and re-mount everything, and chroot into the filesystem to investigate.

A few things that tripped me up, in case they’re helpful for you:

  • The default cryptsetup luksFormat command doesn’t use pbkdf2
  • If you can’t get to the GRUB password prompt - you may not have your EFI boot variables for GRUB set correctly (or at all)
  • If you get prompted for a password twice, despite the keyfile: the initramfs may not be properly configured, or may not contain the keyfile

While it took me a few tries to get everything working, my hope is that the outline above will make it a bit easier for you - good luck!