LVM on top of encrypted partition

Introduction

This article describes the procedure I followed to encrypt almost whole system disk with Debian GNU/Linux including the root partition with the only exception of the file system containing the /boot directory. This directory remained unencrypted because it contains the initrd and kernel images that are accessed by the boot loader such as LILO or GRUB.

The boot filesystem doesn't have to be necessarily unencrypted, because a more recent version of the GRUB bootloader is able to decrypt an encrypted partition using the cryptdevice option. However, the procedure described in this article uses the older approach and leaves the boot partition unencrypted.

Basic principle behind the encryption

I used the dm-crypt kernel module to perform the encryption and the cryptsetup tool to set it up. I created only one encrypted partition on the whole disk and divided it into LVM logical volumes with separate file systems. The scheme is depicted on the figure below:

LVM on top of encrypted partition
Installing cryptsetup and lvm2 and backing up the system

It's important to install the cryptsetup and lvm2 packages in the unencrypted system at first, for example by invoking the following commands:

  • apt-get update
  • apt-get install cryptsetup lvm2

A system backup can be performed after that and this backup should be able to decrypt and set up the particular file systems after it is restored. I did the backup using the GNU/tar archiving tool. I ran my own script from a live-CD and stored the backups to an external USB hard disk. The script used basically the following commands for each file system:

  • mount /dev/sdaX /mnt/misc
  • df -h | grep '/mnt/misc$' >> df.log
  • tar -cvvzf archive.0.tgz -g archive.0.snar -C /mnt/misc --sparse --numeric-owner --exclude=./lost+found
  • umount /mnt/misc

Using the -g option was useless in this case because no further incremental backup was done, but it was possible to pass it to tar without using the snapshot archive.

Creating partitions and the unencrypted boot file system

There's one important thing to take into account before continuing. All the operations described from this point of this article until the subsection Booting up were done from a live-CD system! Such system must contain the essential system utilities such as fdisk, mke2fs, mount, tar, chroot etc. It must also contain the GRUB, cryptsetup and lvm2 tools! I used the Debian Live Rescue CD.

But back to creating partitions. Only two partitions were needed as described at the beginning of this article. One unencrypted partition for the file system containing the /boot directory and one encrypted for all other file systems on top of LVM. I created them by using fdisk in the following way:

  • fdisk -u /dev/sda

I created only two partitions of appropriate sizes, the first of the default type Linux (id 83) and the second of type Linux LVM (id 8e).

I continued with building the boot file system by invoking:

  • mkfs.ext3 -b 4096 -L boot /dev/sda1

And then by extracting the backup of this file system:

  • mount /dev/sda1 /mnt/boot
  • tar -xvvzf boot.0.tgz -g /dev/null -C /mnt/boot

After these operations, the boot partition was succesfully restored.

Filling the partition to encrypt with pseudo-random data

The partition which is to be encrypted should be filled with pseudo-random data of sufficient quality before it is formatted and used. The reason is that an attacker shouldn't be able to easily recognize encrypted data from unencrypted areas and the prospective cryptoanalysis should become harder.

The best generator of pseudo-random numbers is provided by the device /dev/random. However, this device is almost never used to fill the whole partition or disk because it's incredibly slow. Its counterpart /dev/urandom generates pseudo-random data with less entropy and its output is therefore not of such quality, but it's also much faster. It represents a good compromise between quality and speed and that's why I decided to use it:

  • dd if=/dev/urandom of=/dev/sda2 bs=4096

It may still take quite a long time to fill the whole area by pseudo-random numbers. The above given command ran approximately 26 hours on my machine before the entire partition over almost the whole disk was filled up.

Another possibility is to use the random number generator provided by the badblocks command. It's faster than the one provided by the /dev/urandom device, but the quality of the data is of course lower again. If you don't have so much time or need to fill up a much larger volume of data, you can use it by invoking a command similar to:

  • badblocks -v -s -w -t random device
Formatting the dm-crypt/LUKS partition to encrypt

