Disaster Area

Using an Encrypted Root Partition with Raspbian

I recently had to figure out how to encrypt the root partition of a Raspberry Pi running Raspbian. I found some mentions of people doing it here and there, but no end-to-end walkthrough of how to do it. These instructions are for Raspbian ‘wheezy’ and assume a newly imaged SD card from the distributed Raspbian image. I also used another computer running Linux with an SD card reader to perform the actual initial encryption. There may be a way of doing this all form a single Pi, but I didn’t look to closely into it.

Setup on the Pi

First we make sure we’re using the latest software and then reboot to make sure everything is there. After that you should expand the root partition by running sudo raspi-config and following the prompts.

sudo apt-get update && sudo apt-get upgrade
sudo shutdown -r now

Now we’ll upgrade the firmware and kernel. Right now (early November 2013) we need to use the ‘next’ branch as that kernel has CONFIG_BLK_DEV_INITRD enabled. That option allows us to use an initial RAM filesystems (referred to as an initramfs afterwards). This can take a while, and will fill up your root partition if you haven’t expanded it yet. Afterwards reboot to have the new firmware and kernel take effect.

sudo BRANCH=next rpi-update
sudo shutdown -r now

Now we install the necessary packages. Busybox is a collection of common shell utilities and a shell that uses a very small memory footprint and will be our shell in the initramfs. cryptsetup installs the necessary utilities to encrypt and unlock encrypted partitions.

sudo apt-get install busybox cryptsetup

Now we create the initramfs. On normal desktop distributions of Linux, this step is usually run automatically whenever the kernel is update, but we have to do it manually.

sudo mkinitramfs -o /boot/initramfs.gz

Now we need to modify /boot/config.txt to have the bootloader load our new initramfs into memory. Add these lines at the end of the file.

initramfs initramfs.gz followkernel

Encrypting the Root Partition.

On another computer running Linux, mount the SD card. I’m using a Dell Mini 9, and coincidentally the SD card reader uses /dev/mmcblk0 like the Pi does. Your device name may vary, checking dmesg with dmesg | tail may be useful to learn what name your system uses for SD cards. Now create an image of the root partition on the SD card (partition 2). Make sure the file system is alright, then shrink it down to its minimum size. We’ll use this later to restore the existing system to the new, encrypted partition.

dd if=/dev/mmcblk0p2 of=/tmp/raspbian-plain.img bs=4M
e2fsck -f /tmp/raspbian-plain.img
resize2fs -M /tmp/raspbian-plain.img

If your non-Pi Linux computer doesn’t already have them installed, install the cryptsetup command.

sudo apt-get install cryptsetup

Now we’re going to create the encrypted partition. You should explicitly specify the key size and cipher as the default changed in version 1.6.0 os cryptsetup and Raspbian has an older version that doesn’t support the new cipher. The luksOpen command creates /dev/mapper/sdcard, which is the decrypted interface to the encrypted partition.

sudo cryptsetup -v -y --cipher aes-cbc-essiv:sha256 --key-size 256 luksFormat /dev/mmcblk0p2
sudo cryptsetup -v luksOpen /dev/mmcblk0p2 sdcard

Now copy the original filesystem data back to the newly created encrypted partition. This can sometimes take a while; it took just under ten minutes to copy 3.5GB with my setup. After that we’ll re-expand the filesystem to take up the whole partition.

sudo dd if=/tmp/raspbian-plain.img of=/dev/mapper/sdcard bs=4M
sudo e2fsck /dev/mapper/sdcard
sudo resize2fs /dev/mapper/sdcard

Now we need to configure the system on the SD card to use the new partition as its root. Change the root=/dev/mmcblk0p2 part to root=/dev/mapper/sdcard and add cryptdevice=/dev/mmcblk0p2:sdcard. We also need to change /etc/fstab on to use the new encrypted partition. The commands below will show you how to make the boot and main partition available. In the fstab file, change /dev/mmcblk0p2 to /dev/mapper/sdcard.

The last steps are to tell the Pi how to boot the new partition. First mount the boot partition and the new partition somewhere so we can access them easily.

