diff --git a/fsck/main.c b/fsck/main.c
index 32559f1..6a4d867 100644
--- a/fsck/main.c
+++ b/fsck/main.c
@@ -26,6 +26,8 @@
 #include <stdbool.h>
 #include "quotaio.h"
 
+#include "f2fs_metadata_crypt.h"
+
 struct f2fs_fsck gfsck;
 
 #ifdef WITH_ANDROID
@@ -62,6 +64,7 @@ void fsck_usage()
 			" (default 0)\n");
 	MSG(0, "  -m <max-hash-collision>  set max cache hash collision"
 			" (default 16)\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -C encoding[:flag1,flag2] Set options for enabling"
 			" casefolding\n");
 	MSG(0, "  -d debug level [default:0]\n");
@@ -92,6 +95,7 @@ void dump_usage()
 	MSG(0, "  -S sparse_mode\n");
 	MSG(0, "  -a [SSA dump segno from #1~#2 (decimal), for all 0~-1]\n");
 	MSG(0, "  -b blk_addr (in 4KB)\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -V print the version number and exit\n");
 
 	exit(1);
@@ -107,6 +111,7 @@ void defrag_usage()
 	MSG(0, "  -l length [default:512 (2MB)]\n");
 	MSG(0, "  -t target block address [default: main_blkaddr + 2MB]\n");
 	MSG(0, "  -i set direction as shrink [default: expand]\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -V print the version number and exit\n");
 	exit(1);
 }
@@ -119,6 +124,7 @@ void resize_usage()
 	MSG(0, "  -i extended node bitmap, node ratio is 20%% by default\n");
 	MSG(0, "  -s safe resize (Does not resize metadata)");
 	MSG(0, "  -t target sectors [default: device size]\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -V print the version number and exit\n");
 	exit(1);
 }
@@ -129,6 +135,7 @@ void sload_usage()
 	MSG(0, "[options]:\n");
 	MSG(0, "  -C fs_config\n");
 	MSG(0, "  -f source directory [path of the source directory]\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -p product out directory\n");
 	MSG(0, "  -s file_contexts\n");
 	MSG(0, "  -S sparse_mode\n");
