Arch Linux - Install Encrypted Guide

The idea of this guide is to achieve the following:

This will be an exercise in how to set this up. Apparently there may be issues setting BTRFS on a LUKS2 encrypted partition. We shall see. grub currently does not have proper LUKS2, currently waiting for the patches to be merged. Until then root will use LUKS1 as it is supported.

I thought about exploring doing Secure boot and/or encrypted boot to make sure that the PC was as secure as possible.

This should allow for a pretty fancy and bulletproof computer, aside from the vulnerability present in all modern computers of course.

Summary of security on a computer

Steps in this process from a high level

This tutorial is based on: https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system#Btrfs_subvolumes_with_swap

It has been modified to use a swap file instead of a swap partition and to allow for multiple disks, among some other minor adjustments for my preferences.

Configure wifi

Use the iwctl tool:

iwctl

# You will be presented with the following prompt:
[iwd]#

# To get name of wireless adapter
station list

# Scan for networks
station <adapter e.g. wlan0> scan
station wlan0 get-networks

# Connect to network
station wlan0 connect <network name>

# Exit back to shell prompt
exit

# Confirm connection
ping -c 5 archlinux.org

Connect over ssh

If wanting to install from another system.

# Set root password and reload ssh daemon
passwd
systemctl reload sshd

# Get ip address
ip add

On the other system ssh in normally.

Wipe drives

This step is only necessary if the drive wasn’t encrypted before. If it was encrypted, it would be indistinguishable from garbage data anyway. If the drives are over a terabyte it may take several hours.

shred -v -n 1 /dev/sda
shred -v -n 1 /dev/sdb

Partition the disks

Recommended to use gdisk, however it is difficult if reorganising an existing partition. cfdisk is a nice TUI tool that makes it easier although is not GPT aware as far as I know. After formatting may need to reboot before the changes are recognised by the system. My installation refused to do the luksFormat step because it believed the partitions were still in use.

Disk layout

For SSD which is /dev/sdb:

For HDD which is /dev/sda:

Encryption of devices

# Encrypt drives
cryptsetup luksFormat --type luks1 /dev/sda1 # HDD used for home partition
# Confirm
# Passphrase
# Passphrase again
cryptsetup luksFormat --type luks1 /dev/sdb2 # SSD root partition

# Generate a 2048 byte key file to be used to encrypt the data
dd bs=512 count=4 if=/dev/random of=/tmp/key.bin iflag=fullblock
chmod 600 /tmp/key.bin # So only root can read it

# Add keyfile
cryptsetup luksAddKey /dev/sda1 /tmp/key.bin
cryptsetup luksAddKey /dev/sdb2 /tmp/key.bin

# Verify the LUKS header
# If a key was added there should be two keys in the keyslots
cryptsetup luksDump /dev/sda1
cryptsetup luksDump /dev/sdb2

Unlock LUKS containers

Decrypt the drives and make it accessible.

# --allow-discards is for SSD devices
cryptsetup luksOpen /dev/sdb2 root --key-file /tmp/key.bin --allow-discards
cryptsetup luksOpen /dev/sda1 home --key-file /tmp/key.bin

The decrypted drives will now be accessible through /dev/mapper/root and /dev/mapper/home.

Note: When using an SSD the cryptsetup luksOpen command should have the option --allow-discards to allow TRIM support.

Formatting the decrypted drives

mkfs.vfat -F32 /dev/sda1 # unencrypted ESP
mkfs.btrfs -L root /dev/mapper/root
mkfs.btrfs -L home /dev/mapper/home

Check filesystem formats

lsblk -f

Create btrfs sub-volumes

# Mount the partition so that it can be used
mount /dev/mapper/root /mnt

# Create the sub-volumes
btrfs subvolume create /mnt/{@,@var,@snapshots,@swap}

# Unmount the partition so that the sub-volumes may now be mounted instead
umount /mnt