mkdir /tmp/pi_root /tmp/pi_boot
sudo mount /dev/mmcblk0p1 /tmp/pi_boot
sudo mount /dev/mapper/sdcard /tmp/pi_root

Now edit the cmdline.txt file on the Pi’s boot partition (/tmp/pi_boot/cmdline.txt is you used the above commands) to tell the kernel what the new root partition is and how to access it. Change root=/dev/mmcblk0p2 to root=/dev/mapper/sdcard and add cryptdevice=/dev/mmcblk0p2:sdcard. Here’s what mine looks like after those changes. Yours might be a little different, but it should look similar.

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mapper/sdcard cryptdevice=/dev/mmcblk0p2:sdcard rootfstype=ext4 elevator=deadline rootwait smsc95xx.turbo_mode=N

Now we need to tell the rest of the system on the Pi about the new partition. Open up the fstab file on the Pi’s root partition (/tmp/pi_root/etc/fstab) and change /dev/mmcblk0p2 to /dev/mapper/sdcard. Now create a crypttab file (/tmp/pi_root/etc/crypttab) and add this line at the end. Note that those are tab characters seprarating each word, not spaces.

sdcard  /dev/mmcblk0p2  none    luks

Now unmount everything and put the SD card back in your Pi.

sudo umount /tmp/pi_boot /tmp/pi_root
sudo cryptsetup luksClose sdcard

First Boot

The first boot will fail. Have a USB keyboard and a screen hooked up to your Pi so you can work with the Busybox recovery shell. All you have to do is mount the root partition and then exit the shell, and everything else will take care of itself. Run these commands when you get an (initramfs) prompt.

cryptsetup luksOpen /dev/mmcblk0p2 sdcard
exit

Once the Pi is booted up the rest of the way, you need to re-create the initramfs so you get a nicer unlock prompt on future start ups.

sudo mkinitramfs -o /boot/initramfs.gz

You can now reboot. Instead of errors about not being able to find or mount the root partition, you should get a nice prompt asking for your unlock password. Enter it and press return, and the rest of the boot process should follow.

Time Problem

I found sometimes that mounting the decrypted root partition would fail because the last unmount time was far in the future. This is because the Pi can’t save the time between reboots like most other computers. To work around this, you can use the date command to set the time in the initramfs, and then mount the system. date takes the current time in MMDDhhmmYYYY, so November 4, 2013 at 13:30 would be 110413302013. To get to a shell to set the time, intentionally cause the unlock script to exit by entering an incorrect password three times. You’ll then have to wait about 30 seconds for a timeout, and then you’ll be at the (initramfs) prompt. Set the time to something reasonably close (as long as it’s within a day, you’re good) and then restart the unlocking script.

date 110413302013
/scripts/local-top/cryptroot

Remote Unlocking

Having to be physically at the Pi to unlock the disk can be a pain, but there is a way of unlocking it over SSH. The Dropbear SSH server is a very small and lightweight server that can be run from the initramfs. It will automatically add itself to the initramfs if it detects an encrypted partition on the system.

sudo apt-get install dropbear

There’s a minor bug in the initramfs scripts shipping with Dropbear in Raspbian. On line 296 of /usr/share/initramfs-tools/scripts/local-top/cryptroot, add /sbin/ just before blkid. It should look like this afterwards:

FSTYPE="$(/sbin/blkid -s TYPE -o value "$NEWROOT")"

You have to re-create the initramfs again to trigger Dropbear to generate SSH keys.

sudo mkinitramfs -o /boot/initramfs.gz

Now copy over the SSH key Dropbear generates (this is from the Pi, to another computer).

sudo scp /etc/initramfs-tools/root/.ssh/id_rsa username@othercomputer:~/.ssh/id_rsa_rpi_dropbear

Finally, edit initramfs’ authorized_keys file to have Dropbear show you the password prompt as soon as you connect.

sudo nano /etc/initramfs-tools/root/.ssh/authorized_keys

Add this chunk of text just before the ssh-rsa at the beginning of the file. This starts the unlock script, and once it has exited it stops the other instance of the unlock script so boot can continue.

