1. dmsetup and losetup
    1. Author
    2. Commands
    3. Summary
    4. Discussion
    5. A Few Loose Ends

dmsetup and losetup

Author

Commands

#!/bin/bash

# load the device drivers we will be relying on
modprobe loop
modprobe dm_mod
modprobe dm_crypt
modprobe aes

# create a zero filled file
dd if=/dev/zero of=image_file bs=1M count=10

echo "hex dump of file before crypto mapping"
od -t x2 image_file > hexdump1.txt

# represent the image_file as a proper block device through the loop driver
losetup /dev/loop/0 image_file

# establish a crypto mapping through a device-mapper block device.
blksize=$(blockdev --getsize /dev/loop/0)
key="0123456789abcdef0123456789abcdef"
echo "0 $blksize crypt aes-plain $key 0 /dev/loop/0 0" | dmsetup create crypto_mapped_device

# write some plaintext through the mapped device
echo "howdy1" > /dev/mapper/crypto_mapped_device

# unmap/unloop the image_file
dmsetup remove crypto_mapped_device
losetup -d /dev/loop/0

echo "hex dump of image after crypto mapping"
od -t x2 image_file > hexdump2.txt

Summary

Using the loopback and device-mapper drivers in the linux kernel to represent a file as an encrypted block device.

The commands section, when saved to a file and run as a bash script, does this:

Discussion

The modprobe commands load the device drivers that the losetup and dmsetup commands will be relying on. Although once upon a time explicitly loading these modules would have been necessary, these days running the losetup and dmsetup commands would trigger the loading of the necessary modules. I am mostly including the modprobe commands to illustrate some of what is going on behind the scenes. In certain restricted environments (initrd filesystems), you would need to make these modules available explicitly.

Depending on how the kernel you are running was configured, any or all of these modules could already have been compiled in to the kernel image itself, so you may get some error messages when running this script about modules not existing.

The "dd" command creates the zero-filled file image_file and the first "od" command creates a hexdump of it to verify that it is zero filled. The "-t x2" argument to "od", BTW, is specifying hexadecimal as the desired output format of the dump.

The losetup command is used to represent image_file as the proper block device /dev/loop/0(which is a very useful trick by itself as many linux operations, that make perfect sense when done on ordinary files, can still only be done on block devices). I must say I am suprised that there are only 8 possible loopback block devices in linux, /dev/loop/0 through /dev/loop/7. I can easily envision circumstances in which more than 8 being would be useful.

The heart of the whole script is this line:

echo "0 $blksize crypt aes-plain $key 0 /dev/loop/0 0" | dmsetup create crypto_mapped_device

The dmsetup command is used to communicate with the linux device-mapper. The main concept behind the linux device mapper is the glueing together of multiple physical block devices into a single virtual block device through the mapping and transformation of sector (continguous 512 byte segment) ranges. For example, we could glue together the physical devices /dev/sda1 and /dev/sda2 into the single virtual block device /dev/mapper/vdev:

device

sector-range

sector-range

/dev/sda1

0-999

-

/dev/sda2

-

0-999

/dev/mapper/vdev

0-999

1000-1999

with the following dmsetup command:

(
   echo "0 1000 linear /dev/sda1 0"
   echo "1000 1000 linear /dev/sda2 0"
) | dmsetup create vdev

The format of a single dmsetup table line is:

<logical start sector> <sector count> <target_type> <target_args>

And the target_type "linear" has the arguments:

linear <destination device> <start sector>

Other possible target types include "striped" and "crypt". The arguments to a "crypt" target_type are:

crypt <sector format> <key> <IV offset> <real device> <sector offset>

So in our case:

crypt arguments

value

sector format

aes-plain

key

0123456789abcdef0123456789abcdef

IV offset

0

real device

/dev/loop/0 (aka image_file)

sector offset

0

The sector format of aes-plain means to use the aes cipher in CBC mode. If you wanted to use aes in ECB mode (generally not a good idea) you could specify it as aes-ecb. The key has to be specified as a hexadecimal string of the correct length. For the aes cipher, we need 128 bits of key, which equates to 32 hex digits. If you are interested in what other ciphers are available to your particular kernel, this

ls /lib/modules/<kernel-version>/kernel/crypto

will list the available driver modules and this:

cat /proc/crypto

will list information about each crypto driver loaded. For example:

name         : aes
driver       : aes-generic
module       : aes
priority     : 100
type         : cipher
blocksize    : 16
min keysize  : 16
max keysize  : 32

name         : md5
driver       : md5-generic
module       : kernel
priority     : 0
type         : digest
blocksize    : 64
digestsize   : 16

Thus, this line:

echo "0 $blksize crypt aes-plain $key 0 /dev/loop/0 0" | dmsetup create crypto_mapped_device

is setting up the virtual block device /dev/mapper/crypto_mapped_device so that reading and writing to it will cause the corresponding aes encrypted cipher-text to be read and written to image_file through /dev/loop/0.

We test this by writing the string "howdy" to /dev/mapper/crypto_mapped_device, unmapping image_file (to ensure the cipher text is completely written and not cached in memory), and getting a final hex dump of image_file.

The "od" command only only reports on regions of image_file not filled with zeros and each line reports on 16 bytes at a time, so we can estimate that amount of cipher text that was written by doing a simple word count:

wc hexdump2.txt

which yields 35 lines, or 16*35 = 560 bytes of cipher-text. As you would expect, the cipher-text looks pretty random. Here are the first 10 lines of hexdump2.txt:

0000000 5f32 25e6 9633 c4dc 944a a76c 6ceb 6c39
0000020 5bf4 3391 7d2b cccf 02cc 1724 6cc4 86c0
0000040 4788 8416 294a 0821 9e3a 9a48 dd81 ee9b
0000060 115c b0ca b51c dcd8 8b53 191b 2b54 f80e
0000100 c54a 6fe9 4c7e 61dd 1b10 6f10 ebf1 1386
0000120 65ee fb61 4e27 3d42 8f2c feec 7ba5 f4eb
0000140 14c3 2c88 3297 cd6e 2094 567e 3d7c 73ad
0000160 495d 22e1 1639 058e ae0e 5520 1856 4923
0000200 28d8 8943 a9ab 4d0c e02c 1af5 a987 7165
0000220 33bc 0173 fb90 218e aa4c 5c22 e74f 6fd5

A Few Loose Ends

This line,

blksize=$(blockdev --getsize /dev/loop/0)

deserves some explanation. In bash, you can capture the text output of a command by embedding it in a "$()" construct, called command quoting. blockdev is a very useful commmand for dealing with block devices and our only use of it here was to determine how many blocks (units of 512 bytes) the /dev/loop/0 device represented. Since our dd command constructed a 10MB file, we already knew the answer (10*1024*1024/512=20480). We could also have hard coded 20480 in the dmsetup command, or used bash-style arithmetic to derive 20480 from 10MB.

hopeless_linux: encrypted filesystems/dmsetup and losetup (last modified 2007-07-01 16:01:00)