# Do the same for the device containing home
mount /dev/mapper/home /mnt
btrfs sub-volume create /mnt/@home
umount /mnt

Mount filesystem

# Mount the sub-volumes with options specified, also create folders for sub-volumes to be mounted to
mount -o noatime,compress=zstd,ssd,discard=async,space_cache=v2,subvol=/@ /dev/mapper/root /mnt
mkdir -p /mnt/{efi,boot,home,var,swap,.snapshots}
mkdir -p /mnt/.snapshots
mount -o noatime,compress=zstd,ssd,discard=async,space_cache=v2,subvol=/@var /dev/mapper/root /mnt/var
mount -o noatime,compress=zstd,ssd,discard=async,space_cache=v2,subvol=/@snapshots /dev/mapper/root /mnt/.snapshots
mount -o noatime,nodatacow,compress=no,ssd,discard=async,space_cache=v2,subvol=/@swap /dev/mapper/root /mnt/swap

mount -o noatime,compress=zstd,ssd,discard=async,space_cache=v2,subvol=/@home /dev/mapper/home /mnt/home

# Mount ESP
mount /dev/sda1 /mnt/efi

For more information on the options see here.

Copy key file to root partition

Copy the key file generated in the encryption step. The root directory will be used to hold the key used to encrypt the drive. According to here /crypto_keyfile.bin is the default place that the encrypt kernel hook uses.

cp /tmp/key.bin /mnt/crypto_keyfile.bin

Pacstrap the installation

Use the following command to install base packages to the system:

pacstrap /mnt base base-devel linux linux-firmware linux-headers grub efibootmgr intel-ucode btrfs-progs os-prober reflector net-tools networkmanager wpa_supplicant dialog neovim xdg-utils xdg-user-dirs man-db bc git curl openssh

Generating the fstab

The fstab file defines how disk partitions and other devices should be mounted to the system. To generate one for the mounted device structure:

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

Check the fstab to see if it set the mount options correctly.

Add home partition to crypttab

Similar to fstab which sets up the mounted partitions, crypttab decrypts devices at boot up and runs before fstab. This needs to be done for the home partition as it is not decrypted automatically. Since root is decrypted automatically due to the encrypt hook in the initramfs it does not need to be added.

Grab the UUID of the LUKS partition using this handy command, might need to adjust it for you case:

blkid | grep sda1 | cut -d\" -f 2

Edit /etc/crypttab and add the partition UUID like so:

# <name>       <device>                                     <password>              <options>
home    UUID=6ac7f2de-d94a-4686-baa0-9d10f289f690       /crypto_keyfile.bin

Chroot into the new system

This step will quickly go through the initial setup steps from the new system:

arch-chroot /mnt

timedatectl list-timezones # to find your timezone
ln -sf /usr/share/zoneinfo/REGION/CITY /etc/localtime

hwclock --systohc

nvim /etc/locale.gen # Uncomment en_US.UTF-8
locale-gen # Generate locales
nvim /etc/locale.conf # Add LANG=en_US.UTF-8
nvim /etc/hostname # Set to arch-computer or whatever
nvim /etc/hosts # Add the following below:

# Static table lookup for hostnames.
# See hosts(5) for details.

127.0.0.1        localhost
::1              localhost
127.0.1.1        arch-computer.localdomain        arch-computer

Edit mkinitcpio.conf

Configure initramfs with the following options by editing /etc/mkinitcpio.conf: - add to MODULES, btrfs (if you have an intel processor) - add to BINARIES, btrfs - for recovery options, if it fails boot the btrfs check tool will be available in the command line that is presented - add to FILES, /crypto_keyfile.bin - add to HOOKS, udev, keymap, consolefont and encrypt e.g. HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block encrypt lvm2 filesystems fsck)

Then regenerate with mkinitcpio -P or mkinitcpio -p linux.

Installing GRUB

Apparently there are issues with GRUB and LUKS2 support: https://wiki.archlinux.org/title/GRUB#Encrypted_/boot