command="/scripts/local-top/cryptroot && kill -9 `ps | grep -m 1 'cryptroot' | cut -d ' ' -f 3`"

And finally, rebuild initramfs for the last time (until you upgrade your kernel).

sudo mkinitramfs -o /boot/initramfs.gz

To test it out, restart your Pi, and then try logging in from another computer (this step is from another computer that has network access to the Pi).

ssh -i ~/.ssh/id_rsa_rpi_dropbear root@192.168.2.143

You should be asked to enter a password, and once a correct one has been entered the Pi will boot the rest of the way.

Conclusions

Write speed is greatly reduced, down to 4.2 MB/s from 12.9 MB/sec. Writing a continued chunk of data to the SD card also pegs the CPU at 100%. This results in a noticably slower startup and significant delays when something is being written to disk. This might improve if AES-XTS support is enabled in the Pi’s kernel and cryptsetup is updated to use it, but I’m not confident the improvement will be that substantial.


Letter Frequencies in Names of Virginia Cities

Recently I’ve noticed on Facebook a couple of trivia posts asking you to try to name a city in Virgina with a certain letter in it. As background, thses posts are coming from my friends living in Virginia. You also have to be at least somewhat familiar with Virginian municipal structure. Like other states, Virginia is divided up into counties. In most other states, the next step down is cities and/or towns. In Virginia, cities are independant and on the same level as counties. So the bar for being a city is pretty high, and many people can name a decent number of them if they put their mind to it. I wanted to find out if there were any letters not used in any cities. And because (like many programmers) I’m lazy and didn’t want to look through all of the cities manually I turned to Python to help me.

Start with some basic setup. Counter is a counted set. pprint provides a nicer way to print lists and other containers by adding line breaks and such. BeautifulSoup is a really easy to use HTML parser+navigator. Requests makes it easy to download things from the web.

from collections import Counter
import string
from pprint import PrettyPrinter

from bs4 import BeautifulSoup
import requests

# A prettier way to print
pprint = PrettyPrinter().pprint

Wikipedia provides a nice list of the cities in Virginia. I find all the tables (<table> elements) on the page, and the second one is the one I want. I then get a list of the text of all links (<a> elements) in that table.

city_response = requests.get('http://en.wikipedia.org/wiki/Cities_in_virginia')
city_soup = BeautifulSoup(city_response.content)

tables = city_soup.find_all('table')
# Python starts counting at 0 like many other programming languages
table = tables[1]
# There are some blank links, so drop those
cities = [link.string for link in table.find_all('a') if link.string]

Now run through the list of cities, and add each one to a Counter. Then I print out how many cities there are and a list of the letters used, sorted by how often they were used.

city_count = Counter()
for city in cities:
    # Add each letter of the lowercase city name to the Counter
    city_count.update(city.lower())

print("{} cities".format(len(cities)))
pprint(city_count.most_common())
39 cities
[('a', 35),
 ('r', 31),
 ('o', 30),
 ('n', 29),
 ('e', 27),
 ('s', 25),
 ('l', 24),
 ('i', 23),
 ('t', 17),
 ('h', 14),
 ('u', 11),
 ('c', 11),
 ('b', 10),
 ('f', 10),
 ('g', 10),
 ('p', 9),
 ('m', 9),
 ('d', 8),
 ('k', 7),
 ('v', 6),
 ('w', 6),
 (' ', 6),
 ('x', 4),
 ('y', 2),
 ('q', 1)]

I guess space (‘ ‘) counts as a letter, for those cities that are two words. Now I want to see which letters aren’t used . To do this I make a set of the letters that were found and subtract it from a set of all lowercase characters.

Sidenote: Confused from earlier where I said that Counter was a counted set? Well, it is but it isn’t a set so the set operators won’t work on it.

not_in_cities = set(string.ascii_lowercase) - set(city_count)
pprint(not_in_cities)
{'z', 'j'}

Bet you can’t name a city that contains the letter ‘J’!

And because they’re pretty big, let’s look at incorporated towns. Again, Wikipedia has a list of them that I’ll use. This list isn’t in a table, so it’s a bit harder to pull out.