@@ -200,7 +207,7 @@ void f2fs_parse_options(int argc, char *argv[])
 	}
 
 	if (!strcmp("fsck.f2fs", prog)) {
-		const char *option_string = ":aC:c:m:d:fg:O:p:q:StyV";
+		const char *option_string = ":aC:c:m:M:d:fg:O:p:q:StyV";
 		int opt = 0, val;
 		char *token;
 		struct option long_opt[] = {
@@ -243,6 +250,12 @@ void f2fs_parse_options(int argc, char *argv[])
 				c.cache_config.max_hash_collision =
 						atoi(optarg);
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					fsck_usage();
+				}
+				break;
 			case 'g':
 				if (!strcmp(optarg, "android"))
 					c.defset = CONF_ANDROID;
@@ -345,7 +358,7 @@ void f2fs_parse_options(int argc, char *argv[])
 				break;
 		}
 	} else if (!strcmp("dump.f2fs", prog)) {
-		const char *option_string = "d:i:n:s:Sa:b:V";
+		const char *option_string = "d:i:n:s:Sa:b:M:V";
 		static struct dump_option dump_opt = {
 			.nid = 0,	/* default root ino */
 			.start_nat = -1,
@@ -413,6 +426,12 @@ void f2fs_parse_options(int argc, char *argv[])
 					ret = sscanf(optarg, "%x",
 							&dump_opt.blk_addr);
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					dump_usage();
+				}
+				break;
 			case 'V':
 				show_version(prog);
 				exit(0);
@@ -427,7 +446,7 @@ void f2fs_parse_options(int argc, char *argv[])
 
 		c.private = &dump_opt;
 	} else if (!strcmp("defrag.f2fs", prog)) {
-		const char *option_string = "d:s:Sl:t:iV";
+		const char *option_string = "d:M:s:Sl:t:iV";
 
 		c.func = DEFRAG;
 		while ((option = getopt(argc, argv, option_string)) != EOF) {
@@ -473,6 +492,12 @@ void f2fs_parse_options(int argc, char *argv[])
 			case 'i':
 				c.defrag_shrink = 1;
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					defrag_usage();
+				}
+				break;
 			case 'V':
 				show_version(prog);
 				exit(0);
@@ -485,7 +510,7 @@ void f2fs_parse_options(int argc, char *argv[])
 				break;
 		}
 	} else if (!strcmp("resize.f2fs", prog)) {
-		const char *option_string = "d:st:iV";
+		const char *option_string = "d:M:st:iV";
 
 		c.func = RESIZE;
 		while ((option = getopt(argc, argv, option_string)) != EOF) {
@@ -515,6 +540,12 @@ void f2fs_parse_options(int argc, char *argv[])
 			case 'i':
 				c.large_nat_bitmap = 1;
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					resize_usage();
+				}
+				break;
 			case 'V':
 				show_version(prog);
 				exit(0);
@@ -527,7 +558,7 @@ void f2fs_parse_options(int argc, char *argv[])
 				break;
 		}
 	} else if (!strcmp("sload.f2fs", prog)) {
-		const char *option_string = "C:d:f:p:s:St:T:V";
+		const char *option_string = "C:d:f:M:p:s:St:T:V";
 #ifdef HAVE_LIBSELINUX
 		int max_nr_opt = (int)sizeof(c.seopt_file) /
 			sizeof(c.seopt_file[0]);
@@ -553,6 +584,12 @@ void f2fs_parse_options(int argc, char *argv[])
 			case 'f':
 				c.from_dir = absolute_path(optarg);
 				break;
+			case 'M':
+				if (f2fs_metadata_process_key(optarg)) {
+					MSG(0, "Error: Invalid metadata key\n");
+					sload_usage();
+				}
+				break;
 			case 'p':
 				c.target_out_dir = absolute_path(optarg);
 				break;
diff --git a/fsck/mount.c b/fsck/mount.c
index 8ebc5b0..7520a8a 100644
--- a/fsck/mount.c
+++ b/fsck/mount.c
@@ -11,6 +11,7 @@
 #include "fsck.h"
 #include "node.h"
 #include "xattr.h"
+#include "f2fs_metadata_crypt.h"
 #include <locale.h>
 #include <stdbool.h>
 #ifdef HAVE_LINUX_POSIX_ACL_H
@@ -561,6 +562,10 @@ void print_sb_state(struct f2fs_super_block *sb)
 	if (f & cpu_to_le32(F2FS_FEATURE_COMPRESSION)) {
 		MSG(0, "%s", " compression");
 	}
+	if (sb->metadata_crypt_alg) {
+		MSG(0, "%s", " metadata_crypt");
+	}
+
 	MSG(0, "\n");
 	MSG(0, "Info: superblock encrypt level = %d, salt = ",
 					sb->encryption_level);
@@ -686,7 +691,7 @@ void update_superblock(struct f2fs_super_block *sb, int sb_mask)
 	memcpy(buf + F2FS_SUPER_OFFSET, sb, sizeof(*sb));
 	for (addr = SB0_ADDR; addr < SB_MAX_ADDR; addr++) {
 		if (SB_MASK(addr) & sb_mask) {
-			ret = dev_write_block(buf, addr);
+			ret = dev_write_block_unencrypted(buf, addr);
 			ASSERT(ret >= 0);
 		}
 	}
@@ -927,6 +932,24 @@ int sanity_check_raw_super(struct f2fs_super_block *sb, enum SB_ADDR sb_addr)
 		return -1;
 	}
 
