diff --git a/common/encrypt b/common/encrypt
index c2e3e6f6..46b24fd8 100644
--- a/common/encrypt
+++ b/common/encrypt
@@ -573,6 +573,41 @@ _get_encryption_nonce()
 	esac
 }
 
+# Retrieve the encryption IV of the first file extent in an inode as a hex
+# string.  The IV was randomly generated by the filesystem, in the case of
+# btrfs, and isn't exposed directly to userspace.  But it can be read using
+# the filesystem's debugging tools.
+_get_extent_iv()
+{
+	local device=$1
+	local inode=$2
+
+	case $FSTYP in
+	btrfs)
+		# btrfs prints the file extents (for simple unshared
+		# inodes) like:
+		#         item 21 key ($inode EXTENT_DATA 0) itemoff 2534 itemsize 69
+		#                generation 7 type 1 (regular)
+                #		 extent data disk byte 5304320 nr 1048576
+                #		 extent data offset 0 nr 1048576 ram 1048576
+                #		 extent compression 0 (none)
+                #		 extent encryption 133 (1, 33: context 0177d05501da7c23920f9ca00872f0bbc3)
+                # The first character of the context is the version; the rest
+                # are the IV.
+
+		#
+		$BTRFS_UTIL_PROG inspect-internal dump-tree $device | \
+			grep -A 5 "key ($inode EXTENT_DATA 0)" | \
+			awk '/ context [[:xdigit:]]+)/ {
+				match($0, /context ([[:xdigit:]]+)\)/,a);
+				print substr(a[1], 3, length(a[1]) - 1);
+			}'
+		;;
+	*)
+		_fail "_get_extent_iv() isn't implemented on $FSTYP" ;;
+	esac
+}
+
 # Require support for _get_encryption_nonce()
 _require_get_encryption_nonce_support()
 {
@@ -607,6 +642,19 @@ _get_ciphertext_filename()
 	local dir_inode=$3
 
 	case $FSTYP in
+	btrfs)
+		# Extract the filename from the inode_ref object, similar to:
+		# item 24 key (259 INODE_REF 257) itemoff 14826 itemsize 26
+		# 	index 3 namelen 16 name: J\xf7\x15tD\x8eL\xae/\x98\x9f\x09\xc1\xb6\x09>
+		#
+		$BTRFS_UTIL_PROG inspect-internal dump-tree $device | \
+			grep -A 1 "key ($inode INODE_REF " | tail -n 1 | \
+			perl -ne '
+				s/.*?name: //;
+				chomp;
+				s/\\x([[:xdigit:]]{2})/chr hex $1/eg;
+				print;'
+		;;
 	ext4)
 		# Extract the filename from the debugfs output line like:
 		#
@@ -661,6 +709,10 @@ _require_get_ciphertext_filename_support()
 {
 	echo "Checking for _get_ciphertext_filename() support for $FSTYP" >> $seqres.full
 	case $FSTYP in
+	btrfs)
+		# Verify that we have BTRFS_UTIL_PROG
+		_require_btrfs_command inspect-internal dump-tree
+		;;
 	ext4)
 		# Verify that the "ls -l -r" debugfs command is supported and
 		# that it hex-encodes non-ASCII characters, rather than using an
@@ -744,7 +796,7 @@ _do_verify_ciphertext_for_encryption_policy()
 	local raw_key_hex=$6
 	local crypt_contents_cmd="$here/src/fscrypt-crypt-util $7"
 	local crypt_filename_cmd="$here/src/fscrypt-crypt-util $8"
-
+	local use_iv=$9
 	local blocksize=$(_get_block_size $SCRATCH_MNT)
 	local test_contents_files=()
 	local test_filenames_files=()
@@ -798,18 +850,24 @@ _do_verify_ciphertext_for_encryption_policy()
 
 	echo "Verifying encrypted file contents" >> $seqres.full
 	for f in "${test_contents_files[@]}"; do
+		local iv_arg=""
 		read -r src inode blocklist <<< "$f"
 		nonce=$(_get_encryption_nonce $SCRATCH_DEV $inode)
 		_dump_ciphertext_blocks $SCRATCH_DEV $blocklist > $tmp.actual_contents
+		if [ -n "$use_iv" ]; then
+			local iv_hex=$(_get_extent_iv $SCRATCH_DEV $inode)
+			iv_arg=" --iv=$iv_hex"
+		fi
+			
 		$crypt_contents_cmd $contents_encryption_mode $raw_key_hex \
 			--file-nonce=$nonce --block-size=$blocksize \
-			--inode-number=$inode < $src > $tmp.expected_contents
+			--inode-number=$inode $iv_arg < $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 --block-size=$blocksize \
-			--inode-number=$inode \
+			--inode-number=$inode $iv_arg \
 			< $tmp.actual_contents > $tmp.decrypted_contents
 		if ! cmp $src $tmp.decrypted_contents; then
 			_fail "Contents decryption sanity check failed.  File: $f"