town_response = requests.get('http://en.wikipedia.org/wiki/List_of_towns_in_Virginia')
town_soup = BeautifulSoup(town_response.content)

First I grab the main content area. Wikipedia displays the actual town names as elements (<li> elements) within unordered lists (<ul> elements), one for each letter in the alphabet. Because there are some other unordered lists in the content, I find all unordered lists that are only one level down (this is starting to get into the specifics of HTML, but it’ll be over soon). Then I go through each list, and save the link text for the first link I find in each list item. After than, the process is the same as the cities.

town_content = town_soup.find('div', id='mw-content-text')
lists = [element for element in town_content.find_all('ul') if element.parent == town_content]
towns = []
for ul in lists:
    for element in ul.find_all('li'):
        town_link = element.a
        # The references list has rel="nofollow", and we don't want the references
        if 'rel' not in town_link.attrs:
            towns.append(town_link.string)

for town in towns:
    town_count.update(town.lower())

print("{} towns".format(len(towns)))
pprint(town_count.most_common())
190 towns
[('e', 310),
 ('a', 278),
 ('l', 272),
 ('n', 258),
 ('o', 248),
 ('r', 232),
 ('i', 216),
 ('t', 200),
 ('s', 174),
 ('c', 154),
 ('h', 102),
 ('d', 100),
 ('u', 94),
 ('b', 88),
 (' ', 82),
 ('g', 82),
 ('m', 70),
 ('p', 66),
 ('w', 66),
 ('v', 64),
 ('y', 64),
 ('k', 60),
 ('f', 38),
 ('x', 16),
 ('j', 6),
 ('.', 4),
 ('q', 4),
 ('z', 2)]

not_in_towns = set(string.ascii_lowercase) - set(town_count)
pprint(not_in_towns)
set()

set() means that every letter is used (the ‘.’s are from St. Charles and St. Paul). Now I want to see what the counts are for the cities and towns combined. I create a new Counter from the cities` and then add the towns’.

city_town_count = Counter(city_count)
city_town_count.update(town_count)
pprint(city_town_count.most_common())
[('e', 337),
 ('a', 313),
 ('l', 296),
 ('n', 287),
 ('o', 278),
 ('r', 263),
 ('i', 239),
 ('t', 217),
 ('s', 199),
 ('c', 165),
 ('h', 116),
 ('d', 108),
 ('u', 105),
 ('b', 98),
 ('g', 92),
 (' ', 88),
 ('m', 79),
 ('p', 75),
 ('w', 72),
 ('v', 70),
 ('k', 67),
 ('y', 66),
 ('f', 48),
 ('x', 20),
 ('j', 6),
 ('q', 5),
 ('.', 4),
 ('z', 2)]


Autotools Trick: config.site

This is a quick trick for those who have to continually set up common settings when using Autotools style configure scripts. One of the initial steps a configure script does is to look for a $prefix/share/config.site file and then to execute its contents. An example of how this may be useful is if you have some common libraries that are not on the default search paths for your compiler and you want CFLAGS and LDFLAGS set automatically. In my case I commonly use the Nvidia OpenCL headers, which on the machines I use are installed to /usr/local/cuda/include. To use them, I could have a config.log file like so in my default installation prefix.

CPPFLAGS="${CPPFLAGS} -I/usr/local/cuda/include"

Now when I run configure, it picks up that additional flag for the C preprocessor.

A more complicated example is if you maintain a seperate prefix. I do this in my home folder for my CS department account. Because my home folder is shared over NFS to all of the department’s machines, and many of them have different architectures and operating environments, I keep a prefix for different classes of machines. For example:

~/local/
        fast-sparc/
        fast-ubuntu/
        nv-s1070/
        nv-c870/
        src/

The src directory is just a repository for all of the source packages that I end up installing in the other directories. In my .bashrc I export a variable, LOCAL_PREFIX, that is set to the prefix for the machine I’m logging into. Then, all I need to do is ./configure --prefix=$LOCAL_PREFIX and the compiler flags are properly set for that prefix. Another possibility is to export the CONFIG_SITE variable set to the path to the config.site file for that machine configuration.

More details about config.site can be found in the Autoconf manual.