[PATCH 12/12] fscrypt-crypt-util: add support for per extent KDF

Daniel Vacek <[email protected]> Fri, 24 Jul 2026 15:33:28 +0200
Newsgroups org.kernel.vger.linux-fscrypt,net.sourceforge.lists.linux-f2fs-devel,org.kernel.vger.fstests,org.kernel.vger.linux-btrfs,org.kernel.vger.linux-ext4,org.kernel.vger.linux-xfs
Message-ID <[email protected]>
When deriving a key for extent, fscrypt uses different salt. Add the
support for this.

Signed-off-by: Daniel Vacek <[email protected]>
---
 common/encrypt           | 13 +++++++++----
 src/fscrypt-crypt-util.c | 12 +++++++++++-
 2 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/common/encrypt b/common/encrypt
index 4c3fd904..2485b69a 100644
--- a/common/encrypt
+++ b/common/encrypt
@@ -836,7 +836,7 @@ _do_verify_ciphertext_for_encryption_policy()
 	local blocksize=$(_get_block_size $SCRATCH_MNT)
 	local test_contents_files=()
 	local test_filenames_files=()
-	local i src dir dst inode blocklist \
+	local i src dir dst inode blocklist context_per_extent \
 	      padding_flag padding dir_inode len name f nonce decrypted_name
 
 	# Create files whose encrypted contents we'll verify.  For each, save
@@ -891,19 +891,24 @@ _do_verify_ciphertext_for_encryption_policy()
 	# Now unmount the filesystem and verify the ciphertext we just wrote.
 	_scratch_unmount
 
+	case $FSTYP in
+	btrfs)	context_per_extent=--context-per-extent;;
+	*)	context_per_extent=;;
+	esac
+
 	echo "Verifying encrypted file contents" >> $seqres.full
 	for f in "${test_contents_files[@]}"; do
 		read -r src inode blocklist <<< "$f"
 		nonce=$(_get_encryption_data_nonce $SCRATCH_DEV $inode)
 		_dump_ciphertext_blocks $SCRATCH_DEV $blocklist > $tmp.actual_contents
 		$crypt_contents_cmd $contents_encryption_mode $raw_key_hex \
-			--file-nonce=$nonce --inode-number=$inode \
-                        < $src > $tmp.expected_contents
+			--file-nonce=$nonce $context_per_extent --inode-number=$inode \
+			< $src > $tmp.expected_contents
 		if ! cmp $tmp.expected_contents $tmp.actual_contents; then
 			_fail "Expected encrypted contents != actual encrypted contents.  File: $f"
 		fi
 		$crypt_contents_cmd $contents_encryption_mode $raw_key_hex \
-			--decrypt --file-nonce=$nonce --inode-number=$inode \
+			--decrypt --file-nonce=$nonce $context_per_extent --inode-number=$inode \
 			< $tmp.actual_contents > $tmp.decrypted_contents
 		if ! cmp $src $tmp.decrypted_contents; then
 			_fail "Contents decryption sanity check failed.  File: $f"
diff --git a/src/fscrypt-crypt-util.c b/src/fscrypt-crypt-util.c
index f51b3669..1403edb5 100644
--- a/src/fscrypt-crypt-util.c
+++ b/src/fscrypt-crypt-util.c
@@ -75,6 +75,7 @@ static void usage(FILE *fp)
 "                                replicate the en/decryption that is done when\n"
 "                                the filesystem is given a hardware-wrapped key.\n"
 "  --file-nonce=NONCE          File's nonce as a 32-character hex string\n"
+"  --context-per-extent        Derive the key for per extent context.\n"
 "  --fs-uuid=UUID              The filesystem UUID as a 32-character hex string.\n"
 "                                Required for --iv-ino-lblk-32 and\n"
 "                                --iv-ino-lblk-64; otherwise is unused.\n"
@@ -2162,6 +2163,7 @@ struct key_and_iv_params {
 	bool direct_key;
 	bool iv_ino_lblk_64;
 	bool iv_ino_lblk_32;
+	bool context_per_extent;
 	u64 data_unit_index;
 	u64 inode_number;
 	u8 fs_uuid[UUID_SIZE];
@@ -2176,6 +2178,7 @@ struct key_and_iv_params {
 #define HKDF_CONTEXT_IV_INO_LBLK_32_KEY	6
 #define HKDF_CONTEXT_INODE_HASH_KEY	7
 #define HKDF_CONTEXT_KEY_IDENTIFIER_FOR_HW_WRAPPED_KEY 8
+#define HKDF_CONTEXT_PER_EXTENT_ENC_KEY	9
 
 /* Hash the file's inode number using SipHash keyed by a derived key */
 static u32 hash_inode_number(const struct key_and_iv_params *params)
@@ -2366,7 +2369,9 @@ static void derive_real_key(const struct key_and_iv_params *params,
 		} else {
 			if (!params->file_nonce_specified)
 				die("--kdf=HKDF-SHA512 requires --file-nonce or --iv-ino-lblk-{64,32}");
-			info[infolen++] = HKDF_CONTEXT_PER_FILE_ENC_KEY;
+			info[infolen++] = params->context_per_extent?
+						HKDF_CONTEXT_PER_EXTENT_ENC_KEY:
+						HKDF_CONTEXT_PER_FILE_ENC_KEY;
 			memcpy(&info[infolen], params->file_nonce,
 			       FILE_NONCE_SIZE);
 			infolen += FILE_NONCE_SIZE;
@@ -2481,6 +2486,7 @@ enum {
 	OPT_DUMP_KEY_IDENTIFIER,
 	OPT_ENABLE_HW_KDF,
 	OPT_FILE_NONCE,
+	OPT_CONTEXT_PER_EXTENT,
 	OPT_FS_UUID,
 	OPT_HELP,
 	OPT_INODE_NUMBER,
@@ -2500,6 +2506,7 @@ static const struct option longopts[] = {
 	{ "dump-key-identifier", no_argument,   NULL, OPT_DUMP_KEY_IDENTIFIER },
 	{ "enable-hw-kdf",   no_argument,       NULL, OPT_ENABLE_HW_KDF },
 	{ "file-nonce",      required_argument, NULL, OPT_FILE_NONCE },
+	{ "context-per-extent", no_argument,    NULL, OPT_CONTEXT_PER_EXTENT },
 	{ "fs-uuid",         required_argument, NULL, OPT_FS_UUID },
 	{ "help",            no_argument,       NULL, OPT_HELP },
 	{ "inode-number",    required_argument, NULL, OPT_INODE_NUMBER },
@@ -2572,6 +2579,9 @@ int main(int argc, char *argv[])
 				die("Invalid file nonce: %s", optarg);
 			params.file_nonce_specified = true;
 			break;
+		case OPT_CONTEXT_PER_EXTENT:
+			params.context_per_extent = true;
+			break;
 		case OPT_FS_UUID:
 			if (hex2bin(optarg, params.fs_uuid, UUID_SIZE)
 			    != UUID_SIZE)
-- 
2.53.0