Enable crypto disk option in GRUB config

Add the following options in /etc/default/grub:

GRUB_ENABLE_CRYPTODISK=y
GRUB_DISABLE_OS_PROBER=false

Grab the UUID of the encrypted root using lsblk -f. Edit the GRUB_CMDLINE_LINUX_DEFAULT variable and add the following kernel parameters, (note: each newline should be a space, it is a newline for readability):

mem_sleep_default=deep
cryptdevice=UUID=b35bae6b-8c18-452d-9990-b4d4cf68fd38:root:allow-discards
root=/dev/mapper/root
cryptkey=rootfs:/crypto_keyfile.bin

Note: the cryptkey option doesn’t need to be specified as it by default looks for this file but it’s better to be explicit rather than implicit.

Install GRUB

grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB

Generate GRUB config

grub-mkconfig -o /boot/grub/grub.cfg

Enable systemd services

systemctl enable NetworkManager
systemctl enable fstrim.timer

# Configure reflector to your liking
nvim /etc/xdg/reflector/reflector.conf
systemctl enable reflector.timer

# To trigger reflector to pick the closest mirrors manually
systemctl start reflector

Set a root password

passwd

Unmount and Reboot

# exit chroot
exit
umount -R /mnt
reboot

Reboot and a password screen should be presented.

Brand new system

With luck everything should work and you will be presented with a login prompt. If it hangs at Starting systemd-udevd version..., it most likely means that the home partition is not available as was the case with me the first time.

This means that crypttab needs to be configured to decrypt the device holding the home partition.

Fix “watchdog did not stop!” message

Edit /etc/systemd/system.conf, set the following variable RebootWatchdogSec=0

Create user

useradd -m USERNAME
usermod -aG wheel USERNAME
usermod -aG rfkill USERNAME
chfn -f "Full Name" USERNAME
passwd USERNAME

Allow superuser privileges

Edit /etc/sudoers with the visudo command, vim or nano will need to be installed, and uncomment the line at the bottom beginning with %wheel.

Logout and login as new user

This will prove the new user account is working as intended. Also try a command with sudo like sudo ls just to make sure it’s working.

Disable root login

Once a user account with super user privileges is created the root account is no longer necessary and presents a security risk.

passwd --lock root

Setup AUR package manager

cd /tmp
git clone https://aur.archlinux.org/paru.git
cd paru
makepkg -si

Install desktop environment

Install whatever desktop environment you like, this will install cinnamon:

sudo pacman -Syu cinnamon xorg-xinit xorg-xrandr arandr xreader firefox lightdm lightdm-slick-greeter nvidia nvidia-settings nvidia-utils pipewire pipewire-jack wireplumber noto-fonts noto-fonts-cjk noto-fonts-emoji gnome-keyring mtpfs gvfs gvfs-mtp ntfs-3g bluez bluez-utils blueberry arc-gtk-theme arc-icon-theme gnome-multi-writer grub-btrfs galculator archlinux-wallpaper mpv vlc breeze kolourpaint rsync libreoffice-fresh cronie gnome-system-monitor vim unzip htop
paru -S xviewer gnome-terminal-transparency gnome-icon-theme nerd-fonts-fantasque-sans-mono

Fix for cinnamon

mkdir -p $HOME/.cache/cs_themes/ # Fix for cinnamon themes application crashing
paru -S gnome-icon-theme # Fix for missing start menu icons

Configure NVIDIA drivers

Run the following python script to configure it automatically:

paru -S envycontrol
sudo envycontrol --switch nvidia --dm lightdm

Or follow this guide: https://www.youtube.com/watch?v=Pn2iUgW3l6w

Encrypted Swap File

Creating the swapfile

Enter the following commands to create the swap:

sudo su # Become root it you are not
cd /swap
touch ./swapfile # Create the swapfile