@@ -894,6 +952,7 @@ _verify_ciphertext_for_encryption_policy()
 	local crypt_util_contents_args=""
 	local crypt_util_filename_args=""
 	local expected_identifier
+	local use_iv=""
 
 	shift 2
 	for opt; do
@@ -927,14 +986,16 @@ _verify_ciphertext_for_encryption_policy()
 	crypt_util_contents_args+=" --mode-num=$contents_mode_num"
 	crypt_util_filename_args+=" --mode-num=$filenames_mode_num"
 
+	if [ "$FSTYP" == "btrfs" ]; then
+		if (( policy_flags == 0 )); then
+			_notrun "Btrfs does not accept default policies"
+		fi	
+		use_iv=1
+	fi
+
 	if (( policy_version > 1 )); then
 		set_encpolicy_args+=" -v 2"
 		crypt_util_args+=" --kdf=HKDF-SHA512"
-		if [ "$FSTYP" = "btrfs" ]; then
-			if (( policy_flags == 0 )); then
-				_notrun "Btrfs does not accept default policies"
-			fi	
-		fi
 		if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
 			crypt_util_args+=" --direct-key"
 		elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
@@ -1026,7 +1087,8 @@ EOF
 		"$keyspec" \
 		"$raw_key_hex" \
 		"$crypt_util_contents_args" \
-		"$crypt_util_filename_args"
+		"$crypt_util_filename_args" \
+		"$use_iv"
 }
 
 # Replace no-key filenames in the given directory with "NOKEY_NAME".
diff --git a/src/fscrypt-crypt-util.c b/src/fscrypt-crypt-util.c
index ffb9534d..93b8a36f 100644
--- a/src/fscrypt-crypt-util.c
+++ b/src/fscrypt-crypt-util.c
@@ -76,6 +76,8 @@ static void usage(FILE *fp)
 "  --inode-number=INUM         The file's inode number.  Required for\n"
 "                                --iv-ino-lblk-32 and --iv-ino-lblk-64;\n"
 "                                otherwise is unused.\n"
+"  --iv=IV                     For extent-based encryption filesystems, the\n"
+"                                starting IV for the file\n"
 "  --iv-ino-lblk-32            Similar to --iv-ino-lblk-64, but selects the\n"
 "                                32-bit variant.\n"
 "  --iv-ino-lblk-64            Use the format where the IVs include the inode\n"