+	/*
+	 * Check that metadata encryption is enabled on superblock when metadata
+	 * crypt key is specified
+	 */
+	if (get_sb(metadata_crypt_alg) && !c.metadata_crypt_key) {
+		MSG(0, "\tFilesystem has metadata encryption, but we're missing the metadata encryption key.\n");
+		return -1;
+	}
+
+	/*
+	 * Check that metadata encryption is disabled on superblock when metadata
+	 * crypt key is not specified
+	 */
+	if (!get_sb(metadata_crypt_alg) && c.metadata_crypt_key) {
+		MSG(0, "\tFilesystem has does not have metadata encryption, but a metadata encryption key was specified.\n");
+		return -1;
+	}
+
 	if (sanity_check_area_boundary(sb, sb_addr))
 		return -1;
 	return 0;
@@ -940,7 +963,7 @@ int validate_super_block(struct f2fs_sb_info *sbi, enum SB_ADDR sb_addr)
 	if (!sbi->raw_super)
 		return -ENOMEM;
 
-	if (dev_read_block(buf, sb_addr))
+	if (dev_read_block_unencrypted(buf, sb_addr))
 		return -1;
 
 	memcpy(sbi->raw_super, buf + F2FS_SUPER_OFFSET,
@@ -3499,6 +3522,12 @@ int f2fs_do_mount(struct f2fs_sb_info *sbi)
 	}
 	sb = F2FS_RAW_SUPER(sbi);
 
+	/* Get metadata encryption algorithm */
+	c.metadata_crypt_alg = get_sb(metadata_crypt_alg);
+
+	if (f2fs_metadata_verify_args())
+		exit(1);
+
 	ret = check_sector_size(sb);
 	if (ret)
 		return -1;
diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
index b5bda13..6b1912d 100644
--- a/include/f2fs_fs.h
+++ b/include/f2fs_fs.h
@@ -441,6 +441,11 @@ struct f2fs_configuration {
 
 	/* cache parameters */
 	dev_cache_config_t cache_config;
+
+	/* metadata encryption */
+	__u8 *metadata_crypt_key;
+	int metadata_crypt_key_len;
+	int metadata_crypt_alg;
 };
 
 #ifdef CONFIG_64BIT
@@ -675,7 +680,8 @@ struct f2fs_super_block {
 	__u8 hot_ext_count;		/* # of hot file extension */
 	__le16  s_encoding;		/* Filename charset encoding */
 	__le16  s_encoding_flags;	/* Filename charset encoding flags */
-	__u8 reserved[306];		/* valid reserved region */
+	__le32	metadata_crypt_alg;	/* The metadata encryption algorithm (FSCRYPT_MODE_*) */
+	__u8 reserved[302];		/* valid reserved region */
 	__le32 crc;			/* checksum of superblock */
 } __attribute__((packed));
 
@@ -1237,12 +1243,14 @@ extern int dev_readahead(__u64, size_t UNUSED(len));
 #endif
 extern int dev_write(void *, __u64, size_t);
 extern int dev_write_block(void *, __u64);
+extern int dev_write_block_unencrypted(void *, __u64);
 extern int dev_write_dump(void *, __u64, size_t);
 /* All bytes in the buffer must be 0 use dev_fill(). */
 extern int dev_fill(void *, __u64, size_t);
 extern int dev_fill_block(void *, __u64);
 
 extern int dev_read_block(void *, __u64);
+extern int dev_read_block_unencrypted(void *, __u64);
 extern int dev_reada_block(__u64);
 
 extern int dev_read_version(void *, __u64, size_t);
diff --git a/include/f2fs_metadata_crypt.h b/include/f2fs_metadata_crypt.h
new file mode 100644
index 0000000..d15873d
--- /dev/null
+++ b/include/f2fs_metadata_crypt.h
@@ -0,0 +1,21 @@
+/**
+ * f2fs_metadata_crypt.h
+ *
+ * Copyright (c) 2020 Google LLC
+ *
+ * Dual licensed under the GPL or LGPL version 2 licenses.
+ */
+
+#include <inttypes.h>
+#include <linux/fscrypt.h>
+
+int f2fs_get_crypt_alg(const char *optarg);
+
+void f2fs_print_crypt_algs(void);
+
+int f2fs_metadata_process_key(const char *key_serial);
+
+int f2fs_metadata_verify_args(void);
+
+void *f2fs_metadata_crypt_blocks(void *src_buf, size_t len, __u64 blk_addr,
+	bool encrypt);
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 871d773..a82d753 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -2,10 +2,10 @@
 
 lib_LTLIBRARIES = libf2fs.la
 
