zfs icon indicating copy to clipboard operation
zfs copied to clipboard

Passing Encryption Keys Through the Send Stream (Out-of-Band Header Mechanism)

Open CurlyMoo opened this issue 1 month ago • 2 comments

Describe the feature you would like to see added to OpenZFS

Currently, receiving an unencrypted stream into an encrypted dataset requires the target to have its encryption key loaded. If the key needs to come from the sender, using keylocation=prompt is impossible because stdin is already occupied by the incoming zfs send stream.

This leads to the awkward situation where the only way to supply the key to the receiver is:

  • load the key on the target beforehand (exposing data while the dataset is unlocked), or
  • use a key file on disk (which many users do not want on their backup system).

Proposed Mechanism

Allow the sender to pass a passphrase (or raw key) before the main send stream, using a small, well-defined “out-of-band header,” similar in concept to HAProxy’s PROXY_PROTOCOL. With PROXY_PROTOCOL

When HAProxy forwards a TCP connection to a service such as nginx, it can prepend a small, well-defined header to the beginning of the TCP stream. This header contains information like the original client IP address. Because this header is not part of a normal TCP payload, nginx must be explicitly configured to expect the PROXY_PROTOCOL format so it can properly recognize the header, decode the metadata, and then continue processing the rest of the TCP stream exactly as if it had arrived directly.

For example:

echo "password" \
  | zfs send -Ro keylocation=prompt source@snap \
  | zfs receive -o encryption=on -o keylocation=stream target

Of course, in a similar way, the password could be provided through an actual prompt at the sender side.

Conceptual Flow

  1. The sender writes a short metadata header containing the encryption key (or passphrase).
  2. The receiver detects the presence of this header (e.g., via a magic marker or flag).
  3. The receiver extracts the key material, loads it temporarily in memory, and then
  4. Continues processing the normal ZFS send stream as usual.

This would enable secure, automated send/receive workflows where:

  • the target never stores the key on disk,
  • the dataset it never unlocked
  • the key comes from the sender, and
  • no interactive prompt is required.

How will this feature improve OpenZFS?

  • It eliminates the limitation where prompts cannot work during zfs receive due to stdin being occupied.
  • It avoids the security issue of having to load the key manually on the target.
  • It enables secure automated backups of unencrypted → encrypted datasets without ever storing the key on the backup machine.

And yes, i used GenAI to help phrase the idea in proper English.

This is an alternative idea to issue #17937 and probably far easier to implement.

CurlyMoo avatar Nov 16 '25 17:11 CurlyMoo

If your goal is essentially to load a key only for the purpose of receiving, I wonder if a simpler solution might be to do something like add an option to zfs load-key to specify a restriction on what a key is loaded for. For example, it might look something like zfs load-key -t receive <target>, which would cause ZFS to load the key, but any task that requires a key to be loaded and isn't a receive would fail as if the key weren't loaded — these would require you to run zfs load-key <target> as normal to elevate the key to being loaded for all purposes (or add their own with -t).

This means you would still need the added steps of doing load-key and unload-key to issue/revoke permission for the send to occur, but it feels like this might be the more appropriate place to do this, as it would be your 'out-of-band' key handling using the existing commands for that purpose, minus loading the key for all purposes?

Otherwise it feels to me like if you're getting into receive holding the key internally somehow and generating its own encrypted blocks (as if it had received an encrypted stream via zfs send -w) in which case we surely might as well do that on the zfs send so it's encrypted in-flight?

Sorry if this just reads like me thinking out load, but it seems like the problem you're trying to solve is having the key loaded on the receiving end, but if there's a lack of trust then I'm not sure if this proposal fully solves that problem — if you don't trust the receiver you should ideally be sending encrypted datasets so no form of key exchange is required at all, which is the big advantage of zfs send -w for this purpose.

Haravikk avatar Nov 19 '25 12:11 Haravikk

For example, it might look something like zfs load-key -t receive , which would cause ZFS to load the key, but any task that requires a key to be loaded and isn't a receive would fail as if the key weren't loaded

That's also a good idea. As long as nothing is exposed. A dataset/snapshot not yet fully received is also not accessible. So that's fine. The load-key mechanism would then also release the key as soon as the receive is finished. Otherwise the data would still be exposed until you manually unload the key. That would mean that a specific load-key receive is only valid for one occurrence of that action.

The downside is that it would require the key to be loaded for each increment of snapshots sent. Each snapshot fully received is already accessible and therefor exposed. But that's something I can work around on the sending side.

in which case we surely might as well do that on the zfs send so it's encrypted in-flight?

That's why I did the other suggestion :smile:

if you don't trust the receiver you should ideally be sending encrypted datasets so no form of key exchange is required at all

I know, but that requires an encrypted source. I don't want. A key-exchange is fine as long as it's used for encrypting the target for as long as needed. I believe it's possible to have a mechanism to just have the key loaded for as long as needed for just the receive action. Nothing more.

CurlyMoo avatar Nov 19 '25 13:11 CurlyMoo