Ad:a

Encrypt an existing Debian GNU/Linux installation with LUKS

Friday, August 6, 2010 Debian Encryption GRUB LUKS LVM Flattr Encrypt an existing Debian GNU/Linux installation with LUKS

In the following text I‘ll provide an up-to-date description of how to setup a completely encrypted Debian, using the unstable Debian distribution from August 2010.

Motivation

Whether you want to encrypt your system for personal, political or economic reasons, a fully encrypted system provides an excellent protection for data – together with encrypted communication (HTTPS for web traffic, OTR for instant messaging, GPG for e-mail and instant messaging), which is quite common nowadays.

Encrypting only the sensitive data itself may seem like a sufficient approach, but it has some limitations and problems:

Technology

The current state of the art for disk encryption under Linux is LUKS as implemented by cryptsetup using dm-crypt. It‘s free software, included in Debian and other distributions, encrypts securely and runs transparently, stable and fast (Actually, access to encrypted devices is not really slower).

GRUB 2 is stable and default since Debian Squeeze. It works fine with LUKS.

Setting

I want to encrypt the Debian installation on my netbook. I have the following partitions on my 150 GB hard disk:

This is quite an unusual partition layout, but it somehow got that way. What I want is the following:

Using LVM is quite a necessity to create multiple partitions in one encryption container. Alternatively, every partition could get an own encryption container. Note that a physical device (a hard disk) can only contain four partitions. If you need more, you have to use extended partitions or LVM.

Realization

Parameters

# The hard disk where everything is going to happen
export HDD=/dev/sda

# The partition which is going to hold the encrypted container
export CRYPTPARTITION=${HDD}3

# The boot partition
export BOOT=${HDD}1

# Some names; they are not important, but use the same name everywhere
# The name of the encryption container
export CRYPTCONTAINER=encrypted
# The name of the LVM group
export GROUP=group
# The name of the logical swap partition
export LV_SWAP=lv_swap
# The name of the logical root partition
export LV_ROOT=lv_root

# The size of the swap partition; use your RAM size as a rough estimate
export SWAP_SIZE=2G

Preparing the partitions

Note: If you need to create partitions in the first place, use cfdisk.

To increase the encryption strength, the partition should contain random data before creating the encryption container. This is done with badblocks, which checks for defect sectors at the same time. badblocks will destroy your partition (which will happen anyway soon), so double-check that you have the right $PARTITION. Note that this will take some hours.

After that, the various containers are created: first the encryption container. While doing this you have to enter the first (LUKS can handle up to eight keys) key – do not loose this or you are lost. You should create a strong, random key for this purpose – use one of the various key generators like this web-based tool or pwgen, for example. After creating the container, we immediately open, i. e. unlock it to create the LVM group. Now the volume group and the logical partitions for swap and the root file system are created and formatted. Finally, we format the boot partition.

# Install necessary packages
aptitude install cryptsetup lvm2

# Overwrite the target partition with random junk and check for bad blocks
badblocks -v -w -t random $CRYPTPARTITION

# Create the crypt container
cryptsetup luksFormat $CRYPTPARTITION

# Open the crypt container
# After this step, the raw uncrypted partition is accessible as
# /dev/mapper/$CRYPTCONTAINER
cryptsetup luksOpen $CRYPTPARTITION $CRYPTCONTAINER

# Create the logical volume group
pvcreate /dev/mapper/$CRYPTCONTAINER
vgcreate $GROUP /dev/mapper/$CRYPTCONTAINER

# Create the logical swap partition with the desired size
lvcreate -n $LV_SWAP $GROUP -L $SWAP_SIZE

# Use the rest for the logical root partition
lvcreate -n $LV_ROOT $GROUP --extents=100%FREE

# Format both partitions
mkfs.ext3 /dev/mapper/$GROUP-$LV_ROOT
mkswap /dev/mapper/$GROUP-$LV_SWAP

# Format the boot partition
mkfs.ext3 $BOOT

Copying the system

Now the existing system needs to be copied to the encrypted partitions. This should include everything from / apart from /boot, /dev, /media, /mnt, /proc, /sys and probably /tmp. The content of /boot is copied to the new boot partition $BOOT – the only part of the new system which is not encrypted.