-libf2fs_la_SOURCES = libf2fs.c libf2fs_io.c libf2fs_zoned.c nls_utf8.c
+libf2fs_la_SOURCES = libf2fs.c libf2fs_io.c libf2fs_zoned.c nls_utf8.c f2fs_metadata_crypt.c
 libf2fs_la_CFLAGS = -Wall
 libf2fs_la_CPPFLAGS = -I$(top_srcdir)/include
-libf2fs_la_LDFLAGS = -version-info $(LIBF2FS_CURRENT):$(LIBF2FS_REVISION):$(LIBF2FS_AGE)
+libf2fs_la_LDFLAGS = -lkeyutils -version-info $(LIBF2FS_CURRENT):$(LIBF2FS_REVISION):$(LIBF2FS_AGE)
 
 root_libdir=@root_libdir@
 
diff --git a/lib/f2fs_metadata_crypt.c b/lib/f2fs_metadata_crypt.c
new file mode 100644
index 0000000..faf399a
--- /dev/null
+++ b/lib/f2fs_metadata_crypt.c
@@ -0,0 +1,226 @@
+/**
+ * f2fs_metadata_crypt.c
+ *
+ * Copyright (c) 2020 Google LLC
+ *
+ * Dual licensed under the GPL or LGPL version 2 licenses.
+ */
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <linux/if_alg.h>
+#include <linux/socket.h>
+#include <assert.h>
+#include <errno.h>
+#include <keyutils.h>
+
+#include "f2fs_fs.h"
+#include "f2fs_metadata_crypt.h"
+
+extern struct f2fs_configuration c;
+struct f2fs_crypt_mode {
+	const char *friendly_name;
+	const char *cipher_str;
+	unsigned int keysize;
+	unsigned int ivlen;
+} f2fs_crypt_modes[] = {
+	[FSCRYPT_MODE_AES_256_XTS] = {
+		.friendly_name = "AES-256-XTS",
+		.cipher_str = "xts(aes)",
+		.keysize = 64,
+		.ivlen = 16,
+	},
+	[FSCRYPT_MODE_ADIANTUM] = {
+		.friendly_name = "Adiantum",
+		.cipher_str = "adiantum(xchacha12,aes)",
+		.keysize = 32,
+		.ivlen = 32,
+	},
+};
+#define MAX_IV_LEN 32
+
+void f2fs_print_crypt_algs(void)
+{
+	int i;
+
+	for (i = 1; i <= __FSCRYPT_MODE_MAX; i++) {
+		if (!f2fs_crypt_modes[i].friendly_name)
+			continue;
+		MSG(0, "\t%s\n", f2fs_crypt_modes[i].friendly_name);
+	}
+}
+
+int f2fs_get_crypt_alg(const char *optarg)
+{
+	int i;
+
+	for (i = 1; i <= __FSCRYPT_MODE_MAX; i++) {
+		if (f2fs_crypt_modes[i].friendly_name &&
+		    !strcmp(f2fs_crypt_modes[i].friendly_name, optarg)) {
+			return i;
+		}
+	}
+
+	return 0;
+}
+
+int f2fs_metadata_process_key(const char *key_serial_str)
+{
+	key_serial_t key_serial = strtol(key_serial_str, NULL, 10);
+
+	c.metadata_crypt_key_len =
+		keyctl_read_alloc(key_serial, (void **)&c.metadata_crypt_key);
+
+	if (c.metadata_crypt_key_len < 0)
+		return errno;
+
+	return 0;
+}
+
+int f2fs_metadata_verify_args(void)
+{
+	/* If neither specified, nothing to do */
+	if (!c.metadata_crypt_key && !c.metadata_crypt_alg)
+		return 0;
+
+	/* We need both specified */
+	if (!c.metadata_crypt_key || !c.metadata_crypt_alg)
+		return -EINVAL;
+
+	if (c.metadata_crypt_key_len !=
+	    f2fs_crypt_modes[c.metadata_crypt_alg].keysize) {
+		MSG(0, "\tMetadata encryption key length %d didn't match required size %d\n",
+		    c.metadata_crypt_key_len,
+		    f2fs_crypt_modes[c.metadata_crypt_alg].keysize);
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+void f2fs_metadata_crypt_gen_iv(struct af_alg_iv *iv, __u64 blk_addr)
+{
+	int i = 0;
+
+	memset(iv->iv, 0, iv->ivlen);
+
+	while (blk_addr > 0) {
+		iv->iv[i] = blk_addr & 0xFF;
+		blk_addr >>= 8;
+		i++;
+	}
+}
+
+int f2fs_metadata_crypt_block(void *buf, size_t len, __u64 blk_addr,
+			      bool encrypt)
+{
+	struct f2fs_crypt_mode *crypt_mode;
+	int sockfd, fd;
+	struct sockaddr_alg sa = {
+		.salg_family = AF_ALG,
+		.salg_type = "skcipher",
+	};
+	struct msghdr msg = {};
+	struct cmsghdr *cmsg;
+	char cbuf[CMSG_SPACE(4) + CMSG_SPACE(4 + MAX_IV_LEN)] = {0};
+	int blk_offset;
+	struct af_alg_iv *iv;
+	struct iovec iov;
+	int err;
+
+	crypt_mode = &f2fs_crypt_modes[c.metadata_crypt_alg];
+	memcpy(sa.salg_name, crypt_mode->cipher_str,
+	       strlen(crypt_mode->cipher_str));
+
+	sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
+	if (sockfd < 0)
+		return errno;
+	err = bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
+	if (err) {
+		MSG(0, "\tCouldn't bind crypto socket. Maybe support for the crypto algorithm isn't enabled?\n");
+		close(sockfd);
+		return errno;
+	}
+	err = setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, c.metadata_crypt_key,
+			 crypt_mode->keysize);
+	if (err) {
+		MSG(0, "\tCouldn't set crypto socket options.\n");
+		close(sockfd);
+		return errno;
+	}
+	fd = accept(sockfd, NULL, 0);
+	if (fd < 0)
+		goto err_out;
+
+	msg.msg_control = cbuf;
+	msg.msg_controllen = sizeof(cbuf);
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_ALG;
+	cmsg->cmsg_type = ALG_SET_OP;
+	cmsg->cmsg_len = CMSG_LEN(4);
+	*(__u32 *)CMSG_DATA(cmsg) = encrypt ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT;
+
+	cmsg = CMSG_NXTHDR(&msg, cmsg);
+	cmsg->cmsg_level = SOL_ALG;
+	cmsg->cmsg_type = ALG_SET_IV;
+	cmsg->cmsg_len = CMSG_LEN(4 + MAX_IV_LEN);
+	iv = (void *)CMSG_DATA(cmsg);
+	iv->ivlen = crypt_mode->ivlen;
+
+	iov.iov_len = F2FS_BLKSIZE;
+
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	for (blk_offset = 0; blk_offset < len / F2FS_BLKSIZE; blk_offset++) {
+		f2fs_metadata_crypt_gen_iv(iv, blk_addr + blk_offset);
+
+		iov.iov_base = (char *)buf + blk_offset * F2FS_BLKSIZE;
+
+		err = sendmsg(fd, &msg, 0);
+		if (err < 0)
+			goto err_out;
+		err = read(fd, (char *)buf + blk_offset * F2FS_BLKSIZE,
+			   F2FS_BLKSIZE);
+		if (err < 0)
+			goto err_out;
+	}
+
+	close(fd);
+	close(sockfd);
+
+	return 0;
+
+err_out:
+	err = errno;
+	close(fd);
+	close(sockfd);
+
+	return err;
+}
+
+void *f2fs_metadata_crypt_blocks(void *buf, size_t len, __u64 blk_addr,
+	bool encrypt)
+{
+	int err = 0;
+	void *enc_buf;
+
+	if (!c.metadata_crypt_key)
+		return buf;
+
+	enc_buf = malloc(len);
+	memcpy(enc_buf, buf, len);
+
+	err = f2fs_metadata_crypt_block(enc_buf, len, blk_addr, encrypt);
+	if (err) {
+		MSG(0, "\tFailed to en/decrypt blocks. Errno %d\n", err);
+		free(enc_buf);
+		return NULL;
+	}
+
+	return enc_buf;
+}
diff --git a/lib/libf2fs_io.c b/lib/libf2fs_io.c
index 138285d..f117e1e 100644
--- a/lib/libf2fs_io.c
+++ b/lib/libf2fs_io.c
@@ -33,6 +33,7 @@
 #include <assert.h>
 #include <inttypes.h>
 #include "f2fs_fs.h"
+#include "f2fs_metadata_crypt.h"
 
 struct f2fs_configuration c;
 
@@ -499,10 +500,12 @@ static int sparse_write_blk(__u64 block, int count, const void *buf) { return 0;
 static int sparse_write_zeroed_blk(__u64 block, int count) { return 0; }
 #endif
 
-int dev_read(void *buf, __u64 offset, size_t len)
+int __dev_read(void *buf, __u64 offset, size_t len, bool unencrypted)
 {
 	int fd;
+	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
 	int err;
+	void *new_buf = NULL;
 
 	if (c.sparse_mode)
 		return sparse_read_blk(offset / F2FS_BLKSIZE,
@@ -521,9 +524,29 @@ int dev_read(void *buf, __u64 offset, size_t len)
 		return -1;
 	if (read(fd, buf, len) < 0)
 		return -1;
+	if (!unencrypted) {
+		new_buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, false);
+		if (!new_buf)
+			return -1;
+		if (new_buf != buf) {
+			memcpy(buf, new_buf, len);
+			free(new_buf);
+		}
+	}
+
 	return 0;
 }
 
+int dev_read(void *buf, __u64 offset, size_t len)
+{
+	return __dev_read(buf, offset, len, false);
+}
+
+int dev_read_unencrypted(void *buf, __u64 offset, size_t len)
+{
+	return __dev_read(buf, offset, len, true);
+}
+
 #ifdef POSIX_FADV_WILLNEED
 int dev_readahead(__u64 offset, size_t len)
 #else
@@ -541,13 +564,17 @@ int dev_readahead(__u64 offset, size_t UNUSED(len))
 #endif
 }
 
-int dev_write(void *buf, __u64 offset, size_t len)
+int __dev_write(void *buf, __u64 offset, size_t len, bool unencrypted)
 {
 	int fd;
+	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
+	void *src_buf = buf;
+	int err = -1;
 
 	if (c.dry_run)
 		return 0;
 
+	/* TODO: handle sparse mode with metadata encryption */
 	if (c.sparse_mode)
 		return sparse_write_blk(offset / F2FS_BLKSIZE,
 					len / F2FS_BLKSIZE, buf);
@@ -562,11 +589,26 @@ int dev_write(void *buf, __u64 offset, size_t len)
 	 */
 	if (dcache_update_cache(fd, buf, (off64_t)offset, len) < 0)
 		return -1;
+	if (!unencrypted) {
+		buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, true);
+		if (!buf)
+			return -1;
+	}
 	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
-		return -1;
+		goto out;
 	if (write(fd, buf, len) < 0)
-		return -1;
-	return 0;
+		goto out;
+
+	err = 0;
+out:
+	if (buf != src_buf)
+		free(buf);
+	return err;
+}
+
+int dev_write(void *buf, __u64 offset, size_t len)
+{
+	return __dev_write(buf, offset, len, false);
 }
 
 int dev_write_block(void *buf, __u64 blk_addr)
