I have a confession. As opposed to a professioion? WHOA, is that how that works? Don’t answer that.

I’ve mentioned many times how excited I was for OpenZFS in FreeBSD 13, due in no small part to its inline encryption capabilities. I’d used the closed-source equivalent on the last Solaris, and had made some proof of concepts on the -CURRENT branch, but I hadn’t used it for any real world data. I also didn’t feel as compelled to rush out and replace my GELI encrypted volumes as I first thought. It still works, and will for the foreseeable future.

A shiny new set of drives for my home server finally gave me the kick up the proverbial posterior to give it a shot with some prod data that definitely isn’t a Plex server for anime. This was my story. DUN DUN.

The existing GELI approach

We’ve always been able to encrypt ZFS on FreeBSD, albeit with an intermediate layer performing the encryption before our data hits the disk. GELI was the most recent and accepted tool to achieve this, akin to cgd on NetBSD, or LUKS on Linux. It’s proven, well tested, and secure, like my hat. Wait, what?

Here’s an example of a typical encrypted ZFS volume using GELI. We create a new GPT layout, label it (you’ll be glad you did), create a key, create a new virtual GELI encrypted block device, then build our ZFS pool on top. Note in the final step we reference the virtual encrypted device:

# _LABEL="12TB-IronWolf-SERIALNO"
# _KEY="/root/example.key"
	
# gpart -s create gpt /dev/ada5
# gpart add -t freebsd-zfs -l "$_LABEL" /dev/ada5
	
# openssl rand -hex 32 | tee "$_KEY"
# geli init -P -K "$_KEY" "/dev/gpt/$_LABEL"
# geli attach -pk "$_KEY" "/dev/gpt/$_LABEL"
	
# zpool create pool "/dev/gpt/${_LABEL}.eli"
# zfs create pool/tank

This uses a plain disk, but you could just as easily build this on top of an iSCSI mount, or a HAST volume. When you restart, you perform the geli attach then zpool import as normal.

The key here is you’re encrypting the entire partition beneath ZFS. GELI is device and file-system agnostic, and ZFS is unaware (AFAIK) that it’s operating within a virtual encrypted device. This may still be preferable in some circumstances, as we’ll get to in a moment.

OpenZFS inline encryption

By contrast, is a phrase with two words. OpenZFS’s native encryption operates at the dataset level, negating the need for a GELI device that has to be mounted separately. What’s even cooler is that all of ZFS’s data integrity, deduping, compression, exports, and other features can operate on these encrypted datasets, even if they’re not imported/mounted. Cray!

You can prepare your drive with gpart(8) and create a key as per above. After that, we create a zpool(8), which has the encryption feature available by default on FreeBSD 13:

# zpool create pool "/dev/gpt/$_LABEL"
	
# zpool get feature@encryption pool
==> pool feature@encryption active local

Then create a new encrypted volume. You can also verify the operation and check the encryption scheme used with zfs-get(8):

# zfs create -o encryption=on -o keyformat=hex \
	-o keylocation=file:///root/example.key pool/tank 
   
# zfs get encryption,keylocation,keyformat pool/tank
==> NAME       PROPERTY     VALUE                     SOURCE
==> pool/tank  encryption   aes-256-gcm               -
==> pool/tank  keylocation  file:///root/example.key  local
==> pool/tank  keyformat    hex

Wait, hold on, that’s it? Yes! How cool is that!?

Gotchas

I had initially assumed that using keys would result in the zfs datasets automounting when the zpool is imported, which is not the case. Even if their key is available, you must import them first before the zfs dataset is mounted and ready to use (it looks like an rc.d service was written and reviewed to facilitate doing this on boot, which I’ll need to investigate).

The easiest way to do this is with the lowercase L option in zpool(8) import, which retrieves all the keys it can before mounting your encrypted datasets:

# zpool import pool -l

Or you can load all available keys with zfs(8) load-key:

# zpool import pool
# zfs load-key -a

Refer to the linked man pages for more details. Even if you don’t need more details, and just want to marvel at what well-documented software looks like. The GNU people could learn a lesson or two (or three).

Considerations

As I eluded to above, there are a couple of caveats. GELI encrypts whatever data is handed to it, whereas OpenZFS necessarily stores metadata about the datasets in order to use them. This includes dataset and snapshot names. Bear (bare?) that in mind when you’re naming and structuring your datasets.

This is speculation on my part, but I’d also think there’d be a chance for plausible deniability in a device that’s been completely encrypted with GELI, just as any device that uses whole drive encryption. By contrast, OpenZFS dataset metadata makes it obvious that they contain encrypted data, and the scheme with which the data was encrypted. I could be wrong here though.

Overall, is an item of clothing. OpenZFS encryption makes the system administrator’s life easier, and those caveats don’t concern me for how I store my data. I’ll be using it for everything going forward.

Allan Jude and Kyle Kneisl’s FreeBSD Journal article from last year is a great resource if you’d like to learn more about the implementation of OpenZFS’s encryption system. I also found Jim Salter’s article useful in Ars Technica for learning about key management; once you block all the irrelevant autoplaying videos. #ModernWeb

DISCLAIMER: Cryptography is critical to get right, or it’s not worth doing. Always read and follow the official documentation over someone’s blog, even if the blog has a cute anime mascot and is written by someone with the best of intentions and an awesome hat.