@@ -1794,6 +1796,8 @@ struct key_and_iv_params {
 	u8 file_nonce[FILE_NONCE_SIZE];
 	bool file_nonce_specified;
 	bool direct_key;
+	u8 iv[MAX_IV_SIZE];
+	bool iv_set;
 	bool iv_ino_lblk_64;
 	bool iv_ino_lblk_32;
 	u64 block_number;
@@ -1901,7 +1905,9 @@ static void generate_iv(const struct key_and_iv_params *params,
 			union fscrypt_iv *iv)
 {
 	memset(iv, 0, sizeof(*iv));
-	if (params->direct_key) {
+	if (params->iv_set) {
+		memcpy(iv->bytes, params->iv, MAX_IV_SIZE);
+	} else if (params->direct_key) {
 		if (!params->file_nonce_specified)
 			die("--direct-key requires --file-nonce");
 		iv->block_number = cpu_to_le64(params->block_number);
@@ -1987,6 +1993,7 @@ enum {
 	OPT_FS_UUID,
 	OPT_HELP,
 	OPT_INODE_NUMBER,
+	OPT_IV,
 	OPT_IV_INO_LBLK_32,
 	OPT_IV_INO_LBLK_64,
 	OPT_KDF,
@@ -2004,6 +2011,7 @@ static const struct option longopts[] = {
 	{ "fs-uuid",         required_argument, NULL, OPT_FS_UUID },
 	{ "help",            no_argument,       NULL, OPT_HELP },
 	{ "inode-number",    required_argument, NULL, OPT_INODE_NUMBER },
+	{ "iv",              required_argument, NULL, OPT_IV },
 	{ "iv-ino-lblk-32",  no_argument,       NULL, OPT_IV_INO_LBLK_32 },
 	{ "iv-ino-lblk-64",  no_argument,       NULL, OPT_IV_INO_LBLK_64 },
 	{ "kdf",             required_argument, NULL, OPT_KDF },
@@ -2082,6 +2090,14 @@ int main(int argc, char *argv[])
 			if (params.inode_number <= 0 || *tmp || errno)
 				die("Invalid inode number: %s", optarg);
 			break;
+		case OPT_IV:
+			int iv_len = hex2bin(optarg, params.iv, MAX_IV_SIZE);
+			if ((iv_len != AES_BLOCK_SIZE) &&
+			    (iv_len != ADIANTUM_IV_SIZE))
+				die("Invalid iv length: %d (must be %u or %u)",
+				    iv_len, AES_BLOCK_SIZE, ADIANTUM_IV_SIZE);
+			params.iv_set = true;
+			break;
 		case OPT_IV_INO_LBLK_32:
 			params.iv_ino_lblk_32 = true;
 			break;
diff --git a/tests/generic/550 b/tests/generic/550
index aa792089..1c350090 100755
--- a/tests/generic/550
+++ b/tests/generic/550
@@ -17,9 +17,7 @@ _begin_fstest auto quick encrypt
 # real QA test starts here
 _supported_fs generic
 
-# Test both with and without the DIRECT_KEY flag.
 _verify_ciphertext_for_encryption_policy Adiantum Adiantum
-_verify_ciphertext_for_encryption_policy Adiantum Adiantum direct
 
 # success, all done
 status=0
diff --git a/tests/generic/550.out b/tests/generic/550.out
index 4cec7570..418fa0b1 100644
--- a/tests/generic/550.out
+++ b/tests/generic/550.out
@@ -3,8 +3,3 @@ QA output created by 550
 Verifying ciphertext with parameters:
 	contents_encryption_mode: Adiantum
 	filenames_encryption_mode: Adiantum
-
-Verifying ciphertext with parameters:
-	contents_encryption_mode: Adiantum
-	filenames_encryption_mode: Adiantum
-	options: direct
diff --git a/tests/generic/584 b/tests/generic/584
index adafec6a..ec03908b 100755
--- a/tests/generic/584
+++ b/tests/generic/584
@@ -19,9 +19,7 @@ _begin_fstest auto quick encrypt
 # real QA test starts here
 _supported_fs generic
 
-# Test both with and without the DIRECT_KEY flag.
 _verify_ciphertext_for_encryption_policy Adiantum Adiantum v2
-_verify_ciphertext_for_encryption_policy Adiantum Adiantum v2 direct
 
 # success, all done
 status=0
diff --git a/tests/generic/584.out b/tests/generic/584.out
index 946c5f0a..2a5fc053 100644
--- a/tests/generic/584.out
+++ b/tests/generic/584.out
@@ -4,8 +4,3 @@ Verifying ciphertext with parameters:
 	contents_encryption_mode: Adiantum
 	filenames_encryption_mode: Adiantum
 	options: v2
-
-Verifying ciphertext with parameters:
-	contents_encryption_mode: Adiantum
-	filenames_encryption_mode: Adiantum
-	options: v2 direct
diff --git a/tests/generic/720 b/tests/generic/720
new file mode 100755
index 00000000..5072d3ab
--- /dev/null
+++ b/tests/generic/720
@@ -0,0 +1,26 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2019 Google LLC
+#
+# FS QA Test No. 720
+#
+# Verify ciphertext for v2 encryption policies that use Adiantum to encrypt file
+# contents and file names.
+#
+# This is the same as generic/584, except using direct policy 
+#
+. ./common/preamble
+_begin_fstest auto quick encrypt
+
+# Import common functions.
+. ./common/filter
+. ./common/encrypt
+
+# real QA test starts here
+_supported_fs generic
+
+_verify_ciphertext_for_encryption_policy Adiantum Adiantum v2 direct
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/720.out b/tests/generic/720.out
new file mode 100644
index 00000000..0b0d82f8
--- /dev/null
+++ b/tests/generic/720.out
@@ -0,0 +1,6 @@
+QA output created by 720
+
+Verifying ciphertext with parameters:
+	contents_encryption_mode: Adiantum
+	filenames_encryption_mode: Adiantum
+	options: v2 direct
diff --git a/tests/generic/721 b/tests/generic/721
new file mode 100755
index 00000000..98f2e6f9
--- /dev/null
+++ b/tests/generic/721
@@ -0,0 +1,26 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2019 Google LLC
+#
+# FS QA Test No. 550
+#
+# Verify ciphertext for v1 encryption policies that use Adiantum to encrypt file
+# contents and file names.
+# This is the same as generic/550, except with direct keys.
+#
+. ./common/preamble
+_begin_fstest auto quick encrypt
+
+# Import common functions.
+. ./common/filter
+. ./common/encrypt
+
+# real QA test starts here
+_supported_fs generic
+
+_verify_ciphertext_for_encryption_policy Adiantum Adiantum direct
+
+# success, all done
+status=0
+exit
+
diff --git a/tests/generic/721.out b/tests/generic/721.out
new file mode 100644
index 00000000..018c4666
--- /dev/null
+++ b/tests/generic/721.out
@@ -0,0 +1,6 @@
+QA output created by 721
+
+Verifying ciphertext with parameters:
+	contents_encryption_mode: Adiantum
+	filenames_encryption_mode: Adiantum
+	options: direct
