Encryption-at-Rest as a Gateway to Device-Mapper, Loop Devices in Linux
Last updated on 29 July, 2022 at 15:20 hrs.
This post presents a demo using some cool Linux features (loop devices & the Device-Mapper kernel framework) to show how encryption-at-rest is actually achieved in practice (in Linux, of course). For reference, BitLocker and FireVault are the Windows and MacOS equivalents.
Introduction
Encryption-at-rest is a set-up in which the OS kernel (Linux in this
case) will encrypt data before writing it to persistent memory, and
decrypt it when reading from persistent memory. This is great as a
privacy feature: if your laptop is ever stolen/lost, at least your data
isn’t compromised. However, it does need to be implemented with great
care to get any guarantee of security; the Arch Wiki on
dm-crypt
provides a lot of information that someone
doing this seriously ought to understand – I suggest it for further
reading if you’re interested.
This post does not actually go into how to perform an installation of Arch with disk-encryption, but it will surely help one understand how encryption-at-rest is implemented in Linux systems. To do this, the post provides a high-level overview of the kernel’s Device-Mapper abstraction framework and how it can be used, in conjunction with the Loop Devices, to build an encrypted backup setup.
Device-Mapper: dm-crypt
Device-Mapper is a framework provided by the Linux kernel which makes the creation and management of logical volumes, LVs, possible (Logical Volume Management, LVM, is the category for all things LV related). We differentiate between virtual and physical volumes as follows. A physical volume generally corresponds to hardware like an SSD/HDD, while a logical volume exists, and is managed, in software. Logical volumes are generally much more flexible and can be used to do cool things using the kernel’s Device-Mapper framework.
From Device-Mapper’s documentation
Device-Mapper’s “crypt” target provides transparent encryption of block devices using the kernel crypto API.
Recall: To the Linux kernel, a block device is anything that can provide random access to data, in fixed size blocks. RAM, SSDs, HDDs, USB flash drives, are all examples of block devices. Char(acter) devices, on the other hand, deal with data in a stream-oriented fashion; think of the webcam, microphone, speakers, keyboard and many more.
It would be nice to play around with dm-crypt
in a
low-stakes environment since it has the ability to render your system
unusable, if not careful. A virtual machine is the best option, but it’s
a bit too much for me (and I think loop devices are somewhat
unappreciated).
Loop Devices
We’ve seen that the dm-crypt
target provides encryption
of devices which can provide random access to data in fixed size blocks.
Well, a regular file provides can satisfy these requirements pretty
trivially: it can be read and written in byte-sized blocks. But how can
we get the kernel to treat a file like a block device (since that’s what
we need to use dm-crypt
)?
A loop device is a block device whose backing medium
is not a physical volume, but rather another block device or a file in
the file system (see man loop
). This is exactly what we
need to treat a file as a block device.
Project
Now that we have covered the basics, let’s combine them to solve some
real problem. Most people have important digital files that (obviously)
are backed up on, another device like an external HDD/SSD, USB flash
drives, etc. It’s possible that some backed up files are confidential
and so storing them in plain text can’t be good; encrypting each file
individually can work, but it’s too much work (and not too elegant
either), and it also revels (to a cryptographic adversary) too much
information about what the files actually are; I probably don’t have to
wonder what the contents of id_rsa
are.
We’re going to implement a backup program which can be used by a system user to back up a list of files/directories.
Kernel Modules
We need to load the required kernel modules for Loop and dm-crypt to
work. Run modprobe loop dm_crypt
to load the modules into
the kernel.
Creating a Loop Device from a Regular File
The size of the regular file can vary depending on one’s needs; I’ll
create a 1G zeroed-out file called backup
using
dd if=/dev/zero of=backup bs=4M count=250
NOTE: Always be careful when using dd
as it can very easily destroy your disk (as evidenced by its nickname
“disk destroyer”).
Since backup
is a regular file, it can be moved/copied,
using the normal file-system commands, to an external HDD or another
backup device. So it doesn’t matter where one creates the
backup
file.
If need be, backup
can also be simply resized later on.
dd
takes a seek
argument which can be set to
seek at the end of the file, so additional space is appended to the
file. Then, the file system on the block device needs to be updated for
this change to be interpreted properly. Different file systems have
different commands, so one would have to look this up.
Setup: Loop & Dm-crypt
The following command will set up the regular file
backup
as a loop device.
sudo losetup --find --show --partscan backup
The --find
flag asks losetup
to find the
first free loop device and use that and --partscan
asks the
kernel to scan the partition table for the device (see
man losetup
for more). If successful, it will print out a
path like /dev/loop0
– our loop device has been created.
Here’s the line that appears in my lsblk
(the command which
“lists block” devices) output
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 1000M 0 loop
We can see that it has size 1G, and is not mounted. To mount it, we need a file system, but first a short discussion on the non-commutativity of logical volumes and encryption.
Device-Mapper allows us to set up and manage virtual volumes, and
dm-crypt allows us to encrypt these virtual volumes (which satisfy the
interface for block devices). The order of these actually does not
matter to the tools themselves, but it does produce a different outcome.
If dm-crypt encrypts the entire block device (backup
)
first, an adversary with a copy of backup
, depending on how
determined they are, won’t be able to tell that there’s a file-system on
it, since dm-crypt will encrypt the partition table of the block device.
If dm-crypt runs on a partition in a backup
instead, an
attacker can quickly determine that backup
is a dm-crypt’ed
loop device using a simple scan of the first few kilobytes of
backup
for a partition table. The former throws kind of a
smoke-screen for the adversary, and we shall use that approach here.
Next, we have to prepare the device for use with dm-crypt using
following command (where LOOP_DEVICE
is the new block
device added in the lsblk
output; would be
/dev/loop0
for me).
sudo cryptsetup luksFormat "${LOOP_DEVICE}"
If this completes successfully, the device has been set up by dm-crypt.
Aside: Security vs. Safety
TL;DR The data’s security can compromise a user’s safety.
The concern for safety comes when an adversary who possesses a copy
of backup
deduces that it is actually an encrypted file
system. A scenario on the
Wikipedia page for “deniable encryption” describes a situation in
which a person, say Bob, has a kind of “onion-vault” which is encrypted
by multiple layers of dm-crypt (so dm-crypt would encrypt a logical
volume which is contained inside a dm-crypt encrypted logical volume and
so on…) and the adversary becomes aware of this fact. The adversary then
resorts to torturing the keys out of Bob, but the adversary can’t know
whether Bob has provided the last key or not. And because of this fact,
the adversary could indefinitely torture Bob. Deducing this, Bob
actually figures that if the adversary can’t know when he’s given the
last key, there’s no point in revealing any keys since
he is going to be tortured either way.
It’s interesting to think about, and realize that cryptography, powerful as it is, can actually jeopardize the safety of the user in the most extreme situations.
Using a Dm-crypte’ed Block Device
Before using the dm-crypt’ed loop device, its Device-Mapper (dm-crypt) has to be set up in the kernel first (dm-crypt needs the key to perform encryption/decryption in its write/read implementations on the underlying block device).
sudo cryptsetup open "${LOOP_DEVICE}" "${MAPPER_NAME}"
The above command will set up the LOOP_DEVICE
in the
kernel (e.g. /dev/loop0
) and expose it as an unencrypted
block device at /dev/mapper/${MAPPER_NAME}
for plain
read/write operations. The first time that the device is used, it will
have no file system, so we can create one on it, and make a mount point
for this device.
# format the exposed block device
sudo mkfs.ext4 "/dev/mapper/${MAPPER_NAME}"
# create a mount point
sudo mkdir -p "/mnt/${MAPPER_NAME}"
Then to mount the formatted device to the created mount point, run
# mount the formatted exposed block device to mount point
sudo mount "/dev/mapper/${MAPPER_NAME}" "/mnt/${MAPPER_NAME}"
Now, I can back up my SSH keys in there using
rsync -aPrv ~/.ssh "/mnt/${MAPPER_NAME}"
In general, rsync
can be used to make a great backup
tool. The user can have a list of directories to back up in a
~/.config/backup_list.txt
which; a script would then use
rsync
to mirror each directory in the list to
/mnt/${MAPPER_NAME}
.
Extension (for the Reader)
udev
is another interesting part of the kernel which
manages device events. If one can determine which udev
event corresponds to the insertion of their backup media (HDD/SSD/USB
flash), it’s possible to then run a script which will prompt the user
for the backup
file password, open backup
,
mount it and synchronize the user’s directories, then unmount and close
backup
.
Conclusion
This post has gone over the following
- difference between physical and logical volumes
- what block and character devices are
- what Device-Mapper kernel framework is
- how to create and use loop devices
- how to use dm-crypt & loop devices to achieve deniable encryption
I hope you learned something, because I surely did when writing this!