# Disable CoW, this should already be disabled in fstab
chattr +C ./swapfile

# Disable compression, again, this already should be disabled in fstab
# This line will fail if set in fstab
btrfs property set ./swapfile compression none

# Create swapfile same size as RAM to allow hibernation
dd if=/dev/zero of=./swapfile bs=1M count=8192 status=progress
chmod 600 ./swapfile # Only root should be able to read/write swap
mkswap ./swapfile
swapon /swap/swapfile # use swap in current session

Adding swap to fstab

Now add the following to /etc/fstab:

/swap/swapfile none swap defaults 0 0

This will configure the system to use the swapfile as swap on next boot.

Configuring hibernate

Editing mkinitcpio.conf

The system needs to know where to load the existing session that was saved to disk during the last hibernation. First the system needs to know that this is possible by having the appropriate system hook. To do so edit mkinitcpio.conf and add resume after filesystems and lvm2 in the HOOKS section:

HOOKS=(... encrypt filesystems resume ...)

Then regenerate the initramfs with mkinitcpio -P.

Calculating the offset on disk to load the swap file

The resume hook now needs to be configured with where the location of the system swap where the last session was written to disk.

This section is incredibly jank but is the recommended way to do this: https://wiki.archlinux.org/title/Power_management/Suspend_and_hibernate#Hibernation_into_swap_file_on_Btrfs I assume this is because there is no way in the resume variable of grub to point to a btrfs sub-volume or a file within the sub-volume, and so a memory offset to its location on disk is required.

While this seems risky it should be fine as the swapfile location shouldn’t change as CoW is disabled, meaning that it won’t shift around on disk.

Perform the following:

cd /tmp
curl -o btrfs_map_physical.c https://raw.githubusercontent.com/osandov/osandov-linux/master/scripts/btrfs_map_physical.c
gcc -O2 -o btrfs_map_physical btrfs_map_physical.c

# Need to grab the PHYSICAL OFFSET of the swapfile on disk
sudo ./btrfs_map_physical /swap/swapfile | cut -f 9 | head -2 | tail -1
getconf PAGESIZE

# Divide physical offset of swapfile by page size to get resume_offset value
echo "6773809152 / 4096" | bc
# 1653762

# Find UUID of device containing the swapfile, in this case the
# /dev/mapper/root device
lsblk -f | grep root

# Edit /etc/default/grub and add resume and resume_offset values to the
# GRUB_CMDLINE_LINUX_DEFAULT variable.
# e.g. resume=UUID=a8c33cc9-ec0f-4afb-be27-9080195b9b01
# e.g. resume_offset=1653762
sudo nvim /etc/default/grub

# Regenerate grub config
sudo grub-mkconfig -o /boot/grub/grub.cfg

Hibernate functionality will now work on next boot as the swapfile is now configured with the resume hook of the initramfs.

Configure snapper

Snapper is a tool for managing btrfs snapshots. So if something in the system gets corrupted it can be reverted. There are also a number of other packages that provide a GUI interface and also automatically create a snapshot pre and post system upgrades.

sudo pacman -S snapper
sudo systemctl enable snapper-timeline.timer # Enable automatic snapshots
sudo systemctl enable snapper-cleanup.timer
sudo umount /.snapshots # Need to delete .snapshot folder that was made
sudo rm -rf /.snapshots # as snapper creates it and errors if it exists
sudo snapper -c root create-config /
sudo chmod a+rx /.snapshots # Configure permissions
sudo mount -a # Remount everything is fstab

Edit the root configuration file that was just created and change the following: - Add your user to the ALLOW_USERS variable - Change the timeline variables to your preference

Then reboot.

sudo nvim /etc/snapper/configs/root
sudo reboot

Add additional snapper packages:

paru -S snap-pac snap-pac-grub grub-btrfs snapper-gui

Done

With snapper now configured, the system should now be a fully disk encrypted device using btrfs with compression and snapshots.

Have fun!