diff --git a/common/encrypt b/common/encrypt
index 5695a123..c4cc2d83 100644
--- a/common/encrypt
+++ b/common/encrypt
@@ -97,7 +97,8 @@ _require_encryption_policy_support()
 	echo "Checking whether kernel supports encryption policy: $set_encpolicy_args" \
 		>> $seqres.full
 
-	if (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
+	if (( policy_flags & (FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 |
+			      FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) )); then
 		_scratch_unmount
 		_scratch_mkfs_stable_inodes_encrypted &>> $seqres.full
 		_scratch_mount
@@ -769,6 +770,7 @@ FSCRYPT_MODE_ADIANTUM=9
 
 FSCRYPT_POLICY_FLAG_DIRECT_KEY=0x04
 FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64=0x08
+FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32=0x10
 
 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR=1
 FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER=2
@@ -797,6 +799,7 @@ _fscrypt_mode_name_to_num()
 #	'v2':			test a v2 encryption policy
 #	'direct':		test the DIRECT_KEY policy flag
 #	'iv_ino_lblk_64':	test the IV_INO_LBLK_64 policy flag
+#	'iv_ino_lblk_32':	test the IV_INO_LBLK_32 policy flag
 #
 _verify_ciphertext_for_encryption_policy()
 {
@@ -826,6 +829,9 @@ _verify_ciphertext_for_encryption_policy()
 		iv_ino_lblk_64)
 			(( policy_flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 ))
 			;;
+		iv_ino_lblk_32)
+			(( policy_flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 ))
+			;;
 		*)
 			_fail "Unknown option '$opt' passed to ${FUNCNAME[0]}"
 			;;
@@ -841,14 +847,15 @@ _verify_ciphertext_for_encryption_policy()
 		set_encpolicy_args+=" -v 2"
 		crypt_util_args+=" --kdf=HKDF-SHA512"
 		if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
