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:
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"
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"
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
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
(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
Unmount the filesystem and unload the key from memory