@@ -574,6 +616,16 @@ int dev_write_block(void *buf, __u64 blk_addr)
 	return dev_write(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
 }
 
+static int dev_write_unencrypted(void *buf, __u64 offset, size_t len)
+{
+	return __dev_write(buf, offset, len, true);
+}
+
+int dev_write_block_unencrypted(void *buf, __u64 blk_addr)
+{
+	return dev_write_unencrypted(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
+}
+
 int dev_write_dump(void *buf, __u64 offset, size_t len)
 {
 	if (lseek64(c.dump_fd, (off64_t)offset, SEEK_SET) < 0)
@@ -586,7 +638,11 @@ int dev_write_dump(void *buf, __u64 offset, size_t len)
 int dev_fill(void *buf, __u64 offset, size_t len)
 {
 	int fd;
+	__u64 blk_addr = offset >> F2FS_BLKSIZE_BITS;
+	void *src_buf = buf;
+	int err = -1;
 
+	/* TODO: handle sparse mode with metadata encryption */
 	if (c.sparse_mode)
 		return sparse_write_zeroed_blk(offset / F2FS_BLKSIZE,
 						len / F2FS_BLKSIZE);
@@ -598,11 +654,21 @@ int dev_fill(void *buf, __u64 offset, size_t len)
 	/* Only allow fill to zero */
 	if (*((__u8*)buf))
 		return -1;
-	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
+
+	buf = f2fs_metadata_crypt_blocks(buf, len, blk_addr, true);
+	if (!buf)
 		return -1;
+
+	if (lseek64(fd, (off64_t)offset, SEEK_SET) < 0)
+		goto out;
 	if (write(fd, buf, len) < 0)
-		return -1;
-	return 0;
+		goto out;
+
+	err = 0;
+out:
+	if (buf != src_buf)
+		free(buf);
+	return err;
 }
 
 int dev_fill_block(void *buf, __u64 blk_addr)
@@ -615,6 +681,11 @@ int dev_read_block(void *buf, __u64 blk_addr)
 	return dev_read(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
 }
 
+int dev_read_block_unencrypted(void *buf, __u64 blk_addr)
+{
+	return dev_read_unencrypted(buf, blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
+}
+
 int dev_reada_block(__u64 blk_addr)
 {
 	return dev_readahead(blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
diff --git a/mkfs/f2fs_format.c b/mkfs/f2fs_format.c
index a6c542e..bf587bf 100644
--- a/mkfs/f2fs_format.c
+++ b/mkfs/f2fs_format.c
@@ -537,6 +537,9 @@ static int f2fs_prepare_super_block(void)
 		set_sb(s_encoding_flags, c.s_encoding_flags);
 	}
 
+	if (c.metadata_crypt_key)
+		set_sb(metadata_crypt_alg, c.metadata_crypt_alg);
+
 	sb->feature = c.feature;
 
 	if (get_sb(feature) & F2FS_FEATURE_SB_CHKSUM) {
@@ -1046,7 +1049,7 @@ static int f2fs_write_super_block(void)
 	memcpy(zero_buff + F2FS_SUPER_OFFSET, sb, sizeof(*sb));
 	DBG(1, "\tWriting super block, at offset 0x%08x\n", 0);
 	for (index = 0; index < 2; index++) {
-		if (dev_write_block(zero_buff, index)) {
+		if (dev_write_block_unencrypted(zero_buff, index)) {
 			MSG(1, "\tError: While while writing super_blk "
 					"on disk!!! index : %d\n", index);
 			free(zero_buff);
diff --git a/mkfs/f2fs_format_main.c b/mkfs/f2fs_format_main.c
index f2f0a80..8856850 100644
--- a/mkfs/f2fs_format_main.c
+++ b/mkfs/f2fs_format_main.c
@@ -28,6 +28,7 @@
 
 #include "f2fs_fs.h"
 #include "f2fs_format_utils.h"
+#include "f2fs_metadata_crypt.h"
 
 #ifdef WITH_ANDROID
 #include <sparse/sparse.h>
@@ -44,6 +45,7 @@ static void mkfs_usage()
 	MSG(0, "\nUsage: mkfs.f2fs [options] device [sectors]\n");
 	MSG(0, "[options]:\n");
 	MSG(0, "  -a heap-based allocation [default:0]\n");
+	MSG(0, "  -A Metadata encryption algorithm\n");
 	MSG(0, "  -c device1[,device2,...] up to 7 additional devices, except meta device\n");
 	MSG(0, "  -d debug level [default:0]\n");
 	MSG(0, "  -e [cold file ext list] e.g. \"mp3,gif,mov\"\n");
@@ -54,6 +56,7 @@ static void mkfs_usage()
 	MSG(0, "  -l label\n");
 	MSG(0, "  -U uuid\n");
 	MSG(0, "  -m support zoned block device [default:0]\n");
+	MSG(0, "  -M Metadata encryption key_serial in keyring\n");
 	MSG(0, "  -o overprovision percentage [default:auto]\n");
 	MSG(0, "  -O feature1[,feature2,...] e.g. \"encrypt\"\n");
 	MSG(0, "  -C [encoding[:flag1,...]] Support casefolding with optional flags\n");
@@ -97,6 +100,12 @@ static void f2fs_show_info()
 					f2fs_encoding2str(c.s_encoding));
 	if (c.feature & le32_to_cpu(F2FS_FEATURE_PRJQUOTA))
 		MSG(0, "Info: Enable Project quota\n");
+
+	if (c.metadata_crypt_key)
+		MSG(0, "Info: Metadata key is %s\n", c.metadata_crypt_key);
+
+	if (c.metadata_crypt_alg)
+		MSG(0, "Info: Metadata alg is %d\n", c.metadata_crypt_alg);
 }
 
 static void add_default_options(void)
@@ -125,7 +134,7 @@ static void add_default_options(void)
 
 static void f2fs_parse_options(int argc, char *argv[])
 {
-	static const char *option_string = "qa:c:C:d:e:E:g:il:mo:O:rR:s:S:z:t:T:U:Vfw:";
+	static const char *option_string = "qa:A:c:C:d:e:E:g:il:mM:o:O:rR:s:S:z:t:T:U:Vfw:";
 	int32_t option=0;
 	int val;
 	char *token;
@@ -138,6 +147,14 @@ static void f2fs_parse_options(int argc, char *argv[])
 		case 'a':
 			c.heap = atoi(optarg);
 			break;
+		case 'A':
+			c.metadata_crypt_alg = f2fs_get_crypt_alg(optarg);
+			if (c.metadata_crypt_alg < 0) {
+				MSG(0, "Error: invalid crypt algorithm specified. The choices are:");
+				f2fs_print_crypt_algs();
+				exit(1);
+			}
+			break;
 		case 'c':
 			if (c.ndevs >= MAX_DEVICES) {
 				MSG(0, "Error: Too many devices\n");
@@ -178,6 +195,12 @@ static void f2fs_parse_options(int argc, char *argv[])
 		case 'm':
 			c.zoned_mode = 1;
 			break;
+		case 'M':
+			if (f2fs_metadata_process_key(optarg)) {
+				MSG(0, "Error: Invalid metadata key\n");
+				mkfs_usage();
+			}
+			break;
 		case 'o':
 			c.overprovision = atof(optarg);
 			break;
@@ -244,6 +267,14 @@ static void f2fs_parse_options(int argc, char *argv[])
 		}
 	}
 
+	if ((!!c.metadata_crypt_key) != (!!c.metadata_crypt_alg)) {
+		MSG(0, "\tError: Both the metadata crypt key and crypt algorithm must be specified!");
+		exit(1);
+	}
+
+	if (f2fs_metadata_verify_args())
+		exit(1);
+
 	add_default_options();
 
 	if (!(c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR))) {