The Linux LVM partition was already created and filled with pseudo-random data, but it had to be formatted yet. I used the LUKS extension to create and access this partition because it specifies a platform-independent standard on-disk format and also ensures that the encryption and password management are done in a secure and documented manner. The cryptsetup tool supports LUKS and it was therefore easy to format the encrypted partition:

  • cryptsetup -c twofish-xts-benbi -s 512 -y luksFormat /dev/sda2

As you can observe, I used the twofish cipher in the xts encryption mode and benbi as the IV (initialization vector) generation algorithm. If you decide to use another cipher, you should choose from the AES finalists (AES alias Rijndael, Serpent, Twofish, RC6, MARS) to achieve a security level of reasonable quality. The best choice for encryption mode is currently xts. If you need to use different mode, for example because your older system doesn't support xts yet, you shouldn't use lrw which contains vulnerabilities, but rather cbc with the essiv IV generation. Avoid using cbc with the plain IV generation algorithm because it is vulnerable to watermarking attacks. When using the xts mode, both plain and essiv could be used without reducing the quality of encryption. The recommended key size in this mode is 512 bits because the key is splitted into two parts of the same length. The first half is used as an encryption key for the block cipher and the second half to generate the IV.

It was also necessary to open the formatted LUKS partition, i.e. set up a mapping between the encrypted and unencrypted device, in order to access the partition without encryption. I did it by invoking:

  • cryptsetup luksOpen /dev/sda2 main

No further options had to be passed to cryptsetup because they were stored into the LUKS partition header during formatting. Moreover, the file /dev/mapper/main was created.

Creating LVM volumes and group, creating file systems

I decided to edit the configuration file of the LVM installation on the live-CD before proceeding. The reason was that I wanted to store the LVM metadata backup and history of changes outside the temporary root file system created by the live-CD and also outside the LVM volumes I was going to create, specifically on the unencrypted boot partition, in order to have them by hand in case of a disk failure. I also wanted to have them easily accessible for their backup on another medium. I therefore edited the section backup of the file /etc/lvm/lvm.conf in the following way:

  • backup = 1
  • backup_dir = /mnt/boot/lvm/backup
  • archive = 1
  • archive_dir = /mnt/boot/lvm/archive
  • retain_min = 10
  • retain_days = 30

The boot file system was still mounted to the directory /mnt/boot, but the referenced directories didn't exist yet. Hence, I created these directories before any LVM command was invoked:

  • mkdir -p /mnt/boot/lvm/backup
  • mkdir -p /mnt/boot/lvm/archive

I also re-initialized the internal LVM cache of all disks and other block devices by running:

  • vgscan

Then I checked that the device /dev/mapper/main which was mapped to the encrypted partition was included in the internal LVM cache and that no further changes to the LVM configuration file had to be made:

  • grep main /etc/lvm/cache/.cache

Otherwise the filter or types variables in the devices section of the configuration file must have been updated.

After these preparations, I started with the creation of LVM volumes. Firstly, I created one physical volume:

  • pvcreate --metadatacopies 2 /dev/mapper/main

I changed the default number of metadata copies from one to two because I planned to have only one physical volume in my future volume group. This setting is safer because one copy is stored at the beginning of the volume and the other at the end and it's more probable to restore the data on disk in case of a disk failure.

I continued with a volume group creation:

  • vgcreate mg /dev/mapper/main

And then I started to create logical volumes that were intended to contain file systems and a swap space:

  • lvcreate -L rootsize -n root mg
  • lvcreate -L swapsize -n swap mg
    ...
  • lvcreate -l 80%FREE -n home mg

The sizes of the volumes that were passed to the -L argument are derived from the original file system sizes stored in the df.log during the backup. However, I haven't used the whole free space for the last and also largest volume, but only 80% of the available space. The reason was that I planned to create snapshot volumes temporarily for the purpose of file system backups. The snapshot volume is an ideal solution for creating a backup because it is able to provide a frozen image of the contents of another logical volume that can still be updated. It therefore ensures that the backup is consistent. Such a snapshot volume doesn't require the same amount of storage the original logical volume has, but 15-20% should be enough in a typical scenario according to the lvcreate manual page.

