I have always been an advocate on storage security (all types of security, actually). I like how iOS devices keep all files encrypted, even if you do not set a passcode on the device. They do this to facilitate quick erasure of files on the device — to erase all the data, they simply wipe the master key.
Erasing magnetic storage media isn’t difficult, but it is time-consuming. For solid state media such as SSDs and flash drives, the wear-leveling makes it difficult to ensure that all flash blocks have been securely overwritten. The answer to this is to encrypt everything.
Recently I have been busy building a Linux-based NAS and I decided to put this to practice.
I decided to opt for a USB 3 flash drive as my OS drive. It’s half the price of the smallest SSD I could get, so it makes more economic sense. The main problem is securely booting from that drive — to the BIOS it’s just a removable device, but let’s ignore evil maids for now.
The OS drive contains all kinds of keys: SSH host keys, SSL private keys, DNS update keys, etc. Encrypting the OS drive ensures that these keys do not fall into the wrong hands.
CentOS Installation onto a USB drive
To get CentOS to install on a USB device, specify
expert when booting the install DVD. You should also use the graphical installer as the text-based installer doesn’t seem to honour “expert” mode.
CentOS defaults to using LVM on your OS disk, but since I know what I’m doing, I prefer to create a
/boot partition and a
/ (root) partition as
sda2, respectively. The
/boot partition only consumes 28 MB, so allocating 128 MB should be more than enough.
Automatic volume unlocking
I didn’t want to type my password on every boot, in case the machine needed to be rebooted when I’m not around, and this is also made cumbersome by the fact that the server runs headless (and keyboardless).
This may seem counter-intuitive, but recall that the primary purpose for (my) encryption was quick erasure. That means that the key should not reside on the same volume as it cannot be securely erased. A common solution would be to store the key on a separate USB flash drive, which CentOS natively supports through dracut. By specifying the path to the key file in
crypttab, dracut mounts and reads the key file automatically during boot time.
However, since the OS is already on a USB drive, I do not want yet another USB drive just to hold the encryption key. Instead, I decided to generate the key automatically from unique system features, such as the board serial number and MAC addresses of network cards. These features are text-based but LUKS ensures that it is of decent quality by passing it through PBKDF2, just like any user-entered password. This was inspired by the Gauss malware, which encrypts its payload with a key derived using the system’s
%PATH% environment variable and name of the directory in
%PROGRAMFILES%. This means the payload will only decrypt correctly on its target system and analysts will have to resort to using brute-force.
Automatic unlocking is performed through a dracut module I wrote. It installs a script to generate the key file during boot and alters
crypttab to read the generated key file instead. Before it will work, you will have to manually add this generated key to the LUKS volume using the
Upon boot, the LUKS volume should then unlock automatically. Keep your password around as a backup. When you change your mainboard, upgrade the BIOS or CPU, you will need it to unlock your system and generate a new key.
Of course, this doesn’t help if your entire system is stolen or confiscated, but it does help if you need to discard your flash drive or return it for servicing.
I use a software RAID setup for my data drives, using
mdadm. This creates the RAID superblock on each disk, which stores information about the array. Immediately on top of that, I use plain
dm-crypt as opposed to LUKS to avoid yet another header being stored on disk.
Wiping the RAID superblock just leaves the disk with encrypted data from
dm-crypt. Since no other headers are present in an unencrypted format, it is impossible for an attacker to make sense of the encrypted data (partition boundaries, or filesystem superblocks for example).
How safe is this?
I will assume it is relatively tedious for an attacker to gather all the BIOS information for each motherboard, and for each BIOS version (FYI I’m not using the latest BIOS version), and each CPU model. On top of that, the attacker has to know the serial number of the motherboard and the MAC addresses of your network cards. Note that half of each MAC address (the OUI part) is leaked through the
modprobe calls in the dracut init scripts.
This scheme also includes the 128-bit LUKS volume UUID as part of the generated key to deter the construction of rainbow tables. (There is already a salt in the LUKS header associated with each key slot.)
Assuming you have 2 network cards, and there are 5 motherboard manufacturers, each with 5 different models and each supporting 5 processor variants, it would yield 35,184,372,088,832,000 permutations (248 x 5 x 5 x 5). Assuming that each key can be computed at a rate of 1 key per second, that would take at least 1,115,689,120 years. However, LUKS tries to make each key difficult to generate.
LUKS runs your password and this generated key through several thousand rounds of the PBKDF2 algorithm, calibrated to take about 1 second on your machine (you will be disadvantaged if you are running this on a Raspberry Pi or something). I have made the
add-key.sh script to take 3 seconds instead, since I seldom reboot my machine. You should increase this value to something as high as you can tolerate to deter bruteforcing, keeping in mind Moore’s Law and hardware acceleration (e.g. using GPUs). Your backup password also occupies a key slot and can be bruteforced, so select a complex password and increase its PBKDF2 iteration time.
Use at your own risk.