Zum Inhalt

OpenZFS supports native encryption for zfs filesystems. As passphrase a raw byte stream can be used. So it is possible to combine ZFS native encryption with a HSM.

The passphrase is a result of the key derivation function (KDF) from openssl for elliptic curves. Therefor a private key and a public key is necessary. The KDF returns an 32 Bit value, which can be used as raw passphrase for the ZFS encrpytion.

In this example the elliptic curve prime256v1 (aka NIST P-256, secp256r1) is used.

Initiate the Hardware

For the setup, a Nitrokey HSM 2 is used (in the following only HSM called). The first action is to initialise the HSM. The vendor suggests 3537363231383830 as SO-PIN and 648219 as User-PIN. I also recommend the tutorial from raymii if you want to dive deeper into the setup of HSM.

Initialise the HSM with default pins:

$ sc-hsm-tool --initialize --so-pin 3537363231383830 --pin 648219 --label "ZFS Crypto"

The next step is to generate the master key. The label helps to identify the key, but there is no specified schema:

$ pkcs11-tool --login --pin 648219 --keypairgen --key-type EC:prime256v1 --label "keyname:zfs-master"
As output something similar should be printed on the screen. In this example the key has the id f2a69ae949e5e3f51886e31aa81a7c433269b167
Using slot 0 with a present token (0x0)
Key pair generated:
Private Key Object; EC
  label:      keyname:zfs-master
  ID:         f2a69ae949e5e3f51886e31aa81a7c433269b167
  Usage:      sign, derive
  Access:     none
Public Key Object; EC  EC_POINT 256 bits
  EC_POINT:   044104b53ad19a56c2557d8f83d4e82e88828dcf14aeb947e1f184aefe4badd42f15cf3b62a25602d2c696fa6b1a2ceced362085dff898027056dee3b2ed4a80e8970b
  EC_PARAMS:  06082a8648ce3d030107
  label:      keyname:zfs-master
  ID:         f2a69ae949e5e3f51886e31aa81a7c433269b167
  Usage:      verify, derive
  Access:     none

As described, two keys are necessary to derive the passphrase. The recommendation is to use the master key (keyname:zfs-master) and a separate key for every ZFS filesystem. So the next step is to generate a second key:

$ pkcs11-tool --login --pin 648219 --keypairgen --key-type EC:prime256v1 --label "keyname:zfs-crypt-storage-1"
As output something similar should be printed on the screen. In this example the key has the id f8a2537ed2d484ad18325c051a17ef04d36f8c5d:
Using slot 0 with a present token (0x0)
Key pair generated:
Private Key Object; EC
  label:      keyname:zfs-crypt-storage-1
  ID:         f8a2537ed2d484ad18325c051a17ef04d36f8c5d
  Usage:      sign, derive
  Access:     none
Public Key Object; EC  EC_POINT 256 bits
  EC_POINT:   0441042f6a00018cabca1162ef73011cf5a8358951a71b15b00c88cf223e87669075ec7fe10f14c5d28900ae66f3c6e0a41e3ba6b7cd7b0dba3c18f7af2ddf89e971c7
  EC_PARAMS:  06082a8648ce3d030107
  label:      keyname:zfs-crypt-storage-1
  ID:         f8a2537ed2d484ad18325c051a17ef04d36f8c5d
  Usage:      verify, derive
  Access:     none
To use the KDF, a private key (in this example keyname:zfs-master) and a public key (in this example keyname:zfs-crypt-storage-1) is necessary. The private key can be exported to the filesystem and stored beside the encrpyted ZFS filesystem. After exporting the public key keyname:zfs-crypt-storage-1, its private key can be deleted. Keep in mind, that also the public key will be deleted. So exporting should be the first step!

Export the public key keyname:zfs-crypt-storage-1 (f8a2537ed2d484ad18325c051a17ef04d36f8c5d) and convert the key into PEM format:

$ pkcs11-tool \
    --read-object \
    --type pubkey \
    --id f8a2537ed2d484ad18325c051a17ef04d36f8c5d | \
    openssl ec -pubin -inform DER -outform PEM -pubout \
    -out zfs-crypt-storage-1.pub.pem

As output something similar should be printed on the screen:

$ cat zfs-crypt-storage-1.pub.pem 
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL2oAAYyryhFi73MBHPWoNYlRpxsV
sAyIzyI+h2aQdex/4Q8UxdKJAK5m88bgpB47prfNew26PBj3ry3fielxxw==
-----END PUBLIC KEY-----