-			if (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
-				_fail "'direct' and 'iv_ino_lblk_64' options are mutually exclusive"
-			fi
 			crypt_util_args+=" --mode-num=$contents_mode_num"
 		elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
 			crypt_util_args+=" --iv-ino-lblk-64"
 			crypt_util_contents_args+=" --mode-num=$contents_mode_num"
 			crypt_util_filename_args+=" --mode-num=$filenames_mode_num"
+		elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 )); then
+			crypt_util_args+=" --iv-ino-lblk-32"
+			crypt_util_contents_args+=" --mode-num=$contents_mode_num"
+			crypt_util_filename_args+=" --mode-num=$filenames_mode_num"
 		fi
 	else
 		if (( policy_flags & ~FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
@@ -872,7 +879,8 @@ _verify_ciphertext_for_encryption_policy()
 	fi
 
 	echo "Creating encryption-capable filesystem" >> $seqres.full
-	if (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
+	if (( policy_flags & (FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 |
+			      FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) )); then
 		_scratch_mkfs_stable_inodes_encrypted &>> $seqres.full
 	else
 		_scratch_mkfs_encrypted &>> $seqres.full
diff --git a/src/fscrypt-crypt-util.c b/src/fscrypt-crypt-util.c
index 1bf8f95c..ce9da85d 100644
--- a/src/fscrypt-crypt-util.c
+++ b/src/fscrypt-crypt-util.c
@@ -63,10 +63,14 @@ static void usage(FILE *fp)
 "  --decrypt                   Decrypt instead of encrypt\n"
 "  --file-nonce=NONCE          File's nonce as a 32-character hex string\n"
 "  --fs-uuid=UUID              The filesystem UUID as a 32-character hex string.\n"
-"                                Only required for --iv-ino-lblk-64.\n"
+"                                Required for --iv-ino-lblk-32 and\n"
+"                                --iv-ino-lblk-64; otherwise is unused.\n"
 "  --help                      Show this help\n"
-"  --inode-number=INUM         The file's inode number.  Only required for\n"
-"                                --iv-ino-lblk-64.\n"
+"  --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-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"
 "                                number and the same key is shared across files.\n"
 "                                Requires --kdf=HKDF-SHA512, --fs-uuid,\n"
@@ -143,6 +147,11 @@ static inline u32 ror32(u32 v, int n)
 	return (v >> n) | (v << (32 - n));
 }
 
+static inline u64 rol64(u64 v, int n)
+{
+	return (v << n) | (v >> (64 - n));
+}
+
 static inline u64 ror64(u64 v, int n)
 {
 	return (v >> n) | (v << (64 - n));
@@ -1579,6 +1588,50 @@ static void test_adiantum(void)
 }
 #endif /* ENABLE_ALG_TESTS */
 
+/*----------------------------------------------------------------------------*
+ *                               SipHash-2-4                                  *
+ *----------------------------------------------------------------------------*/
+
+/*
+ * Reference: "SipHash: a fast short-input PRF"
+ *	https://cr.yp.to/siphash/siphash-20120918.pdf
+ */
+
+#define SIPROUND						\
+	do {							\
+		v0 += v1;	    v2 += v3;			\
+		v1 = rol64(v1, 13); v3 = rol64(v3, 16);		\
+		v1 ^= v0;	    v3 ^= v2;			\
+		v0 = rol64(v0, 32);				\
+		v2 += v1;	    v0 += v3;			\
+		v1 = rol64(v1, 17); v3 = rol64(v3, 21);		\
+		v1 ^= v2;	    v3 ^= v0;			\
+		v2 = rol64(v2, 32);				\
+	} while (0)
+
+/* Compute the SipHash-2-4 of a 64-bit number when formatted as little endian */
+static u64 siphash_1u64(const u64 key[2], u64 data)
+{
+	u64 v0 = key[0] ^ 0x736f6d6570736575ULL;
+	u64 v1 = key[1] ^ 0x646f72616e646f6dULL;
+	u64 v2 = key[0] ^ 0x6c7967656e657261ULL;
+	u64 v3 = key[1] ^ 0x7465646279746573ULL;
+	u64 m[2] = {data, (u64)sizeof(data) << 56};
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(m); i++) {
+		v3 ^= m[i];
+		SIPROUND;
+		SIPROUND;
+		v0 ^= m[i];
+	}
+
+	v2 ^= 0xff;
+	for (i = 0; i < 4; i++)
+		SIPROUND;
+	return v0 ^ v1 ^ v2 ^ v3;
+}
+
 /*----------------------------------------------------------------------------*
  *                               Main program                                 *
  *----------------------------------------------------------------------------*/
@@ -1723,15 +1776,39 @@ struct key_and_iv_params {
 	u8 file_nonce[FILE_NONCE_SIZE];
 	bool file_nonce_specified;
 	bool iv_ino_lblk_64;
+	bool iv_ino_lblk_32;
 	u32 inode_number;
 	u8 fs_uuid[UUID_SIZE];
 	bool fs_uuid_specified;
 };
 
 #define HKDF_CONTEXT_KEY_IDENTIFIER	1
-#define HKDF_CONTEXT_PER_FILE_KEY	2
+#define HKDF_CONTEXT_PER_FILE_ENC_KEY	2
 #define HKDF_CONTEXT_DIRECT_KEY		3
 #define HKDF_CONTEXT_IV_INO_LBLK_64_KEY	4
+#define HKDF_CONTEXT_DIRHASH_KEY	5
+#define HKDF_CONTEXT_IV_INO_LBLK_32_KEY	6
+#define HKDF_CONTEXT_INODE_HASH_KEY	7
+
+/* 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)
+{
+	u8 info[9] = "fscrypt";
+	union {
+		u64 words[2];
+		u8 bytes[16];
+	} hash_key;
+
+	info[8] = HKDF_CONTEXT_INODE_HASH_KEY;
+	hkdf_sha512(params->master_key, params->master_key_size,
+		    NULL, 0, info, sizeof(info),
+		    hash_key.bytes, sizeof(hash_key));
+
+	hash_key.words[0] = get_unaligned_le64(&hash_key.bytes[0]);
+	hash_key.words[1] = get_unaligned_le64(&hash_key.bytes[8]);
+
+	return (u32)siphash_1u64(hash_key.words, params->inode_number);
+}
 
 /*
  * Get the key and starting IV with which the encryption will actually be done.
@@ -1752,8 +1829,20 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
 
 	memset(iv, 0, sizeof(*iv));
 
-	if (params->iv_ino_lblk_64 && params->kdf != KDF_HKDF_SHA512)
-		die("--iv-ino-lblk-64 requires --kdf=HKDF-SHA512");
+	if (params->iv_ino_lblk_64 || params->iv_ino_lblk_32) {
+		const char *opt = params->iv_ino_lblk_64 ? "--iv-ino-lblk-64" :
+							   "--iv-ino-lblk-32";
+		if (params->iv_ino_lblk_64 && params->iv_ino_lblk_32)
+			die("--iv-ino-lblk-64 and --iv-ino-lblk-32 are mutually exclusive");
+		if (params->kdf != KDF_HKDF_SHA512)
+			die("%s requires --kdf=HKDF-SHA512", opt);
+		if (!params->fs_uuid_specified)
+			die("%s requires --fs-uuid", opt);
+		if (params->inode_number == 0)
+			die("%s requires --inode-number", opt);
+		if (params->mode_num == 0)
+			die("%s requires --mode-num", opt);
+	}
 
 	switch (params->kdf) {
 	case KDF_NONE:
@@ -1776,23 +1865,24 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
 		break;
 	case KDF_HKDF_SHA512:
 		if (params->iv_ino_lblk_64) {
-			if (!params->fs_uuid_specified)
-				die("--iv-ino-lblk-64 requires --fs-uuid");
-			if (params->inode_number == 0)
-				die("--iv-ino-lblk-64 requires --inode-number");
-			if (params->mode_num == 0)
-				die("--iv-ino-lblk-64 requires --mode-num");
 			info[infolen++] = HKDF_CONTEXT_IV_INO_LBLK_64_KEY;
 			info[infolen++] = params->mode_num;
 			memcpy(&info[infolen], params->fs_uuid, UUID_SIZE);
 			infolen += UUID_SIZE;
 			put_unaligned_le32(params->inode_number, &iv->bytes[4]);
+		} else if (params->iv_ino_lblk_32) {
+			info[infolen++] = HKDF_CONTEXT_IV_INO_LBLK_32_KEY;
+			info[infolen++] = params->mode_num;
+			memcpy(&info[infolen], params->fs_uuid, UUID_SIZE);
+			infolen += UUID_SIZE;
+			put_unaligned_le32(hash_inode_number(params),
+					   iv->bytes);
 		} else if (params->mode_num != 0) {
 			info[infolen++] = HKDF_CONTEXT_DIRECT_KEY;
 			info[infolen++] = params->mode_num;
 			file_nonce_in_iv = true;
 		} else if (params->file_nonce_specified) {
-			info[infolen++] = HKDF_CONTEXT_PER_FILE_KEY;
+			info[infolen++] = HKDF_CONTEXT_PER_FILE_ENC_KEY;
 			memcpy(&info[infolen], params->file_nonce,
 			       FILE_NONCE_SIZE);
 			infolen += FILE_NONCE_SIZE;
@@ -1817,6 +1907,7 @@ enum {
 	OPT_FS_UUID,
 	OPT_HELP,
 	OPT_INODE_NUMBER,
+	OPT_IV_INO_LBLK_32,
 	OPT_IV_INO_LBLK_64,
 	OPT_KDF,
 	OPT_MODE_NUM,
@@ -1830,6 +1921,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-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 },
 	{ "mode-num",        required_argument, NULL, OPT_MODE_NUM },
@@ -1890,6 +1982,9 @@ int main(int argc, char *argv[])
 		case OPT_INODE_NUMBER:
 			params.inode_number = parse_inode_number(optarg);
 			break;
+		case OPT_IV_INO_LBLK_32:
+			params.iv_ino_lblk_32 = true;
+			break;
 		case OPT_IV_INO_LBLK_64:
 			params.iv_ino_lblk_64 = true;
 			break;
diff --git a/tests/generic/900 b/tests/generic/900
new file mode 100755
index 00000000..dc2a2225
--- /dev/null
+++ b/tests/generic/900
@@ -0,0 +1,43 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2020 Google LLC
+#
+# FS QA Test No. 900
+#
+# Verify ciphertext for v2 encryption policies that use the IV_INO_LBLK_32 flag
+# and use AES-256-XTS to encrypt file contents and AES-256-CTS-CBC to encrypt
+# file names.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+	cd /
+	rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+. ./common/encrypt
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+_supported_fs generic
+_supported_os Linux
+
+_verify_ciphertext_for_encryption_policy AES-256-XTS AES-256-CTS-CBC \
+	v2 iv_ino_lblk_32
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/900.out b/tests/generic/900.out
new file mode 100644
index 00000000..8fbb34cc
--- /dev/null
+++ b/tests/generic/900.out
@@ -0,0 +1,6 @@
+QA output created by 900
+
+Verifying ciphertext with parameters:
+	contents_encryption_mode: AES-256-XTS
+	filenames_encryption_mode: AES-256-CTS-CBC
+	options: v2 iv_ino_lblk_32
diff --git a/tests/generic/group b/tests/generic/group
index c6ce029c..d3501ccc 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -604,3 +604,4 @@
 599 auto quick remount shutdown
 600 auto quick quota
 601 auto quick quota
+900 auto quick encrypt