# Create a mount point for the encrypted root system and mount it
mkdir /mnt/crypt
mount /dev/mapper/$GROUP-$LV_ROOT /mnt/crypt/

# Copy everything to the encrypted partition
cp -a /bin /etc /home /lib /root /sbin /usr /var /srv /selinux /mnt/crypt

# Create empty directories
cd /mnt/crypt
mkdir boot dev media mnt proc sys tmp
chmod 1777 tmp

# Mount the new boot partition and copy the old boot stuff
mount $BOOT boot/
cp -a /boot/* boot/

Making the system bootable

Now comes the most complicated part of the installation: Making the new system bootable. In order to do this, we have to chroot into the encrypted system. This allows us to act as like we booted the encrypted system. Before we actually chroot, we bind the /dev and /proc directories into the encrypted file system. This allows the chrooted system to use the running system.

# Bind /dev and /proc
mount -o bind /dev/ dev/
mount -o bind /proc/ proc/

# Do the chroot
chroot .

Now we are in the new system. Since this is basically a copy from the old, non-encrypted installation, we need to make it familiar with the fact that it is now on a different, and moreover an encrypted partition. First of all we inform cryptsetup via its configuration file /etc/crypttab that we have something to unlock on every boot.

# Configure cryptsetup
echo "$CRYPTCONTAINER $CRYPTPARTITION none luks" >> /etc/crypttab

Next, the system itself has to know which partitions to use – this is described in /etc/fstab. A fstab entry may look like the following two examples:

# /dev/sda5
UUID=fc35c8d0-018f-4397-8be0-114924c2a6a3 /               ext3    noatime,errors=remount-ro 0       1

/dev/sda5       /               ext3    noatime,errors=remount-ro 0       1

Both lines define the same mount, but use different ways to specify the partition: UUIDs and positions. UUIDs are used to identify partitions when they are not at the same position as they used to be. You get the UUID of an device with blkid /dev/DEVICENAME. To update the fstab, we add a new entry defining the boot partition, and fix the definitions of root and swap locations. The new fstab will contain the following lines:

$BOOTPARTITION       /boot           ext3    defaults        0       0
/dev/mapper/$GROUP-$LV_ROOT /               ext3    noatime,errors=remount-ro 0       1
/dev/mapper/$GROUP-$LV_SWAP none            swap    sw              0       0

You may use UUIDs instead.

The next step is the actualization of the boot loader and the initramfs, since Linux will now need encryption and lvm support in its initramfs. Ideally, the system would know itself which modules are needed in the initramfs. Since we are running the system in a chroot, this autodetection would fail (It depends on /sys being correctly filled). Therefor, we make sure that the modules lvm2 and dmcrypt are present in the initramfs even if the old system did not need them to boot, because the new system will definitely need them. The modules we need depend on busybox, so we install it. If you want to be able to suspend to disk, you have to specify the right resume device, i. e. your swap partition.

# Add needed initramfs modules
echo "lvm2
dmcrypt" >> /etc/initramfs-tools/modules

# Install busybox
aptitude install busybox

# Specify the resume device
echo "RESUME=UUID=$SWAP_UUID" > /etc/initramfs-tools/conf.d/resume

As a security measure, we want to be able to boot the old system if the new one fails. To facilitate this, an entry for the old system should be added to the boot loader. We copy it from the old boot loader config /boot/grub/grub.cfg into /etc/grub.d/40_custom. In my case, the entry looks like the following:

menuentry 'Debian GNU/Linux, with Linux 2.6.32-5-686' --class debian --class gnu-linux --class gnu --class os {
  insmod part_msdos
  insmod ext2
  set root='(hd0,msdos5)'
  search --no-floppy --fs-uuid --set fc35c8d0-018f-4397-8be0-114924c2a6a3
  echo  'Loading Linux 2.6.32-5-686 ...'
  linux /boot/vmlinuz-2.6.32-5-686 root=UUID=fc35c8d0-018f-4397-8be0-114924c2a6a3 ro  quiet
  echo  'Loading initial ramdisk ...'
  initrd  /boot/initrd.img-2.6.32-5-686
}

After this preparations the boot stuff on /boot and in the boot sector of the hard disk can be created.

# Create updated initrd
update-initramfs -u

# Update grub2 configuration
dpkg-reconfigure grub-pc

Now the file /boot/grub/grub.cfg should contain a section which refers to the encrypted device. Check the line set root= (this should refer to your boot partition) and the root= part of the linux line (this should refer to your root partition in the LVM group). My entry looks like the following:

menuentry 'Debian GNU/Linux, with Linux 2.6.32-5-686' --class debian --class gnu-linux --class gnu --class os {
  insmod part_msdos
  insmod ext2
  set root='(hd0,msdos1)'
  search --no-floppy --fs-uuid --set aa6f9a0d-fe13-46e2-b544-0997533fb2d5
  echo    'Loading Linux 2.6.32-5-686 ...'
  linux    /vmlinuz-2.6.32-5-686 root=/dev/mapper/$GROUP-$LV_ROOT ro  quiet
  echo    'Loading initial ramdisk ...'
  initrd    /initrd.img-2.6.32-5-686
}

Booting, troubleshooting, testing

Now we are ready to boot the new system. Since we made some some quite complicated changes, you might want to make yourself familiar with the GRUB 2 Rescue mode. Even more useful is QEMU, a tool which allows you to simulate for example the boot process. A VNC client like tightvnc is used to watch the simulated boot process. With these tools, you can verify your boot setup without in fact booting and hence can keep a running system.

# Install tools
aptitude install qemu-system xtightvncviewer

# Emulate booting (send emulator to the background)
qemu -hda $HDD -vnc :0 &
# Watch boot
vncviewer localhost

If the VNC viewer does not show a successful boot, you should not restart your system, but fix it. A boot is successful if it shows an encryption password prompt or a warning about a 64-bit Linux running on a 32-bit processor. If you reboot the system and fail to start the encrypted installation, use the following commands from your old installation to get back into the system:

# Open the partitions
cryptsetup luksOpen $CRYPTPARTITION $CRYPTCONTAINER
vgscan --mknodes
vgchange -a y

# Create a mount point for the encrypted root system (if necessary) and mount it
mkdir -p /mnt/crypt
mount /dev/mapper/$GROUP-$LV_ROOT /mnt/crypt/
cd /mnt/crypt

# Mount boot
mount $BOOT boot/

Now you can start again at „Making the system bootable“.

Cleaning up

I suppose you managed it to boot your new, shiny, encrypted system. If so, there are still things to do: First of all, we can change the initrd configuration in /etc/initramfs-tools/initramfs.conf from most modules back to dep – automatically detecting which modules to use. Now even lvm2 and dmcrypt can be removed from the explicit modules list /etc/initramfs-tools/modules. Run update-initramfs -u afterwards.

As a final step, I want to increase the encrypted partition to the whole rest of the hard disk and remove the old, unencrypted installation. I‘ll follow a description from the Ubuntu forum. Note that this is a quite difficult and dangerous operation, hence I created a backup of my home directory as a first measure. The whole operation took place from a gNewSense live stick. I prepared the old partition using badblocks. Next, I removed both the encryption container and the old partition from the partition table using fdisk and added a new partition taking the whole space. Then I increased the LUKS container, then the LVM group, then the logical root volume, then the file system on the volume. Finally, I booted into the system again.

aptitude update
aptitude install cryptsetup lvm2

badblocks -v -w -t random /dev/sda4

fdisk /dev/sda
> p
> d
> 3
> d
> 4
> n
> 3
> [enter]
> [enter]
> p
> w

modprobe dm-crypt

cryptsetup luksOpen $CRYPTPARTITION $CRYPTCONTAINER

vgscan --mknodes
vgchange -ay

cryptsetup resize $CRYPTCONTAINER

pvresize /dev/mapper/$CRYPTCONTAINER

pvchange -x y /dev/mapper/$CRYPTCONTAINER

# Repeat this step until there is no free space left
# Use smaller steps when there are no 4GB left anymore
lvresize -L +4G /dev/$GROUP/$LV_ROOT

pvchange -x n /dev/mapper/$CRYPTCONTAINER

e2fsck -f /dev/mapper/$GROUP-$LV_ROOT
resize2fs -p /dev/mapper/$GROUP-$LV_ROOT

Now I have a fully encrypted copy of my lovely old Debian installation. The GRUB boot entry for the old system in /etc/grub.d/40_custom is now dysfunctional and should be removed as well. After that, run update-grub2.