Date: Sun, 05 Jul 2026 17:55:35 +0000 From: Benedict Reuschling <bcr@FreeBSD.org> To: doc-committers@FreeBSD.org, dev-commits-doc-all@FreeBSD.org Subject: git: 3590b05e4b - main - handbook/zfs: Add new section about ZFS native encryption Message-ID: <6a4a9a97.323f3.56189ee9@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by bcr: URL: https://cgit.FreeBSD.org/doc/commit/?id=3590b05e4b638ebc1fdf54e2de325e9d6c768045 commit 3590b05e4b638ebc1fdf54e2de325e9d6c768045 Author: Benedict Reuschling <bcr@FreeBSD.org> AuthorDate: 2026-07-05 17:53:32 +0000 Commit: Benedict Reuschling <bcr@FreeBSD.org> CommitDate: 2026-07-05 17:53:32 +0000 handbook/zfs: Add new section about ZFS native encryption This change adds a new section describing how to use ZFS native encryption including examples to the handbook. It is based on my own training material, the open-zfs.org documentation on the subject and the corresponding man pages (zfs-load-key(8), zfs-unload-key(8)). I ran my writeup through an AI for grammar, typo and active voice fixes, instructing it to not make any changes to the text itself. PR: 264531 Reviewed by: michaelo@ Event: Halifax Hackathon 202606 Differential Revision: https://reviews.freebsd.org/D57889 --- .../content/en/books/handbook/zfs/_index.adoc | 299 +++++++++++++++++++++ 1 file changed, 299 insertions(+) diff --git a/documentation/content/en/books/handbook/zfs/_index.adoc b/documentation/content/en/books/handbook/zfs/_index.adoc index e1738f9716..2ee5dd7dbf 100644 --- a/documentation/content/en/books/handbook/zfs/_index.adoc +++ b/documentation/content/en/books/handbook/zfs/_index.adoc @@ -2645,6 +2645,305 @@ Another restriction aimed at preventing abuse: non-`root` users must own the mou `zfs allow _someuser_ allow _mydataset_` gives the specified user the ability to assign any permission they have on the target dataset, or its children, to other users. If a user has the `snapshot` permission and the `allow` permission, that user can then grant the `snapshot` permission to other users. +[[zfs-native-encryption]] +== ZFS Native Encryption + +ZFS supports native encryption of datasets and the data stored within them. +This was not always the case, as previous solutions relied on FreeBSD's GELI-based encryption. +While this approach is secure, it was not as easily portable to non-FreeBSD systems that lacked GELI support. +With ZFS native encryption, encrypted datasets can be used on other systems that support this pool feature without relying on third-party components or operating system-native encryption. + +Another benefit of ZFS native encryption is that datasets do not need to be decrypted for administrative tasks such as snapshots, replication, or scrub operations. +ZFS data integrity checks work just as well with natively encrypted datasets, and compression also works encrypted datasets. + +Different datasets on the same pool can use separate keys independently. +Consider a central file server where different users store their data in encrypted home datasets. +User A has a different key than User B. +Both users can work side by side and choose to unencrypt their datasets as needed without knowing each other's decryption keys. +The decryption method can also differ completely between users without affecting one another. +This allows sensitive data from different users to be stored on the same pool while remaining protected by separate keys. + +Before using ZFS native encryption, be aware of the following: + +- Encryption is applied at the dataset level, not the pool level. +- Booting from encrypted ZFS datasets is not yet supported in FreeBSD's loader. +- Encryption can only be enabled when a dataset is created, not afterward. +- The block cipher, key length, and encryption mode cannot be changed after they have been set. +- ZFS does not encrypt metadata. + +The last point may seem like a drawback compared to full-disk encryption. +However, it provides greater flexibility by allowing different keys to protect datasets within the same pool instead of requiring a single key or passphrase to unlock the entire pool. +Specifically, ZFS encrypts the following: + +* file and zvol data, +* file attributes, +* ACLs, +* permission bits, +* directory listings, +* FUID mappings, +* userused/groupused data, and +* deduplicated data + +Some ZFS metadata and information remains unencrypted, including the following: + +* pool structure and name, +* dataset and snapshot names, +* dataset hierarchy, +* properties, +* file size, +* file holes, and +* deduplication tables + +This unencrypted metadata allows ZFS to perform routine maintenance operations, such as `zfs scrub`, without first decrypting the data. +As a result, ZFS can detect and repair corrupted encrypted data that is encrypted data without accessing the unencrypted file contents. + +To encrypt existing unencrypted data, copy it to a dataset with encryption enabled. +This allows data to be migrated to a secure location within the pool, provided the passphrase is supplied to authorize access. +Typically, this is done by loading the encryption key into memory after confirming knowledge of a secret such as a passphrase. +Once the key has been loaded, the dataset contents become accessible for reading and writing. +To re-secure the dataset, unload the key from memory. +ZFS then makes the dataset inaccessible until the key is loaded again. + +When encryption is enabled, some ZFS operations behave differently. +ZFS applies compression before encryption to preserve compression ratios. +While ZFS normally uses 256-bit checksums, encryption replaces them with a 128-bit checksum and a 128-bit Message Authentication Code (MAC) provided by the encryption suite. +This provides additional protection against malicious data modification. + +When deduplication is used together with encryption, ZFS performs deduplication only within the encrypted dataset, its snapshots, and its clones. +This prevents information from leaking between encrypted datasets. +The tradeoff is lower deduplication efficiency because ZFS cannot compare checksums across the entire pool. +Even so, deduplication combined with encryption still reveals which blocks are identical and incurs additional CPU overhead for each block written. + +Other limitations apply when using ZFS encryption. +The `embedded_data` feature cannot be used with encryption. +Datasets with encryption enabled also cannot have the `copies` property set to 3, because the implementation stores encrypted metadata in the location where the third copy would normally reside. + +[[zfs-encrypted-dataset-creation]] +=== Creating an Encrypted Dataset + +ZFS encryption is enabled by setting the `encryption=on` property when creating a dataset. +Setting this property outside of a `zfs create` command does not enable encryption retroactively, as it only takes effect during dataset creation. +In the following example, a passphrase is used when mounting the encrypted dataset after it has been created. + +[source,shell] +.... +# zfs create -o encryption=on -o keyformat=passphrase -o keylocation=prompt zroot/secretdata +Enter passphrase: +Re-enter passphrase: +.... + +If the passphrase is shorter than eight characters, ZFS rejects it and does not create the dataset. +After the passphrase has been entered correctly twice, ZFS creates and mounts the dataset in the pool. + +Any data stored in the encrypted dataset remains encrypted while it resides there. +Copying data from an encrypted dataset to an unencrypted location decrypts the data during the copy operation. +The newly created dataset can then be mounted at a different location if necessary. + +[source,shell] +.... +# zfs set mountpoint=/secretdata zroot/secretdata +# echo "Hello FreeBSD!" > /secretdata/message +# cat /secretdata/message +Hello FreeBSD! +.... + +As expected, the data is currently available for reading and writing by anyone who has access to the dataset. + +[[zfs-encrypted-dataset-properties]] +=== Properties of Encrypted Datasets + +Listing the ZFS native encryption properties for a dataset is done with the following command: + +[source,shell] +.... +# zfs get encryption,keylocation,keyformat zroot/secretdata +NAME PROPERTY VALUE SOURCE +mypool/secretdata encryption aes-256-gcm - +mypool/secretdata keylocation prompt local +mypool/secretdata keyformat passphrase - +.... + +The `encryption` property shows `aes-256-gcm`, which is currently the default encryption method used by ZFS. +As indicated by the `SOURCE` column, neither this property nor the `keyformat` property can be changed using `zfs set` after the dataset has been created. +See the crossref:zfs-change-encryption-key[zfs-change-encryption-key, changing the encryption key] section for information on how to modify some of these properties. +The following table lists the encryption-related properties used by ZFS: + +[.informaltable] +[cols="10%,90%"] +|=== + +|[[zfs-term-encryptionroot]]encryptionroot +|The dataset from which the current dataset inherits its encryption key. +Dataset clones share the encryption key of their originating dataset. + +|[[zfs-term-keyformat]]keyformat +a|The format of the encryption key. Possible values are: `raw`, `hex`, and `passphrase`. Both `raw` and `hex` keys must be 32 bytes long and contain random values. +|[[zfs-term-encryption]]encryption +a|The cipher used for encryption, consisting of the block cipher, key length, and encryption mode. Both the `encryption` and `keyformat` properties must be specified when the dataset is created. Possible values for the `encryption` property are: + + * `off` (default), + * `on`, + * `aes-128-ccm`, + * `aes-192-ccm`, + * `aes-256-ccm`, + * `aes-128-gcm`, + * `aes-192-gcm`, and + * `aes-256-gcm` (current standard when `on` is set). + +This property cannot be changed once it has been set. + +|[[zfs-term-keystatus]]keystatus +a|Indicates whether an encryption key has been successfully loaded. Possible values are `none`, `available`, and `unavailable`. + +|[[zfs-term-keylocation]]keylocation +a|Specifies the location from which to load the encryption key. Possible values are: + +* `prompt`, +* `file://path/to/file`, +* `http://some.address`, and +* `https://other.address` + +|[[zfs-term-pbkdf2iters]]pbkdf2iters +a|The number of iterations used when deriving an encryption key from a passphrase. This forces an attacker to perform a large number of computationally expensive hash operations. As computing power increases, this value should also be increased from its current default of 350000. +|=== + +[[zfs-unload-encryption-key]] +=== Unloading the Encryption Key + +Protecting the data stored in an encrypted dataset requires two steps: first unmount the dataset, then unload the encryption key from memory. +This distinction is important. +An unmounted dataset is not protected if its encryption key remains loaded in memory. +Always run `zfs unload-key` to protect the data. +To access the dataset again, load the key and provide the passphrase as described in the crossref:zfs-load-encryption-key[zfs-load-encryption-key, loading the encryption key] section. +ZFS prevents `zfs unload-key` from running while the dataset is still mounted. + +[source,shell] +.... +# zfs unmount zroot/secretdata +# zfs unload-key zroot/secretdata +.... + +Running `zfs unload-key` multiple times results in an error indicating that the key has already been unloaded. + +[[zfs-load-encryption-key]] +=== Loading the Encryption Key + +Before an encrypted dataset can be used like any other dataset, its encryption key must be loaded and the passphrase provided. +At this point, the `keystatus` property is still set to `unavailable`, and mounting the dataset fails because the key has not yet been loaded: + +[source,shell] +.... +# zfs get keystatus zroot/secretdata +NAME PROPERTY VALUE SOURCE +zroot/secretdata keystatus unavailable - +# zfs mount zroot/secretdata +cannot mount 'zroot/secretdata': encryption key not loaded +.... + +This indicates that the dataset is encrypted and requires an encryption key. +To mount the dataset, run `zfs load-key` and provide the passphrase when prompted: + +[source,shell] +.... +# zfs load-key zroot/secretdata +Enter passphrase for 'zroot/secretdata': +# zfs get keystatus zroot/secretdata +NAME PROPERTY VALUE SOURCE +zroot/secretdata keystatus available - +.... + +Next, mount the dataset to make it accessible through the filesystem hierarchy: + +[source,shell] +.... +# zfs mount zroot/secretdata +# mount|grep secretdata +zroot/secretdata on /secretdata (zfs, local, noatime, nfsv4acls) +.... + +To load multiple encryption keys recursively, specify the `-r` option with `zfs load-key`. +If a key has already been loaded, ZFS reports this with an error message. + +[source,shell] +.... +# zfs load-key zroot/secretdata +Key load error: Key already loaded for 'zroot/secretdata'. +.... + +The `keystatus` property also confirms that the key has been loaded by reporting the value `available`. +To verify whether a passphrase is correct without loading the key, use the `-n` option to perform a dry run. + +[[zfs-change-encryption-key]] +=== Changing the Encryption Key + +ZFS also supports changing encryption keys, such as replacing a passphrase. +This operation does not require re-encrypting the dataset. +Run the following command to change the encryption passphrase: + +[source,shell] +.... +# zfs change-key zroot/secretdata +Enter new passphrase for 'zroot/secretdata': +Re-enter new passphrase for 'zroot/secretdata': +.... + +Running `zfs change-key` requires the current key to be loaded. If it is not, +ZFS still prompts for the new key but then displays a warning that the current key has not been loaded. + +The zfs change-key command can also modify the `keylocation`, `keyformat`, and `pbkdf2iters` properties by specifying them with the `-o` option. +Running `zfs change-key` on an encrypted child dataset makes it an encryption root if it is not one already. +To prevent this behavior and continue inheriting the parent's key, specify the `-i` option. + +Be aware that changing the encryption key of a parent dataset also changes the key for child datasets that inherit it. +Consequently, those child datasets continue to use the parent's encryption key. +If a child dataset should use a different key, either specify a different `keyformat` when creating it or run `zfs change-key` on the child dataset. +Doing so creates a new encryption root for the child and breaks its encryption inheritance from the parent. + +ZFS clones of an encrypted dataset always use the encryption key of their origin dataset. +As a result, the `keystatus`, `keyformat`, `keylocation`, and `pbkdf2iters` properties are not inherited like other dataset properties. +Instead, they use the values defined by the encryption root. +To determine the encryption root, use the read-only `encryptionroot` property. + +If an attacker compromises an encryption key, changing the passphrase with `zfs change-key` does not necessarily protect existing or newly written data. +New data continues to be encrypted with the same master key as the existing data. +If an attacker obtains both a user key and its corresponding wrapped master key, running `zfs change-key` does not overwrite the previous master key on disk. +As a result, the old master key may remain available for forensic analysis for an indeterminate period. + +If a master key has been compromised, the preferred solution is to securely erase the underlying storage devices and create a new pool. +Afterwards, restore the data from a backup to the new pool. +Alternatively, create a new encrypted dataset, migrate the data using `zfs send` and `zfs receive`, and then run `zpool trim --secure` to erase the freed space. +If the underlying hardware does not support secure TRIM, use `zpool initialize` instead. + +[[zfs-encrypted-send]] +=== Sending Encrypted Datasets + +ZFS replication is based on sending dataset snapshots, which can also be created from encrypted datasets. +The resulting snapshots remain encrypted. + +[source,shell] +.... +# zfs snapshot zroot/secretdata@snap1 +.... + +In addition to crossref:zfs-load-encryption-key[zfs-send-ssh, sending encrypted backups over SSH], using the `-w` (raw) option with `zfs send` transfers the encrypted data blocks to the target pool. +A raw send provides several advantages: + +* the receiving system never sees plaintext data. +* the receiving system does not require the passphrase since no decryption takes place. +* Backups can be sent without first loading the encryption key. +* an untrusted system can receive the encrypted data but cannot decrypt or modify it without the encryption key. + +To send the snapshot created above as a raw stream to the dataset `secret` in the pool `backup`, enter the following command: + +[source,shell] +.... +# zfs send -w zroot/secretdata@snap1 | zfs recv backup/secret +.... + +Without the `-w` option, `zfs send` transmits the data in unencrypted form. +The receiving system may then re-encrypt the data using a different key. +However, doing so prevents future incremental raw sends to that destination. + [[zfs-advanced]] == Advanced Topicshome | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a4a9a97.323f3.56189ee9>