The aforementioned lvcreate command sequence also generated a set of files /dev/mg/root, /dev/mg/swap ... /dev/mg/home.

Because all the LVM volumes and group existed already and I wasn't going to update them, the LVM metadata weren't going to change. I therefore unmounted the boot file system:

  • umount /mnt/boot

The last step of this subsection was to create file systems and also a swap space on top of the existing logical volumes. I did so by invoking the following command sequence:

  • mkfs.ext3 -b 4096 -L root /dev/mg/root
    ...
  • mkfs.ext3 -b 4096 -L home /dev/mg/home
  • mkswap -L swap /dev/mg/swap
Restoring the backups on top of LVM

The LVM logical volumes were generated and formatted in the preceding subsection. Hence, it was possible to restore the file system backups made at the beginning of this subsection. I ran the following commands for each file system to do so:

  • mount /dev/mg/lv-name /mnt/misc
  • tar -xvvzf archive.0.tgz -g /dev/null -C /mnt/misc
  • umount /mnt/misc
Mounting the file systems and running chroot

I needed to update the initrd image and install the GRUB boot loader later and I needed to do it in a chroot environment with the original directory tree. I therefore prepared such environment by running these commands:

  • mkdir /mnt/root
  • mount /dev/mg/root /mnt/root
  • mount /dev/sda1 /mnt/root/boot
    ...
  • mount /dev/mg/home /mnt/root/home
  • mount -t proc proc /mnt/root/proc
  • mount -t sysfs sysfs /mnt/root/sys
  • mount -t devtmpfs udev /mnt/root/dev
  • chroot /mnt/root
Editing system files in order to point to logical volumes

The backups of the original system were restored already, but they had to be customized yet in order to point to the created logical volumes instead of disk partitions. However, the first file I edited didn't reference any partition device because it wasn't used on the original system. The name of the file in question is /etc/crypttab and it is a configuration file of the cryptsetup tool that specifies the encrypted devices that should be configured at boot time. I added a line of the following format:

  • main UUID=... none luks

This line should have ensured that the device main created in the subsection Formatting the dm-crypt/LUKS partition to encrypt will be configured during system startup. However, it was not sufficient for the system to start because the root file system was located on this device and initrd had to be therefore generated properly as described later. The UUID of the LUKS partition could have been found out by using the blkid command. For example, I could have invoked:

  • blkid /dev/sda2

The next important system file to edit was the file /etc/fstab which contains static information about the file systems. I replaced the UUID=... strings by the logical volume names as follows:

  • /dev/mg/root / ext3 defaults,errors=remount-ro 0 1
  • UUID=... /boot ext3 defaults,errors=remount-ro,nodev,nosuid,noexec 0 2
  • /dev/mg/swap none swap sw 0 0
    ...
  • /dev/mg/home /home ext3 defaults,errors=remount-ro 0 3

The initrd image was responsible for the resume operation. Hence, I edited the configuration file /etc/initramfs-tools/conf.d/resume:

  • RESUME=/dev/mg/swap

The initrd image had to be regenerated after this change, but this action was planned anyway later.

Finally, I modified the LVM configuration file /etc/lvm/lvm.conf in the same way as explained at the beginning of the subsection Creating LVM volumes and group, creating file systems. Notice that the change was done in a different file this time because I edited the restored configuration file in the chroot environment instead of the file created and used by the live-CD.

Updating initrd

Updating initrd correctly was a crucial step so that the system was able to start. The kernel itself couldn't mount the root file system on top of encrypted LVM because it required additional modules, tools and configuration settings for the mount. All these resources must have been part of the initrd image that should have been mounted as a temporary root file system in order to provide them for the purpose of mounting the real root directory.