If the export was sucessfull, the private key keyname:zfs-crypt-storage-1 can be deleted:

$ pkcs11-tool \
    --delete-object \
    --type privkey \
    --login \
    --id f8a2537ed2d484ad18325c051a17ef04d36f8c5d

Create the encrypted ZFS filesystem

To use openssl with a HSM, the pkcs11-url needs to be provided to the commandline tool. In this example our HSM has the serial number DENK02123456.

To get detailed information about the connected devices (HSM, Smart Cards, ...), the tool p11tool is very helpful. In normal the case, business PCs/Laptops has smart card reader on board and will show here additional devices beside the HSM.

For this example the output of the HSM look something like:

$ p11tool --list-tokens
pkcs11:model=PKCS%2315%20emulated;manufacturer=www.CardContact.de;serial=DENK02123456;token=ZFS%20Crypto%20%28UserPIN%29%00%00%00%00%00%00%00%00%00%00%00%00
In most cases, only the serial number (pkcs11:serial=DENK02123456) is necessary to address the HSM. To get the pkcs11-url for the relevant key, an overview can be printed out with the following command:
$ p11tool --list-all "pkcs11:serial=DENK02123456"
Object 0:
    URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=www.CardContact.de;serial=DENK02123456;token=ZFS%20Crypto%20%28UserPIN%29%00%00%00%00%00%00%00%00%00%00%00%00;id=%F2%A6%9A%E9%49%E5%E3%F5%18%86%E3%1A%A8%1A%7C%43%32%69%B1%67;object=keyname%3Azfs-master;type=public
    Type: Public key (EC/ECDSA-SECP256R1)
    Label: keyname:zfs-master
    ID: f2:a6:9a:e9:49:e5:e3:f5:18:86:e3:1a:a8:1a:7c:43:32:69:b1:67

In most cases, the url can be shortend to pkcs11:serial=DENK02123456;id=%F2%A6%9A%E9%49%E5%E3%F5%18%86%E3%1A%A8%1A%7C%43%32%69%B1%67. Take in mind, that the key ID of keyname:zfs-master (f2a69ae949e5e3f51886e31aa81a7c433269b167) is given as hex value and needs to be written with a leading % for every hex group (%F2%A6%9A%E9%49%E5%E3%F5%18%86%E3%1A%A8%1A%7C%43%32%69%B1%67).

$ openssl pkeyutl -derive \
    -engine pkcs11 \
    -keyform engine \
    -peerform PEM \
    -inkey "pkcs11:serial=DENK02123456;id=%F2%A6%9A%E9%49%E5%E3%F5%18%86%E3%1A%A8%1A%7C%43%32%69%B1%67" \
    -peerkey zfs-crypt-storage-1.pub.pem \
    -out 00_11_key.bin

To encrypt the filesystem a key is derived with ECDH from the public and private key. Important: OpenSSL mention in the documentation, that the derived key should not be used directly. Hash the value instead: "Also note that the derived shared secret is not suitable for use directly as a shared key. Typically the shared secret is passed through some hash function first in order to generate a key."

Create encrypted ZFS Filesystem

$ openssl pkeyutl -derive \
    -engine pkcs11 \
    -keyform engine \
    -peerform PEM \
    -inkey "pkcs11:serial=DENK02123456;id=%F2%A6%9A%E9%49%E5%E3%F5%18%86%E3%1A%A8%1A%7C%43%32%69%B1%67" \
    -peerkey zfs-crypt-storage-1.pub.pem \
    | sha512sum | awk '{print $1}' \
    | zfs create -o encryption=aes-256-ccm -o keyformat=raw pool1/crypthsm            

Mount encrypted ZFS

$ openssl pkeyutl -derive \
    -engine pkcs11 \
    -keyform engine \
    -peerform PEM \
    -inkey "pkcs11:serial=DENK02123456;id=%F2%A6%9A%E9%49%E5%E3%F5%18%86%E3%1A%A8%1A%7C%43%32%69%B1%67" \
    -peerkey zfs-crypt-storage-1.pub.pem \
    | sha512sum | awk '{print $1}' \
    | zfs load-key pool1/crypthsm

Mount the filesystem

zfs mount pool1/crypthsm

Unmount the filesystem and unload the key from memory

zfs unmount -a && zfs unload-key -a