Adding LUKS hard disk encryption on LVM after the fact

I have an external hard disk enclosure with two disks. I used Logical Volume Manager to create a single logical volume that spanned them, and slowly filled it to about 60% capacity. Lately, I’ve been trying to be more conscious of using encryption, and this was one area where I hadn’t done so. At the time I felt like learning how to do LVM was enough, and LUKS could wait until later. Well, it’s later. Here’s how I added encryption after the fact (without a spare hard disk).

The warning

As these are potentially dangerous commands, you absolutely should back up your files prior to beginning. If you have sufficient spare media, you should consider not trying to do this conversion in-place. Instead just create a new filesystem and copy files over.

The plan

In my case, the volume of data was such that I didn’t have the resources to do so, and the value of the files was fairly low, so I just went ahead without. The plan is to shrink the existing filesystem and logical volume, create a new logical volume in the freed space, with an encrypted filesystem inside. Then, move files from the old filesystem to the new one, while readjusting the allocation of disk space if needed.

The script

This was reconstructed from my bash history, and typed out by hand. Errors are certainly possible, so don’t blindly reuse these exact commands. With that said, let’s begin.

First, unmount the filesystem (FS), and shrink it to be as small as possible. Then, shrink the logical volume (LV) to match the FS’s size:

umount /dev/vg0/usr-store
resize2fs -pM /dev/vg0/usr-store
lvreduce -L ? /dev/vg0/usr-store # from resize2fs

If resize2fs reports “2000 (4k) blocks” as the new filesystem size, then the size for lvreduce is in megabytes: 2000 * 4*1024 / 1024*1024.

Now, allocate the space you just freed up by shrinking usr-store to a new LV:

lvcreate -l 100%FREE -n new-store vg0

Now, we’ll add the encryption layer:

cryptsetup --verify-passphrase luksFormat /dev/vg0/new-store
cryptsetup luksOpen /dev/vg0/new-store new-store

And create a new filesystem. I chose ext4, which has efficiencies when dealing with large files, but your workload might require something different.

mkfs -t ext4 -m2 -O dir_index,filetype,sparse_super -L new-store-fs /dev/mapper/new-store

At this point, you have a new filesystem that’s LUKS-encrypted and ready to use. We can now begin transferring files from the old filesystem to the new one.

mkdir /mnt/new-store
mount -t ext4 /dev/mapper/new-store /mnt/new-store
mount -t ext4 /dev/vg0/usr-store /mnt/usr-store
mkdir /mnt/new-store/mike
chown mike:mike /mnt/new-store/mike
rsync -a --remove-source-files /mnt/usr-store/mike/Files /mnt/new-store/mike
# wait... wait... wait...
find /mnt/usr-store/mike/Files -type d -empty -delete

If you filled the new filesystem, then you’ll need to readjust the disk space allocation by shrinking the old filesystem & LV, and growing the new filesystem & LV.

umount /dev/vg0/usr-store
resize2fs -pM /dev/vg0/usr-store
lvreduce -L ? /dev/vg0/usr-store
mount -t ext4 /dev/vg0/usr-store /mnt/usr-store

lvextend -l +100%FREE /dev/vg0/new-store
cryptsetup resize new-store
resize2fs -p /dev/mapper/new-store

Now you can continue moving files.

Once the old filesystem is empty, simply remove the logical volume that contains it, and rename the new encrypted volume and LV. You can optionally extend the FS & LV to fill the rest of the space now as well. I chose to use the space for a new LV I’ll be using for backups instead.

umount /dev/vg0/usr-store
lvremove /dev/vg0/usr-store

lvrename vg0 new-store usr-store
dmsetup rename new-store usr-store
rmdir /mnt/new-store
mount -t ext4 /dev/mapper/usr-store /mnt/usr-store