It was necessary to prepare some configuration files before the update. Firstly, the value of the MODULES variable in the file /etc/initramfs-tools/initramfs.conf should have been set to most:

  • MODULES=most

Without this setting, the cryptsetup tool and the dm-crypt and xts modules weren't included. Neither was the ahci module.

Secondly, I had to add the twofish and twofish-common cryptographic modules to the file /etc/initramfs-tools/modules:

  • twofish
  • twofish-common

These modules were not part of initrd if only MODULES=most was specified because the default cipher was AES and only its modules were thereofore included.

Finally, the file /etc/initramfs-tools/conf.d/cryptroot was created manually. I created the file in question with the following format:

  • target=main,source=UUID=...,key=none,rootdev,lvm=mg-root
  • target=main,source=UUID=...,key=none,lvm=mg-swap

However, this file should be autogenerated when the udev partition is mounted to the /dev directory in the chroot environment.

After the configuration changes were done, it was possible to regenerate the initrd image by invoking the update-initramfs command. This tool had to be run in the chroot environment because it had to be able to gather all the necessary information from the original directory tree with the restored file systems and also produce the resultant initrd image in the restored /boot directory. I used the following commands:

  • update-initramfs -k all -d
  • update-initramfs -k 2.6.32-5-686-bigmem -c

I also wanted to check that the new initrd image contains the device mapper dm-mod module, the dm-crypt module, the kernel crypto modules twofish, twofish-common and xts, the cryptsetup and lvm tools and the cryptroot configuration file with appropriate contents. I therefore invoked a command sequence similar to this:

  • mkdir /tmp/initrd
  • cd /tmp/initrd
  • cat /boot/initrd.img-2.6.32-5-686-bigmem | gunzip - | cpio -i
  • cd lib/modules
  • find . -name dm-mod.ko
  • find . -name dm-crypt.ko
  • find . -name twofish.ko
  • find . -name twofish_common.ko
  • find . -name xts.ko
  • cd ../../sbin
  • find . -name cryptsetup
  • find . -name lvm
  • cd ../conf/conf.d
  • cat cryptroot
Installing GRUB

We are still working from the live-CD operating system, but in a chroot environment and thus in the restored file/directory tree, the boot loader can be installed easily. All the files are already present and the devices as well, the udev filesystem was mounted in the subsection Mounting the filesystems and running chroot.

I suppose that GRUB is your default boot loader. Then it's sufficient to install it into MBR and generate or update its configuration:

  • grub-install
  • update-grub
Booting up

Because the GRUB and update-initramfs commands finished successfully and the initrd image contained all the resources needed to access the root device, I tried to reboot my notebook from disk. GRUB started, loaded the kernel and the initrd image, decompressed them, mounted initrd as a temporary root file system and I was prompted for a password to unlock the encrypted main partition. I entered the correct input string and the partition was accessed. The mg volume group was found and the root logical volume was activated. It was mounted and after that, usual system initialization continued and the system successfully booted up.

However, some configuration changes were still to be done. First of all, I wanted to make my initrd image smaller again and autodetect most settings. I therefore changed the value of the MODULES variable back to dep in the file /etc/initramfs-tools/initramfs.conf:

  • MODULES=dep

I commented out the twofish and twofish_common modules in /etc/initramfs-tools/modules because they were autodetected when they were used:

  • # twofish
  • # twofish_common

The cryptroot file in the initrd image could be also automatically generated if the devices and symbolic links to them were created correctly by the udev daemon and the LVM volume and group information was available. This was the case on the running system and I therefore deactivated the /etc/initramfs-tools/conf.d/cryptroot file by renaming it to cryptroot.inactive:

  • cd /etc/initramfs-tools/conf.d
  • mv cryptroot cryptroot.inactive

I updated initrd once again:

  • update-initramfs -k all -u

And I tried to reboot, just to be on the safe side.

Backing up partition table, LUKS header and LVM metadata

Even if this step wasn't necessary, I decided to execute it in order to protect my data against hard disk failures. I backed up the partition table of my laptop hard disk drive, the LUKS header of my encrypted partition and the LVM metadata and I stored them on another encrypted partition of my external USB hard disk. It may seem a bit paranoid that I prohibited unauthorized access to these files, but I didn't want anybody to modify this sensitive information.

If the partition table gets corrupted, there're tools such as the gpart program in Debian that might help to recover it. However, such a program might not be successful and the partition table backup may therefore be very handy so that file system recovery tools, for instance the fsck program, could be run on the damaged system. I used the sfdisk command to store the backup because it saves not only the primary partition table from MBR, but also the extended partition table from additional EBRs:

  • sfdisk -uS -d /dev/sda > sda-sfdisk-partition-table.out

The inverse operation, i.e. partition table recovery, might be done as follows if needed:

  • sfdisk -uS /dev/sda < sda-sfdisk-partition-table.out

Furthermore, it's possible to recover lost partitions of many types by using such a tool as the testdisk program. But this, of course, isn't the case if the partition is encrypted because it's not readable without proper decryption. The main trouble with encrypted LUKS partitions, volumes or files is that they use two types of keys. The data are encrypted with so called master key which is generated when the LUKS partition is formatted and this key is encrypted by one or more user-defined keys and stored into key slots in the LUKS header. The main benefits are that more user keys may be used to access the data because 8 key slots are available and that such a user key can be revoked without the need of re-encrypting the whole device. Unfortunatelly, when the LUKS header where the master key is stored is damaged, all the data on the encrypted volume are lost if we don't have a backup of the header.

The backup can be created by using the standard dd command by copying the LUKS header into a file. However, the size of the header must be passed to dd. The header size can be found out from the LUKS dump provided by cryptsetup:

  • cryptsetup luksDump /dev/sda2 | grep '^Payload offset:'

I received the value 4040 which is the number of blocks of 512B in size that can be simply passed to dd:

  • dd if=/dev/sda2 of=sda2-luks-header.out count=4040

You should keep in mind that the saved header contains all the user keys that were used at the time of the backup. If some of the keys have been revoked since then, a new backup should be made.

Restoring the backup can be done simply by switching the if and of arguments:

  • dd if=sda2-luks-header.out of=/dev/sda2

To be able to recover the LVM volumes, a backup is also needed, the backup of their metadata this time. The metadata of a volume group are stored into its physical volumes, one copy in each volume by default as specified in the LVM configuration file /etc/lvm/lvm.conf. If there are too many physical volumes in a volume group, it's advisable to store the metadata only into a few (let's say three) volumes. On the contrary, if a volume group consists of only one physical volume, it's recommended to store two metadata copies into the physical volume because one is stored at the beginning and the second at the end of the volume. The number of metadata copies that should be stored in a specific physical volume can be defined by the --metadatacopies argument that can be passed to the pvcreate command. However, this metadata redundancy doesn't have to be a sufficient protection scheme in all cases, especially if the disk drive is heavily corrupted. It's therefore reasonable to have a backup of the metadata and ideally store it on another medium.

The volume group metadata backups are stored by default into the files that bear the volume group names and are located in the directory /etc/lvm/backup. However, I've changed this location to another directory /boot/lvm/backup because the original directory itself was stored on the LVM volume. This step is described in the subsection Creating LVM volumes and group, creating file systems. The backup to this directory is done everytime whenever the physical or logical volumes or volume groups are updated or whenever a command vgcfgbackup without the -f switch is called. Because I preferred not to wait for regular backups to copy these metadata files onto another medium, I copied them manually immediatelly after they have been changed:

  • vgcfgbackup -f mg-lvm-backup.out mg
Summary

This procedure was successfully used to encrypt Debian squeeze and Debian jessie on both i386 and amd64 architectures. No problem during any system upgrade or successive restart occurred. Furthermore, standard Debian distribution upgrade from squeeze to wheezy and later to jessie was possible without any interventions caused by this method.

 

Inserted: 2016-10-19 23:06:48
Last updated: 2016-10-19 23:06:48