diff --git a/configure b/configure
index 899116517c..49460dc6bf 100755
--- a/configure
+++ b/configure
@@ -12222,7 +12222,7 @@ done
   # defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it
   # doesn't have these OpenSSL 1.1.0 functions. So check for individual
   # functions.
-  for ac_func in OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data
+  for ac_func in OPENSSL_init_ssl OPENSSL_init_crypto BIO_get_data BIO_meth_new ASN1_STRING_get0_data
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
diff --git a/configure.in b/configure.in
index ecdf172396..fb81c77c47 100644
--- a/configure.in
+++ b/configure.in
@@ -1216,7 +1216,7 @@ if test "$with_openssl" = yes ; then
   # defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it
   # doesn't have these OpenSSL 1.1.0 functions. So check for individual
   # functions.
-  AC_CHECK_FUNCS([OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data])
+  AC_CHECK_FUNCS([OPENSSL_init_ssl OPENSSL_init_crypto BIO_get_data BIO_meth_new ASN1_STRING_get0_data])
   # OpenSSL versions before 1.1.0 required setting callback functions, for
   # thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
   # function was removed.
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 355b408b0a..0752f169ff 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7473,6 +7473,39 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
     </variablelist>
    </sect1>
 
+   <sect1 id="runtime-config-encryption">
+    <title>Encryption Key Management</title>
+
+    <variablelist>
+     <varlistentry id="guc-cluster-passphrase-command" xreflabel="cluster_passphrase_command">
+      <term><varname>cluster_passphrase_command</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>cluster_passphrase_command</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This option specifies an external command to be invoked when a passphrase
+        for key management system needs to be obtained.
+       </para>
+       <para>
+        The command must print the passphrase to the standard output and exit
+        with code 0.  In the parameter value, <literal>%p</literal> is
+        replaced by a prompt string.  (Write <literal>%%</literal> for a
+        literal <literal>%</literal>.)  Note that the prompt string will
+        probably contain whitespace, so be sure to quote adequately.  A single
+        newline is stripped from the end of the output if present.  The passphrase
+        must be at least 64 bytes.
+       </para>
+       <para>
+        This parameter can only be set in the <filename>postgresql.conf</filename>
+        file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </sect1>
+
    <sect1 id="runtime-config-client">
     <title>Client Connection Defaults</title>
 
@@ -9317,6 +9350,20 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
+      <varlistentry id="guc-key-management-enabled" xreflabel="key_management_enabled">
+      <term><varname>key_management_enabled</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary>Key management configuration parameter parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Reports whether encryption key management is enabled for this cluster.
+        See <xref linkend="app-initdb-cluster-passphrase-command"/> for more information.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-data-directory-mode" xreflabel="data_directory_mode">
       <term><varname>data_directory_mode</varname> (<type>integer</type>)
       <indexterm>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 3da2365ea9..26596cedae 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -48,6 +48,7 @@
 <!ENTITY wal           SYSTEM "wal.sgml">
 <!ENTITY logical-replication    SYSTEM "logical-replication.sgml">
 <!ENTITY jit    SYSTEM "jit.sgml">
+<!ENTITY key-management SYSTEM "key-management.sgml">
 
 <!-- programmer's guide -->
 <!ENTITY bgworker   SYSTEM "bgworker.sgml">
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7a0bb0c70a..2b288d4f46 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22295,4 +22295,80 @@ SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
 
   </sect1>
 
+  <sect1 id="functions-key-management">
+   <title>Key Management Functions</title>
+
+   <sect2 id="functions-key-management-wrap">
+    <title>Wrapping and Unwrapping Encryption Key</title>
+    <para>
+     The functions shown in
+     <xref linkend="functions-key-management-table"/> are for wrapping and
+     unwrapping the secret data with the master encryption key described in
+     <xref linkend="key-management"/>.
+    </para>
+
+    <table id="functions-key-management-table">
+     <title>Encryption Key Management <acronym>SQL</acronym> Functions</title>
+     <tgroup cols="3">
+      <thead>
+       <row>
+        <entry>Function</entry>
+        <entry>Return Type</entry>
+        <entry>Description</entry>
+       </row>
+      </thead>
+      <tbody>
+
+       <row>
+        <entry>
+         <indexterm>
+          <primary>pg_wrap</primary>
+         </indexterm>
+         <literal><function>pg_wrap(<parameter>data</parameter> <type>text</type>)</function></literal>
+        </entry>
+        <entry>
+         <type>bytea</type>
+        </entry>
+        <entry>
+         Wrap the given wrapped data with the master encryption key
+        </entry>
+       </row>
+
+       <row>
+        <entry>
+         <indexterm>
+          <primary>pg_unwrap</primary>
+         </indexterm>
+         <literal><function>pg_unwrap(<parameter>data</parameter> <type>bytea</type>)</function></literal>
+        </entry>
+        <entry>
+         <type>text</type>
+        </entry>
+        <entry>
+         Unwrap the given data with the master encryption key
+        </entry>
+       </row>
+
+       <row>
+        <entry>
+         <indexterm>
+          <primary>pg_rotate_cluster_passphrase</primary>
+         </indexterm>
+         <literal><function>pg_rotate_cluster_passphrase()</function></literal>
+        </entry>
+        <entry>
+         <type>boolean</type>
+        </entry>
+        <entry>
+         Rotate the cluster passphrase. See
+         <xref linkend="key-management-rotation"/> for details.
+        </entry>
+       </row>
+
+      </tbody>
+     </tgroup>
+    </table>
+   </sect2>
+  </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 4f89b4e930..2bf6a297b6 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -979,8 +979,9 @@ build-postgresql:
        <listitem>
         <para>
          Build with support for <acronym>SSL</acronym> (encrypted)
-         connections. This requires the <productname>OpenSSL</productname>
-         package to be installed.  <filename>configure</filename> will check
+         connections and key management. This requires the
+         <productname>OpenSSL</productname> package to be installed.
+         <filename>configure</filename> will check
          for the required header files and libraries to make sure that
          your <productname>OpenSSL</productname> installation is sufficient
          before proceeding.
diff --git a/doc/src/sgml/key-management.sgml b/doc/src/sgml/key-management.sgml
new file mode 100644
index 0000000000..63a6479f7f
--- /dev/null
+++ b/doc/src/sgml/key-management.sgml
@@ -0,0 +1,141 @@
+<!-- doc/src/sgml/key-management.sgml -->
+
+<chapter id="key-management">
+ <title>Encryption Key Management</title>
+
+ <indexterm zone="key-management">
+  <primary>key management</primary>
+ </indexterm>
+
+ <para>
+  <productname>PostgreSQL</productname> supports
+  <firstterm>Encryption Key Management System</firstterm>, which is enabled when
+  <productname>PostgreSQL</productname> is build with <literal>--with-openssl</literal>
+  and <xref linkend="app-initdb-cluster-passphrase-command"/> is specified during
+  <command>initdb</command>. The cluster passphrase provided by
+  <option>--cluster-passphrase-command</option> option during <command>initdb</command>
+  and the one generated by <xref linkend="guc-cluster-passphrase-command"/> in the
+  <filename>postgresql.conf</filename> must match, otherwise, the database cluster
+  will not startup, unless the cluster passphrase is changed described in
+  <xref linkend="key-management-rotation"/>.
+ </para>
+
+ <para>
+  The user-provided cluster passphrase is derived into a
+  <firstterm>Key Encryption Key</firstterm>, which is used to encapsulate the
+  <firstterm>Master Encryption Keys</firstterm> during the <command>initdb</command>
+  process. The encapsulated encryption keys are stored at
+  <filename>pg_cryptokeys</filename>.
+ </para>
+
+ <para>
+  Encryption key management system provides several functions to allow user to
+  use the master encryption key to wrap and unwrap their own encryption secrets such
+  as encryption key and password during encryption and decryption operations. This
+  feature allows users to encrypt and decrypt data without knowing the actual key.
+ </para>
+
+ <sect1 id="key-management-wrap-unwrap">
+  <title>Wrap and Unwrap User Secret</title>
+
+  <para>
+   Encryption key management system provides several functions described in
+   in <xref linkend="functions-key-management-table"/>, to wrap and unwrap user
+   secrets with the encryption keys, which are uniquely and securely
+   stored inside the database cluster. Wrap and unwrap functions provides
+   integrity checking, to see if the wrapped data was modified.
+  </para>
+
+  <para>
+   These functions allow user to encrypt and decrypt user data without having
+   to provide user encryption secret in plain text. One possible use case is
+   to use encryption key management system together with <xref linkend="pgcrypto"/>.
+   User wraps the user encryption secret with <function>pg_wrap</function> function
+   and passes the wrapped encryption secret to <function>pg_unwrap</function>
+   function for the <structname>pgcrypto</structname> encryption functions.
+   The wrapped secret can be stored in the application server or somewhere
+   secured and should obtained promptly for cryptographic operation with
+   <structname>pgcrypto</structname>.
+  </para>
+
+  <para>
+   Here is an example that shows how to encrypt and decrypt data together with
+   wrap and unwrap functions:
+  </para>
+
+<programlisting>
+=# SELECT pg_wrap('my secret passward');
+                                                                              pg_wrap
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ \xb2c89f76f04f95d029f179e0fc3df4ed7254127b5562a9e27d42d1cd037c942dea65ce7c0750c520fa4f4e90481c9eb7e1e42a068248c262c1a6f25c6eab64303b1154ccc9a14361223641aab4a7aabe
+(1 row)
+</programlisting>
+
+  <para>
+   Once wrapping the user key, user can encrypt and decrypt user data using the
+   wrapped user key together with the key unwrap functions:
+  </para>
+
+<programlisting>
+ =# INSERT INTO tbl
+        VALUES (pgp_sym_encrypt('secret data',
+                                 pg_unwrap('\xb2c89f76f04f95d029f179e0fc3df4ed7254127b5562a9e27d42d1cd037c942dea65ce7c0750c520fa4f4e90481c9eb7e1e42a068248c262c1a6f25c6eab64303b1154ccc9a14361223641aab4a7aabe')));
+ INSERT 1
+
+ =# SELECT * FROM tbl;
+                                                                             col
+--------------------------------------------------------------------------------------------------------------------------------------------------------------
+ \xc30d04070302a199ee38bea0320b75d23c01577bb3ffb315d67eecbeca3e40e869cea65efbf0b470f805549af905f94d94c447fbfb8113f585fc86b30c0bd784b10c9857322dc00d556aa8de14
+(1 row)
+
+ =# SELECT pgp_sym_decrypt(col,
+                           pg_unwrap('\xb2c89f76f04f95d029f179e0fc3df4ed7254127b5562a9e27d42d1cd037c942dea65ce7c0750c520fa4f4e90481c9eb7e1e42a068248c262c1a6f25c6eab64303b1154ccc9a14361223641aab4a7aabe')) as col
+    FROM tbl;
+     col
+--------------
+ secret data
+(1 row)
+</programlisting>
+
+  <para>
+   The data <literal>'secret data'</literal> is practically encrypted by the
+   user secret <literal>'my secret passward'</literal> but using wrap and
+   unwrap functions user don't need to know the actual user secret during
+   operation.
+  </para>
+ </sect1>
+
+ <sect1 id="key-management-rotation">
+  <title>Cluster Passphrase Rotation</title>
+
+  <para>
+   Encryption keys are not interminable, and possibility of key being breached
+   increases longer that a key is in use. Key rotation replaces old key with new
+   key and allows them to minimize their exposure to such an attacker. Rotating
+   keys on a regular basis help meet standardized security practices such as
+   <ulink url="https://www.pcisecuritystandards.org/">PCI-DSS</ulink> and is a
+   security best practice to limit the number of encrypted bytes available for a
+   specific key version. The key lifetime are based on key length, key strength,
+   algorithm and total number of bytes enciphered.
+  </para>
+
+  <para>
+   In <productname>PostgreSQL</productname> encryption key management, a key
+   is the cluster passphrase. Cluster passphrase rotation is represented by
+   generating a new version of the cluster passphrase, and making that version
+   as the primary version. It rotates the cluster passphrase to the new one
+   and re-encrypt the all master encryption keys with it again. The data that have
+   been encrypted with the master encryption keys doesn't need to be re-encrypted
+   again because the raw master encryption keys don't change.
+  </para>
+
+  <para>
+   To rotate the cluster passphrase user firstly needs to update
+   <xref linkend="guc-cluster-passphrase-command"/> in the
+   <filename>postgresql.conf</filename> so that
+   <productname>PostgreSQL</productname> can obtain a new encryption passphrase.
+   Then execute <function>pg_rotate_cluster_passphrase</function> function to
+   rotates the passphrase.
+  </para>
+ </sect1>
+</chapter>
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index e59cba7997..24eb37c054 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -163,6 +163,7 @@
   &wal;
   &logical-replication;
   &jit;
+  &key-management;
   &regress;
 
  </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index a04a180165..d5e8862516 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -165,6 +165,25 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-cluster-passphrase-command" xreflabel="cluster passphrase command">
+      <term><option>--cluster-passphrase-command=<replaceable class="parameter">command</replaceable></option></term>
+      <listitem>
+       <para>
+        This option specifies an external command to be invoked when a passphrase
+        for key management system needs to be obtained.
+       </para>
+       <para>
+        The command must print the passphrase to the standard output and exit
+        with code 0.  In the parameter value, <literal>%p</literal> is
+        replaced by a prompt string.  (Write <literal>%%</literal> for a
+        literal <literal>%</literal>.)  Note that the prompt string will
+        probably contain whitespace, so be sure to quote adequately.  A single
+        newline is stripped from the end of the output if present.  The passphrase
+        must be at least 64 bytes.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-D <replaceable class="parameter">directory</replaceable></option></term>
       <term><option>--pgdata=<replaceable class="parameter">directory</replaceable></option></term>
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index f64d659522..0db32540da 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -333,7 +333,8 @@ GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, b
       Copy all other files such as <filename>pg_xact</filename> and
       configuration files from the source cluster to the target cluster
       (everything except the relation files). Similarly to base backups,
-      the contents of the directories <filename>pg_dynshmem/</filename>,
+      the contents of the directories <filename>pg_cryptokeys</filename>,
+      <filename>pg_cryptokeys_temp</filename>, <filename>pg_dynshmem/</filename>,
       <filename>pg_notify/</filename>, <filename>pg_replslot/</filename>,
       <filename>pg_serial/</filename>, <filename>pg_snapshots/</filename>,
       <filename>pg_stat_tmp/</filename>, and
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 49de1d57ab..889197c50f 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -820,6 +820,13 @@ psql --username=postgres --file=script.sql postgres
    is down.
   </para>
 
+  <para>
+   During the upgrade <command>pg_upgrade</command> copies the all internal keys
+   to the new cluster. If you want to upgrade from the old cluster that enables
+   the key management to the new cluster that also enables, you must use the same
+   <varname>cluster_passphrase_command</varname> to both clusters. Otherwise
+   <command>pg_upgrade</command> fails due to mismatching the cluster passphrase.
+  </para>
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 1c19e863d2..045cee1b06 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -77,6 +77,11 @@ Item
  <entry>Subdirectory containing transaction commit timestamp data</entry>
 </row>
 
+<row>
+ <entry><filename>pg_cryptokeys</filename></entry>
+ <entry>Subdirectory containing cryptographic keys</entry>
+</row>
+
 <row>
  <entry><filename>pg_dynshmem</filename></entry>
  <entry>Subdirectory containing files used by the dynamic shared memory
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 9706a95848..4ace302038 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -21,7 +21,7 @@ SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \
 	main nodes optimizer partitioning port postmaster \
 	regex replication rewrite \
 	statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
-	jit
+	jit crypto
 
 include $(srcdir)/common.mk
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7621fc05e2..1faf38d292 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -42,6 +42,7 @@
 #include "commands/progress.h"
 #include "commands/tablespace.h"
 #include "common/controldata_utils.h"
+#include "crypto/kmgr.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
@@ -78,6 +79,7 @@
 #include "utils/timestamp.h"
 
 extern uint32 bootstrap_data_checksum_version;
+extern bool bootstrap_key_management_enabled;
 
 /* Unsupported old recovery command file names (relative to $PGDATA) */
 #define RECOVERY_COMMAND_FILE	"recovery.conf"
@@ -4550,6 +4552,7 @@ InitControlFile(uint64 sysidentifier)
 	ControlFile->wal_log_hints = wal_log_hints;
 	ControlFile->track_commit_timestamp = track_commit_timestamp;
 	ControlFile->data_checksum_version = bootstrap_data_checksum_version;
+	ControlFile->key_management_enabled = bootstrap_key_management_enabled;
 }
 
 static void
@@ -4837,6 +4840,9 @@ ReadControlFile(void)
 	/* Make the initdb settings visible as GUC variables, too */
 	SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no",
 					PGC_INTERNAL, PGC_S_OVERRIDE);
+
+	SetConfigOption("key_management", KeyManagementEnabled() ? "yes" : "no",
+					PGC_INTERNAL, PGC_S_OVERRIDE);
 }
 
 /*
@@ -4879,6 +4885,16 @@ DataChecksumsEnabled(void)
 	return (ControlFile->data_checksum_version > 0);
 }
 
+/*
+ * Are key management enabled?
+ */
+bool
+KeyManagementEnabled(void)
+{
+	Assert(ControlFile != NULL);
+	return ControlFile->key_management_enabled;
+}
+
 /*
  * Returns a fake LSN for unlogged relations.
  *
@@ -5289,6 +5305,10 @@ BootStrapXLOG(void)
 	/* some additional ControlFile fields are set in WriteControlFile() */
 	WriteControlFile();
 
+	/* Enable key manager if required */
+	if (bootstrap_key_management_enabled)
+		BootStrapKmgr();
+
 	/* Bootstrap the commit log, too */
 	BootStrapCLOG();
 	BootStrapCommitTs();
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 5480a024e0..569489cf65 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "common/link-canary.h"
+#include "crypto/kmgr.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -51,6 +52,7 @@
 #include "utils/relmapper.h"
 
 uint32		bootstrap_data_checksum_version = 0;	/* No checksum */
+bool		bootstrap_key_management_enabled = false;
 
 
 #define ALLOC(t, c) \
@@ -226,7 +228,7 @@ AuxiliaryProcessMain(int argc, char *argv[])
 	/* If no -x argument, we are a CheckerProcess */
 	MyAuxProcType = CheckerProcess;
 
-	while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1)
+	while ((flag = getopt(argc, argv, "B:c:d:D:eFkr:x:X:-:")) != -1)
 	{
 		switch (flag)
 		{
@@ -249,6 +251,9 @@ AuxiliaryProcessMain(int argc, char *argv[])
 					pfree(debugstr);
 				}
 				break;
+			case 'e':
+				bootstrap_key_management_enabled = true;
+				break;
 			case 'F':
 				SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
 				break;
diff --git a/src/backend/crypto/Makefile b/src/backend/crypto/Makefile
new file mode 100644
index 0000000000..a641860a0f
--- /dev/null
+++ b/src/backend/crypto/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile
+#    Makefile for src/backend/crypto
+#
+# IDENTIFICATION
+#    src/backend/crypto/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/crypto
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = kmgr.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/crypto/kmgr.c b/src/backend/crypto/kmgr.c
new file mode 100644
index 0000000000..470b96b117
--- /dev/null
+++ b/src/backend/crypto/kmgr.c
@@ -0,0 +1,537 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr.c
+ *	 Key manager routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * Key manager is enabled if user requests during initdb.  We have one key
+ * encryption key (KEK) and three internal keys: SQL key and two TDE keys.
+ * KEK is derived from the user-provided passphrase and used for wrapping the
+ * internal keys.  The SQL key is used for wrap and unwrap the user secret
+ * via pg_wrap and pg_unwrap SQL functions.  The two TDE keys will be used
+ * for other encryption feature such as transparent data encryption in the
+ * future.  An internal key consists of encryption key and HMAC key.  Wrapping
+ * process encrypts data and append HMAC of the encrypted data.  And unwrapping
+ * process does the integrity check of the wrapped data and decrypt data.
+ * These internal keys are wrapped by KEK and stored into each file located at
+ * KMGR_DIR.
+ *
+ * IDENTIFICATION
+ *	  src/backend/crypto/kmgr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+
+#include "common/sha2.h"
+#include "common/kmgr_utils.h"
+#include "crypto/kmgr.h"
+#include "storage/fd.h"
+#include "storage/ipc.h"
+#include "storage/shmem.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+/* GUC variables */
+bool		key_management_enabled = false;;
+char	   *cluster_passphrase_command = NULL;
+
+static MemoryContext KmgrCtx = NULL;
+
+/*
+ * Cache of the internal keys. These are used for key rotation so that
+ * we can encrypt them with the new key encryption key.
+ */
+static uint8 *internalKeys[KMGR_MAX_INTERNAL_KEYS];
+
+/* Key wrap and unwrap contexts initialized with the SQL keys */
+static KeyWrapCtx *WrapCtx = NULL;
+static KeyWrapCtx *UnwrapCtx = NULL;
+
+static void ShutdownKmgr(int code, Datum arg);
+static void generate_keys(uint8 *buf);
+static void KmgrSaveCryptoKeys(const char *dir, CryptoKeyOnDisk *keys);
+static void recoverIncompleteRotation(void);
+
+/*
+ * This function must be called ONCE on system install.
+ */
+void
+BootStrapKmgr(void)
+{
+	KeyWrapCtx *ctx;
+	uint8		keys_raw[KMGR_MAX_INTERNAL_KEYS][KMGR_KEY_AND_HMACKEY_LEN];
+	CryptoKeyOnDisk keys_disk[KMGR_MAX_INTERNAL_KEYS];
+	char		passphrase[KMGR_MAX_PASSPHRASE_LEN];
+	uint8		kek[KMGR_KEY_LEN];
+	uint8		kekhmac[KMGR_HMACKEY_LEN];
+	int			passlen;
+	int			i;
+
+	/*
+	 * Requirement check. We need openssl library to enable key management
+	 * because all encryption and decryption calls happen via openssl function
+	 * calls.
+	 */
+#ifndef USE_OPENSSL
+	ereport(ERROR,
+			(errcode(ERRCODE_CONFIG_FILE_ERROR),
+			 (errmsg("cluster encryption is not supported because OpenSSL is not supported by this build"),
+			  errhint("Compile with --with-openssl to use cluster encryption."))));
+#endif
+
+	/* Get key encryption key from the passphrase command */
+	passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+												  passphrase, KMGR_MAX_PASSPHRASE_LEN);
+	if (passlen < KMGR_MIN_PASSPHRASE_LEN)
+		ereport(ERROR,
+				(errmsg("passphrase must be more than %d bytes",
+						KMGR_MIN_PASSPHRASE_LEN)));
+
+	/* Get key encryption key and HMAC key from passphrase */
+	kmgr_derive_keys(passphrase, passlen, kek, kekhmac);
+	ctx = create_keywrap_ctx(kek, kekhmac, true);
+	if (!ctx)
+		elog(ERROR, "could not initialize cipher contect");
+
+	/* Generate all internal keys */
+	for (i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
+		generate_keys(keys_raw[i]);
+
+	/* Wrap all internal keys with key encryption key */
+	for (i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
+	{
+		int			wrapped_keylen;
+
+		if (!kmgr_wrap_key(ctx, keys_raw[i], KMGR_KEY_AND_HMACKEY_LEN,
+						   keys_disk[i].key, &wrapped_keylen))
+		{
+			free_keywrap_ctx(ctx);
+			elog(ERROR, "failed to wrap cluster encryption key");
+		}
+		Assert(wrapped_keylen == KMGR_WRAPPED_KEY_LEN);
+
+		keys_disk[i].id = i;
+	}
+
+	/* Save the internal keys to the disk */
+	KmgrSaveCryptoKeys(KMGR_DIR, keys_disk);
+
+	free_keywrap_ctx(ctx);
+}
+
+/*
+ * Get encryption key passphrase and verify it, then get the internal keys.
+ * This function is called by postmaster at startup time.
+ */
+void
+InitializeKmgr(void)
+{
+	MemoryContext oldctx;
+	CryptoKeyOnDisk *keys_disk;
+	char		passphrase[KMGR_MAX_PASSPHRASE_LEN];
+	uint8	   *sql_key;
+	uint8	   *sql_hmackey;
+	int			passlen;
+	int			i;
+	int			nkeys;
+
+	if (!key_management_enabled)
+		return;
+
+	elog(DEBUG1, "starting up key management system");
+
+	/* Recover the failure of the last passphrase rotation if necessary */
+	recoverIncompleteRotation();
+
+	/* Get the crypto keys from the file */
+	keys_disk = kmgr_get_cryptokeys(KMGR_DIR, &nkeys);
+
+	/* Get cluster passphrase */
+	passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+												  passphrase, KMGR_MAX_PASSPHRASE_LEN);
+
+	KmgrCtx = AllocSetContextCreate(TopMemoryContext,
+									"Key manager context",
+									ALLOCSET_DEFAULT_SIZES);
+	oldctx = MemoryContextSwitchTo(KmgrCtx);
+
+	/*
+	 * Verify the correctness of given passphrase using user key and internal
+	 * key.
+	 */
+	for (i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
+	{
+		uint8		keybuf[KMGR_WRAPPED_KEY_LEN];
+		uint32		keyid = keys_disk[i].id;
+
+		internalKeys[keyid] = (uint8 *) palloc(KMGR_KEY_AND_HMACKEY_LEN);
+
+		if (!kmgr_verify_passphrase(passphrase, passlen, keys_disk[i].key,
+									keybuf))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("cluster passphrase does not match expected passphrase")));
+		memcpy(internalKeys[keyid], keybuf, KMGR_KEY_AND_HMACKEY_LEN);
+	}
+
+	/*
+	 * The passphrase is correct, we wet wrap and unwrap context with the user
+	 * key. These context is used for pg_wrap and pg_unwrap.
+	 */
+	sql_key = internalKeys[KMGR_SQL_KEY_ID];
+	sql_hmackey = (uint8 *) ((char *) sql_key + KMGR_KEY_LEN);
+	WrapCtx = create_keywrap_ctx(sql_key, sql_hmackey, true);
+	UnwrapCtx = create_keywrap_ctx(sql_key, sql_hmackey, false);
+
+	MemoryContextSwitchTo(oldctx);
+	on_shmem_exit(ShutdownKmgr, 0);
+}
+
+/*
+ * This must be called once during postmaster shutdown.
+ */
+static void
+ShutdownKmgr(int code, Datum arg)
+{
+	if (WrapCtx)
+		free_keywrap_ctx(WrapCtx);
+	if (UnwrapCtx)
+		free_keywrap_ctx(UnwrapCtx);
+}
+
+/*
+ * Generate pair of the encryption and HMAC key. The buf must have
+ * sufficient space to store two keys.
+ */
+static void
+generate_keys(uint8 *buf)
+{
+	Assert(buf != NULL);
+
+	if (!pg_strong_random(buf, KMGR_KEY_LEN))
+		elog(ERROR, "failed to generate cluster encryption key");
+	if (!pg_strong_random(buf + KMGR_KEY_LEN, KMGR_HMACKEY_LEN))
+		elog(ERROR, "failed to generate cluster hmac key");
+}
+
+/*
+ * Save the given crypto keys to the disk. We don't need CRC check for crypto
+ * keys because these keys has HMAC which is used for integrity check
+ * during unwrapping.
+ */
+static void
+KmgrSaveCryptoKeys(const char *dir, CryptoKeyOnDisk *keys)
+{
+	int			i;
+
+	elog(DEBUG2, "saving all cryptographic keys");
+
+	for (i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
+	{
+		int			fd;
+		char		path[MAXPGPATH];
+
+		CryptoKeyFilePath(path, dir, keys[i].id);
+
+		if ((fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY)) < 0)
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not open file \"%s\": %m",
+							path)));
+
+		errno = 0;
+		pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_WRITE);
+		if (write(fd, &(keys[i]), sizeof(CryptoKeyOnDisk)) != sizeof(CryptoKeyOnDisk))
+		{
+			/* if write didn't set errno, assume problem is no disk space */
+			if (errno == 0)
+				errno = ENOSPC;
+
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not write file \"%s\": %m",
+							path)));
+		}
+		pgstat_report_wait_end();
+
+		pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_SYNC);
+		if (pg_fsync(fd) != 0)
+			ereport(PANIC,
+					(errcode_for_file_access(),
+					 errmsg("could not fsync file \"%s\": %m",
+							path)));
+		pgstat_report_wait_end();
+
+		if (close(fd) != 0)
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not close file \"%s\": %m",
+							path)));
+	}
+}
+
+/*
+ * SQL function to wrap the given data by the user key
+ */
+Datum
+pg_wrap(PG_FUNCTION_ARGS)
+{
+	text	   *data = PG_GETARG_TEXT_PP(0);
+	bytea	   *res;
+	int			datalen;
+	int			reslen;
+	int			len;
+
+	if (!key_management_enabled)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("could not wrap key because key management is not supported")));
+
+	datalen = VARSIZE_ANY_EXHDR(data);
+	reslen = VARHDRSZ + SizeOfWrappedKey(datalen);
+	res = palloc(reslen);
+
+	if (!kmgr_wrap_key(WrapCtx, (uint8 *) VARDATA_ANY(data), datalen,
+					   (uint8 *) VARDATA(res), &len))
+		elog(ERROR, "could not wrap the given secret");
+
+	SET_VARSIZE(res, reslen);
+
+	PG_RETURN_TEXT_P(res);
+}
+
+/*
+ * SQL function to unwrap the given data by the user key
+ */
+Datum
+pg_unwrap(PG_FUNCTION_ARGS)
+{
+	bytea	   *data = PG_GETARG_BYTEA_PP(0);
+	text	   *res;
+	int			datalen;
+	int			buflen;
+	int			len;
+
+	if (!key_management_enabled)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("could not wrap key because key management is not supported")));
+
+	datalen = VARSIZE_ANY_EXHDR(data);
+
+	/* Check if the input length is more than minimum length of wrapped key */
+	if (datalen < SizeOfWrappedKey(0))
+		ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid wrapped key input")));
+
+	buflen = VARHDRSZ + SizeOfUnwrappedKey(datalen);
+	res = palloc(buflen);
+
+	if (!kmgr_unwrap_key(UnwrapCtx, (uint8 *) VARDATA_ANY(data), datalen,
+						 (uint8 *) VARDATA(res), &len))
+		elog(ERROR, "could not unwrap the given secret");
+
+	/*
+	 * The size of unwrapped key can be smaller than the size estimated before
+	 * unwrapping since the padding is removed during unwrapping.
+	 */
+	SET_VARSIZE(res, VARHDRSZ + len);
+
+	PG_RETURN_TEXT_P(res);
+}
+
+/*
+ * SQL function to rotate the cluster passphrase. This function assumes that
+ * the cluster_passphrase_command is already reloaded to the new value.
+ * All internal keys are wrapped by the new passphrase and saved to the disk.
+ * To update all crypto keys atomically we save the newly wrapped keys to the
+ * temporary directory, pg_cryptokeys_tmp, and remove the original directory,
+ * pg_cryptokeys, and rename it. These operation is performed without the help
+ * of WAL.  In the case of failure during rotationpg_cryptokeys directory and
+ * pg_cryptokeys_tmp directory can be left in incomplete status.  We recover
+ * the incomplete situation by calling to checkIncompleteRotation.
+ */
+Datum
+pg_rotate_cluster_passphrase(PG_FUNCTION_ARGS)
+{
+	KeyWrapCtx *ctx;
+	CryptoKeyOnDisk *newkeys;
+	char		passphrase[KMGR_MAX_PASSPHRASE_LEN];
+	uint8		new_kek[KMGR_KEY_LEN];
+	uint8		new_hmackey[KMGR_HMACKEY_LEN];
+	int			passlen;
+	int			outlen;
+	int			i;
+
+	if (!key_management_enabled)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("could not rotate cluster passphrase because key management is not supported")));
+
+	/* Recover the failure of the last passphrase rotation if necessary */
+	recoverIncompleteRotation();
+
+	passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+												  passphrase,
+												  KMGR_MAX_PASSPHRASE_LEN);
+	if (passlen < KMGR_MIN_PASSPHRASE_LEN)
+		ereport(ERROR,
+				(errmsg("passphrase must be more than %d bytes",
+						KMGR_MIN_PASSPHRASE_LEN)));
+
+	/* Get new key encryption key and wrap context */
+	kmgr_derive_keys(passphrase, passlen, new_kek, new_hmackey);
+	ctx = create_keywrap_ctx(new_kek, new_hmackey, true);
+
+	newkeys = palloc(sizeof(CryptoKeyOnDisk) * KMGR_MAX_INTERNAL_KEYS);
+
+	for (i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
+	{
+		if (!kmgr_wrap_key(ctx, internalKeys[i], KMGR_KEY_AND_HMACKEY_LEN,
+						   newkeys[i].key, &outlen))
+			elog(ERROR, "failed to wrap key");
+		newkeys[i].id = i;
+		Assert(outlen == KMGR_WRAPPED_KEY_LEN);
+	}
+
+	/* Create temporary directory */
+	if (MakePGDirectory(KMGR_TMP_DIR) < 0)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not create temporary directory \"%s\": %m",
+						KMGR_TMP_DIR)));
+	fsync_fname(KMGR_TMP_DIR, true);
+
+	LWLockAcquire(KmgrFileLock, LW_EXCLUSIVE);
+
+	/* Save the key wrapped by the new passphrase to the temporary directory */
+	KmgrSaveCryptoKeys(KMGR_TMP_DIR, newkeys);
+
+	/* Remove the original directory */
+	if (!rmtree(KMGR_DIR, true))
+		ereport(ERROR,
+				(errmsg("could not remove directory \"%s\"",
+						KMGR_DIR)));
+
+	/* Rename to the original directory */
+	if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+		ereport(ERROR,
+				(errmsg("could not rename directory \"%s\" to \"%s\": %m",
+						KMGR_TMP_DIR, KMGR_DIR)));
+	fsync_fname(KMGR_DIR, true);
+
+	LWLockRelease(KmgrFileLock);
+
+	PG_RETURN_BOOL(true);
+}
+
+/*
+ * Check the last passphrase rotation was completed. If not, we decide which wrapped
+ * keys will be used according to the status of temporary directory and its wrapped
+ * keys.
+ */
+static void
+recoverIncompleteRotation(void)
+{
+	struct stat st;
+	struct stat st_tmp;
+	CryptoKeyOnDisk *keys;
+	int			nkeys_tmp;
+
+	/* The cluster passphrase rotation was completed, nothing to do */
+	if (stat(KMGR_TMP_DIR, &st_tmp) != 0)
+		return;
+
+	/*
+	 * If there is only temporary directory, it means that the previous
+	 * rotation failed after wrapping the all internal keys by the new
+	 * passphrase.  Therefore we use the new cluster passphrase.
+	 */
+	if (stat(KMGR_DIR, &st) != 0)
+	{
+		ereport(DEBUG1,
+				(errmsg("there is only temporary directory, use the newly wrapped keys",
+						KMGR_DIR, KMGR_TMP_DIR)));
+
+		if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+			ereport(ERROR,
+					errmsg("could not rename directory \"%s\" to \"%s\": %m",
+						   KMGR_TMP_DIR, KMGR_DIR));
+		ereport(LOG,
+				errmsg("cryptographic keys wrapped by new passphrase command are chosen"),
+				errdetail("last cluster passphrase rotation failed in the middle"));
+		return;
+	}
+
+	/*
+	 * In case where both the original directory and temporary directory
+	 * exist, there are two possibilities: (a) the all internal keys are
+	 * wrapped by the new passphrase but rotation failed before removing the
+	 * original directory, or (b) the rotation failed during wrapping internal
+	 * keys by the new passphrase.  In case of (a) we need to use the wrapped
+	 * keys in the temporary directory as rotation is essentially completed,
+	 * but in case of (b) we use the wrapped keys in the original directory.
+	 *
+	 * To check the possibility of (b) we validate the wrapped keys in the
+	 * temporary directory by checking the number of wrapped keys.  Since the
+	 * wrapped key length is smaller than one disk sector, which is 512 bytes
+	 * on common hardware, saving wrapped key is atomic write. So we can
+	 * ensure that the all wrapped keys are valid if the number of wrapped
+	 * keys in the temporary directory is KMGR_MAX_INTERNAL_KEYS.
+	 */
+	keys = kmgr_get_cryptokeys(KMGR_TMP_DIR, &nkeys_tmp);
+
+	if (nkeys_tmp == KMGR_MAX_INTERNAL_KEYS)
+	{
+		/*
+		 * This is case (a), the all wrapped keys in temporary directory are
+		 * valid. Remove the original directory and rename.
+		 */
+		ereport(DEBUG1,
+				(errmsg("last passphrase rotation failed before renaming direcotry name, use the newly wrapped keys")));
+
+		if (!rmtree(KMGR_DIR, true))
+			ereport(ERROR,
+					(errmsg("could not remove directory \"%s\"",
+							KMGR_DIR)));
+		if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+			ereport(ERROR,
+					errmsg("could not rename directory \"%s\" to \"%s\": %m",
+						   KMGR_TMP_DIR, KMGR_DIR));
+
+		ereport(LOG,
+				errmsg("cryptographic keys wrapped by new passphrase command are chosen"),
+				errdetail("last cluster passphrase rotation failed in the middle"));
+	}
+	else
+	{
+		/*
+		 * This is case (b), the last passphrase rotation failed during
+		 * wrapping keys. Remove the keys in the temporary directory and use
+		 * keys in the original keys.
+		 */
+		ereport(DEBUG1,
+				(errmsg("last passphrase rotation failed during wrapping keys, use the old wrapped keys")));
+
+		if (!rmtree(KMGR_TMP_DIR, true))
+			ereport(ERROR,
+					(errmsg("could not remove directory \"%s\"",
+							KMGR_DIR)));
+		ereport(LOG,
+				errmsg("cryptographic keys wrapped by old passphrase command are chosen"),
+				errdetail("last cluster passphrase rotation failed in the middle"));
+	}
+
+	pfree(keys);
+}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 4763c24be9..2ed6163a72 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3906,6 +3906,15 @@ pgstat_get_wait_io(WaitEventIO w)
 		case WAIT_EVENT_DSM_FILL_ZERO_WRITE:
 			event_name = "DSMFillZeroWrite";
 			break;
+		case WAIT_EVENT_KEY_FILE_READ:
+			event_name = "KeyFileRead";
+			break;
+		case WAIT_EVENT_KEY_FILE_WRITE:
+			event_name = "KeyFileWrite";
+			break;
+		case WAIT_EVENT_KEY_FILE_SYNC:
+			event_name = "KeyFileSync";
+			break;
 		case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ:
 			event_name = "LockFileAddToDataDirRead";
 			break;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 73d278f3b2..5ceb80a341 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -100,6 +100,7 @@
 #include "common/file_perm.h"
 #include "common/ip.h"
 #include "common/string.h"
+#include "crypto/kmgr.h"
 #include "lib/ilist.h"
 #include "libpq/auth.h"
 #include "libpq/libpq.h"
@@ -1335,6 +1336,11 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	autovac_init();
 
+	/*
+	 * Initialize key manager.
+	 */
+	InitializeKmgr();
+
 	/*
 	 * Load configuration files for client authentication.
 	 */
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index a2e28b064c..661d2236ce 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -18,6 +18,7 @@
 
 #include "access/xlog_internal.h"	/* for pg_start/stop_backup */
 #include "catalog/pg_type.h"
+#include "common/kmgr_utils.h"
 #include "common/file_perm.h"
 #include "commands/progress.h"
 #include "lib/stringinfo.h"
@@ -154,6 +155,9 @@ struct exclude_list_item
  */
 static const char *const excludeDirContents[] =
 {
+	/* Skip temporary crypto key files */
+	KMGR_TMP_DIR,
+
 	/*
 	 * Skip temporary statistics files. PG_STAT_TMP_DIR must be skipped even
 	 * when stats_temp_directory is set because PGSS_TEXT_FILE is always
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index db47843229..75161adfd8 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -49,3 +49,4 @@ MultiXactTruncationLock				41
 OldSnapshotTimeMapLock				42
 LogicalRepWorkerLock				43
 CLogTruncationLock					44
+KmgrFileLock						45
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cb8c23e4b7..8cf7be2105 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -42,6 +42,7 @@
 #include "catalog/pg_type.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
+#include "crypto/kmgr.h"
 #include "executor/spi.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
@@ -3887,6 +3888,13 @@ PostgresMain(int argc, char *argv[],
 	/* Early initialization */
 	BaseInit();
 
+	/*
+	 * Initialize kmgr for cluster encryption. Since kmgr needs to attach to
+	 * shared memory the initialization must be called after BaseInit().
+	 */
+	if (!IsUnderPostmaster)
+		InitializeKmgr();
+
 	/*
 	 * Create a per-backend PGPROC struct in shared memory, except in the
 	 * EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index af876d1f01..2029e265a7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -43,6 +43,7 @@
 #include "commands/vacuum.h"
 #include "commands/variable.h"
 #include "common/string.h"
+#include "crypto/kmgr.h"
 #include "funcapi.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -747,6 +748,8 @@ const char *const config_group_names[] =
 	gettext_noop("Statistics / Monitoring"),
 	/* STATS_COLLECTOR */
 	gettext_noop("Statistics / Query and Index Statistics Collector"),
+	/* ENCRYPTION */
+	gettext_noop("Encryption"),
 	/* AUTOVACUUM */
 	gettext_noop("Autovacuum"),
 	/* CLIENT_CONN */
@@ -2058,6 +2061,17 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"key_management", PGC_INTERNAL, PRESET_OPTIONS,
+		 gettext_noop("Show whether key management is enabled for this cluster."),
+		 NULL,
+		 GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+		},
+		&key_management_enabled,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
@@ -4315,6 +4329,16 @@ static struct config_string ConfigureNamesString[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"cluster_passphrase_command", PGC_SIGHUP, ENCRYPTION,
+			gettext_noop("Command to obtain passphrase for database encryption."),
+			NULL
+		},
+		&cluster_passphrase_command,
+		"",
+		NULL, NULL, NULL
+	},
+
 	{
 		{"application_name", PGC_USERSET, LOGGING_WHAT,
 			gettext_noop("Sets the application name to be reported in statistics and logs."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index aa44f0c9bf..146e52bbb5 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -615,6 +615,11 @@
 					# autovacuum, -1 means use
 					# vacuum_cost_limit
 
+#------------------------------------------------------------------------------
+# ENCRYPTION
+#------------------------------------------------------------------------------
+
+#cluster_passphrase_command = ''
 
 #------------------------------------------------------------------------------
 # CLIENT CONNECTION DEFAULTS
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index a6577486ce..aed263cb55 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -145,6 +145,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int	wal_segment_size_mb;
+static char *cluster_passphrase = NULL;
 
 
 /* internal vars */
@@ -202,6 +203,7 @@ static const char *const subdirs[] = {
 	"global",
 	"pg_wal/archive_status",
 	"pg_commit_ts",
+	"pg_cryptokeys",
 	"pg_dynshmem",
 	"pg_notify",
 	"pg_serial",
@@ -1206,6 +1208,13 @@ setup_config(void)
 								  "password_encryption = scram-sha-256");
 	}
 
+	if (cluster_passphrase)
+	{
+		snprintf(repltok, sizeof(repltok), "cluster_passphrase_command = '%s'",
+				 escape_quotes(cluster_passphrase));
+		conflines = replace_token(conflines, "#cluster_passphrase_command = ''", repltok);
+	}
+
 	/*
 	 * If group access has been enabled for the cluster then it makes sense to
 	 * ensure that the log files also allow group access.  Otherwise a backup
@@ -1416,14 +1425,14 @@ bootstrap_template1(void)
 	unsetenv("PGCLIENTENCODING");
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" --boot -x1 -X %u %s %s %s",
+			 "\"%s\" --boot -x1 -X %u %s %s %s %s",
 			 backend_exec,
 			 wal_segment_size_mb * (1024 * 1024),
 			 data_checksums ? "-k" : "",
+			 cluster_passphrase ? "-e" : "",
 			 boot_options,
 			 debug ? "-d 5" : "");
 
-
 	PG_CMD_OPEN;
 
 	for (line = bki_lines; *line != NULL; line++)
@@ -2311,6 +2320,8 @@ usage(const char *progname)
 	printf(_("      --wal-segsize=SIZE    size of WAL segments, in megabytes\n"));
 	printf(_("\nLess commonly used options:\n"));
 	printf(_("  -d, --debug               generate lots of debugging output\n"));
+	printf(_("  -c  --cluster-passphrase-command=COMMAND\n"
+			 "                            set command to obtain passphrase for key management\n"));
 	printf(_("  -k, --data-checksums      use data page checksums\n"));
 	printf(_("  -L DIRECTORY              where to find the input files\n"));
 	printf(_("  -n, --no-clean            do not clean up after errors\n"));
@@ -2377,7 +2388,6 @@ check_need_password(const char *authmethodlocal, const char *authmethodhost)
 	}
 }
 
-
 void
 setup_pgdata(void)
 {
@@ -2984,6 +2994,7 @@ main(int argc, char *argv[])
 		{"wal-segsize", required_argument, NULL, 12},
 		{"data-checksums", no_argument, NULL, 'k'},
 		{"allow-group-access", no_argument, NULL, 'g'},
+		{"cluster-passphrase-command", required_argument, NULL, 'c'},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3025,7 +3036,7 @@ main(int argc, char *argv[])
 
 	/* process command-line options */
 
-	while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "c:dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -3107,6 +3118,9 @@ main(int argc, char *argv[])
 			case 9:
 				pwfilename = pg_strdup(optarg);
 				break;
+			case 'c':
+				cluster_passphrase = pg_strdup(optarg);
+				break;
 			case 's':
 				show_setting = true;
 				break;
@@ -3177,6 +3191,14 @@ main(int argc, char *argv[])
 		exit(1);
 	}
 
+#ifndef USE_OPENSSL
+	if (cluster_passphrase)
+	{
+		pg_log_error("cluster encryption is not supported because OpenSSL is not supported by this build");
+		exit(1);
+	}
+#endif
+
 	check_authmethod_unspecified(&authmethodlocal);
 	check_authmethod_unspecified(&authmethodhost);
 
@@ -3244,6 +3266,11 @@ main(int argc, char *argv[])
 	else
 		printf(_("Data page checksums are disabled.\n"));
 
+	if (cluster_passphrase)
+		printf(_("Key management system is enabled.\n"));
+	else
+		printf(_("Key management system is disabled.\n"));
+
 	if (pwprompt || pwfilename)
 		get_su_pwd();
 
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index e73639df74..7ba1eb0220 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -25,6 +25,7 @@
 #include "access/xlog_internal.h"
 #include "catalog/pg_control.h"
 #include "common/controldata_utils.h"
+#include "common/kmgr_utils.h"
 #include "common/logging.h"
 #include "getopt_long.h"
 #include "pg_getopt.h"
@@ -334,5 +335,7 @@ main(int argc, char *argv[])
 		   ControlFile->data_checksum_version);
 	printf(_("Mock authentication nonce:            %s\n"),
 		   mock_auth_nonce_str);
+	printf(_("Key management:                       %s\n"),
+		   ControlFile->key_management_enabled ? _("on") : _("off"));
 	return 0;
 }
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 233441837f..b5b47eacb2 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -804,6 +804,8 @@ PrintControlValues(bool guessed)
 		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
 	printf(_("Data page checksum version:           %u\n"),
 		   ControlFile.data_checksum_version);
+	printf(_("Key management:                       %s\n"),
+		   ControlFile.key_management_enabled ? _("on") : _("off"));
 }
 
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 9088f1f80f..9e44eccb8c 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -73,6 +73,14 @@ static const char *excludeDirContents[] =
 	/* Contents removed on startup, see AsyncShmemInit(). */
 	"pg_notify",
 
+	/*
+	 * Skip cryptographic keys. It's generally not good idea to copy the
+	 * cryptographic keys from source database because these might use
+	 * different cluster passphrase.
+	 */
+	"pg_cryptokeys",			/* defined as KMGR_DIR */
+	"pg_cryptokeys_tmp",		/* defined as KMGR_TMP_DIR */
+
 	/*
 	 * Old contents are loaded for possible debugging but are not required for
 	 * normal operation, see OldSerXidInit().
diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c
index 00d71e3a8a..c7c774659e 100644
--- a/src/bin/pg_upgrade/controldata.c
+++ b/src/bin/pg_upgrade/controldata.c
@@ -9,10 +9,16 @@
 
 #include "postgres_fe.h"
 
+#include <dirent.h>
 #include <ctype.h>
 
 #include "pg_upgrade.h"
 
+#include "access/xlog_internal.h"
+#include "common/controldata_utils.h"
+#include "common/file_utils.h"
+#include "common/kmgr_utils.h"
+
 /*
  * get_control_data()
  *
@@ -59,6 +65,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
 	bool		got_date_is_int = false;
 	bool		got_data_checksum_version = false;
 	bool		got_cluster_state = false;
+	bool		got_key_management_enabled = false;
 	char	   *lc_collate = NULL;
 	char	   *lc_ctype = NULL;
 	char	   *lc_monetary = NULL;
@@ -202,6 +209,13 @@ get_control_data(ClusterInfo *cluster, bool live_check)
 		got_data_checksum_version = true;
 	}
 
+	/* Only in <= 12 */
+	if (GET_MAJOR_VERSION(cluster->major_version) <= 1200)
+	{
+		cluster->controldata.key_management_enabled = false;
+		got_key_management_enabled = true;
+	}
+
 	/* we have the result of cmd in "output". so parse it line by line now */
 	while (fgets(bufin, sizeof(bufin), output))
 	{
@@ -485,6 +499,18 @@ get_control_data(ClusterInfo *cluster, bool live_check)
 			cluster->controldata.data_checksum_version = str2uint(p);
 			got_data_checksum_version = true;
 		}
+		else if ((p = strstr(bufin, "Key management:")) != NULL)
+		{
+			p = strchr(p, ':');
+
+			if (p == NULL || strlen(p) <= 1)
+				pg_fatal("%d: controldata retrieval problem\n", __LINE__);
+
+			p++;				/* remove ':' char */
+			/* used later for contrib check */
+			cluster->controldata.key_management_enabled = strstr(p, "on") != NULL;
+			got_key_management_enabled = true;
+		}
 	}
 
 	pclose(output);
@@ -539,7 +565,8 @@ get_control_data(ClusterInfo *cluster, bool live_check)
 		!got_index || !got_toast ||
 		(!got_large_object &&
 		 cluster->controldata.ctrl_ver >= LARGE_OBJECT_SIZE_PG_CONTROL_VER) ||
-		!got_date_is_int || !got_data_checksum_version)
+		!got_date_is_int || !got_data_checksum_version ||
+		!got_key_management_enabled)
 	{
 		if (cluster == &old_cluster)
 			pg_log(PG_REPORT,
@@ -605,6 +632,10 @@ get_control_data(ClusterInfo *cluster, bool live_check)
 		if (!got_data_checksum_version)
 			pg_log(PG_REPORT, "  data checksum version\n");
 
+		/* value added in Postgres 12 */
+		if (!got_key_management_enabled)
+			pg_log(PG_REPORT, "  key management enabled\n");
+
 		pg_fatal("Cannot continue without required control information, terminating\n");
 	}
 }
@@ -669,6 +700,14 @@ check_control_data(ControlData *oldctrl,
 		pg_fatal("old cluster uses data checksums but the new one does not\n");
 	else if (oldctrl->data_checksum_version != newctrl->data_checksum_version)
 		pg_fatal("old and new cluster pg_controldata checksum versions do not match\n");
+
+	/*
+	 * We cannot upgrade if the old cluster enables the key management but
+	 * the new one doesn't support because the old one might already have
+	 * data encrypted by the master encryption key.
+	 */
+	if (oldctrl->key_management_enabled && !newctrl->key_management_enabled)
+		pg_fatal("old cluster uses key management but the new one does not\n");
 }
 
 
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index cc8a675d00..58ec5a8c82 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -11,6 +11,7 @@
 
 #include <sys/stat.h>
 #include <fcntl.h>
+#include <dirent.h>
 #ifdef HAVE_COPYFILE_H
 #include <copyfile.h>
 #endif
@@ -21,6 +22,7 @@
 
 #include "access/visibilitymap.h"
 #include "common/file_perm.h"
+#include "common/file_utils.h"
 #include "pg_upgrade.h"
 #include "storage/bufpage.h"
 #include "storage/checksum.h"
@@ -372,3 +374,78 @@ check_hard_link(void)
 
 	unlink(new_link_file);
 }
+
+/*
+ * Copy cryptographic keys from the old cluster to the new cluster.
+ */
+void
+copy_master_encryption_key(ClusterInfo *old_cluster, ClusterInfo * new_cluster)
+{
+	DIR				*dir;
+	struct dirent *de;
+	char path[MAXPGPATH];
+
+	/* We copy the crypto keys only if both clusters enable the key management */
+	if (!old_cluster->controldata.key_management_enabled ||
+		!new_cluster->controldata.key_management_enabled)
+		return;
+
+	prep_status("Copying master encryption key");
+
+	snprintf(path, MAXPGPATH, "%s/%s", old_cluster->pgdata, KMGR_DIR);
+
+	if ((dir = opendir(path)) == NULL)
+		pg_fatal("could not open directory \"%s\": %m", path);
+
+	while ((de = readdir(dir)) != NULL)
+	{
+		if (strlen(de->d_name) == 4 &&
+			strspn(de->d_name, "0123456789ABCDEF") == 4)
+		{
+			CryptoKeyOnDisk key;
+			char src_path[MAXPGPATH];
+			char dst_path[MAXPGPATH];
+			uint32	id;
+			int src_fd;
+			int dst_fd;
+			int len;
+
+			id = strtoul(de->d_name, NULL, 16);
+
+			snprintf(src_path, MAXPGPATH, "%s/%s/%04X",
+					 old_cluster->pgdata, KMGR_DIR, id);
+			snprintf(dst_path, MAXPGPATH, "%s/%s/%04X",
+					 new_cluster->pgdata, KMGR_DIR, id);
+
+			if ((src_fd = open(src_path, O_RDONLY | PG_BINARY, 0)) < 0)
+				pg_fatal("could not open file \"%s\": %m", src_path);
+
+			if ((dst_fd = open(dst_path, O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
+								pg_file_create_mode)) < 0)
+				pg_fatal("could not open file \"%s\": %m", dst_path);
+
+			/* Read the source key */
+			len = read(src_fd, &key, sizeof(CryptoKeyOnDisk));
+			if (len != sizeof(CryptoKeyOnDisk))
+			{
+				if (len < 0)
+					pg_fatal("could not read file \"%s\": %m", src_path);
+				else
+					pg_fatal("could not read file \"%s\": read %d of %zu",
+							 src_path, len, sizeof(CryptoKeyOnDisk));
+			}
+
+			/* Write to the dest key */
+			len = write(dst_fd, &key, sizeof(CryptoKeyOnDisk));
+			if (len != sizeof(CryptoKeyOnDisk))
+				pg_fatal("could not write fie \"%s\"", dst_path);
+
+			close(src_fd);
+			close(dst_fd);
+		}
+	}
+
+	closedir(dir);
+
+	check_ok();
+}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb096..5dd540e94f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -157,6 +157,13 @@ main(int argc, char **argv)
 	transfer_all_new_tablespaces(&old_cluster.dbarr, &new_cluster.dbarr,
 								 old_cluster.pgdata, new_cluster.pgdata);
 
+	/*
+	 * Copy the internal encryption keys from the old cluster to the new one.
+	 * This is necessary because the data in the old cluster might be
+	 * encrypted with the old master encryption key.
+	 */
+	copy_master_encryption_key(&old_cluster, &new_cluster);
+
 	/*
 	 * Assuming OIDs are only used in system tables, there is no need to
 	 * restore the OID counter because we have not transferred any OIDs from
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cefbe0..32ab236265 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -11,6 +11,7 @@
 #include <sys/time.h>
 
 #include "libpq-fe.h"
+#include "common/kmgr_utils.h"
 
 /* Use port in the private/dynamic port number range */
 #define DEF_PGUPORT			50432
@@ -219,6 +220,7 @@ typedef struct
 	bool		date_is_int;
 	bool		float8_pass_by_value;
 	bool		data_checksum_version;
+	bool		key_management_enabled;
 } ControlData;
 
 /*
@@ -375,6 +377,8 @@ void		rewriteVisibilityMap(const char *fromfile, const char *tofile,
 								 const char *schemaName, const char *relName);
 void		check_file_clone(void);
 void		check_hard_link(void);
+void		copy_master_encryption_key(ClusterInfo *old_cluster,
+									   ClusterInfo * new_cluster);
 
 /* fopen_priv() is no longer different from fopen() */
 #define fopen_priv(path, mode)	fopen(path, mode)
diff --git a/src/common/Makefile b/src/common/Makefile
index 6939b9d087..35f84d7683 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -48,6 +48,7 @@ LIBS += $(PTHREAD_LIBS)
 OBJS_COMMON = \
 	archive.o \
 	base64.o \
+	cipher.o \
 	config_info.o \
 	controldata_utils.o \
 	d2s.o \
@@ -59,6 +60,7 @@ OBJS_COMMON = \
 	ip.o \
 	jsonapi.o \
 	keywords.o \
+	kmgr_utils.o \
 	kwlookup.o \
 	link-canary.o \
 	md5.o \
@@ -78,6 +80,7 @@ OBJS_COMMON = \
 
 ifeq ($(with_openssl),yes)
 OBJS_COMMON += \
+	cipher_openssl.o \
 	protocol_openssl.o \
 	sha2_openssl.o
 else
diff --git a/src/common/cipher.c b/src/common/cipher.c
new file mode 100644
index 0000000000..488ae30746
--- /dev/null
+++ b/src/common/cipher.c
@@ -0,0 +1,98 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher.c
+ *	  Shared frontend/backend for cryptographic functions
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/cipher.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/cipher.h"
+#include "common/cipher_openssl.h"
+
+void
+pg_cipher_setup(void)
+{
+#ifdef USE_OPENSSL
+	ossl_cipher_setup();
+#endif
+}
+
+pg_cipher_ctx *
+pg_cipher_ctx_create(void)
+{
+#ifdef USE_OPENSSL
+	return ossl_cipher_ctx_create();
+#endif
+	return NULL;
+}
+
+void
+pg_cipher_ctx_free(pg_cipher_ctx *ctx)
+{
+#ifdef USE_OPENSSL
+	ossl_cipher_ctx_free(ctx);
+#endif
+}
+
+bool
+pg_aes256_encrypt_init(pg_cipher_ctx *ctx, uint8 *key)
+{
+#ifdef USE_OPENSSL
+	return ossl_aes256_encrypt_init(ctx, key);
+#endif
+	return false;
+}
+
+bool
+pg_aes256_decrypt_init(pg_cipher_ctx *ctx, uint8 *key)
+{
+#ifdef USE_OPENSSL
+	return ossl_aes256_decrypt_init(ctx, key);
+#endif
+	return false;
+}
+
+bool
+pg_cipher_encrypt(pg_cipher_ctx *ctx, const uint8 *input, int input_size,
+				  const uint8 *iv, uint8 *dest, int *dest_size)
+{
+	bool		r = false;
+#ifdef USE_OPENSSL
+	r = ossl_cipher_encrypt(ctx, input, input_size, iv, dest, dest_size);
+#endif
+	return r;
+}
+
+bool
+pg_cipher_decrypt(pg_cipher_ctx *ctx, const uint8 *input, int input_size,
+				  const uint8 *iv, uint8 *dest, int *dest_size)
+{
+	bool		r = false;
+#ifdef USE_OPENSSL
+	r = ossl_cipher_decrypt(ctx, input, input_size, iv, dest, dest_size);
+#endif
+	return r;
+}
+
+bool
+pg_compute_HMAC(const uint8 *key, const uint8 *data,
+				int data_size, uint8 *result, int *result_size)
+{
+	bool		r = true;
+#ifdef USE_OPENSSL
+	r = ossl_compute_HMAC(key, data, data_size, result,
+						  result_size);
+#endif
+	return r;
+}
diff --git a/src/common/cipher_openssl.c b/src/common/cipher_openssl.c
new file mode 100644
index 0000000000..56763aad3d
--- /dev/null
+++ b/src/common/cipher_openssl.c
@@ -0,0 +1,150 @@
+/*-------------------------------------------------------------------------
+ * cipher_openssl.c
+ *		Cryptographic function using OpenSSL
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the database encryption.
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/cipher_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/cipher_openssl.h"
+
+#include <openssl/conf.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/hmac.h>
+
+bool
+ossl_cipher_setup(void)
+{
+#ifdef HAVE_OPENSSL_INIT_CRYPTO
+	/* Setup OpenSSL */
+	if (!OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL))
+		return false;
+#else
+	OPENSSL_config(NULL);
+#endif
+	return false;
+}
+
+pg_cipher_ctx *
+ossl_cipher_ctx_create(void)
+{
+	return EVP_CIPHER_CTX_new();
+}
+
+void
+ossl_cipher_ctx_free(pg_cipher_ctx *ctx)
+{
+	return EVP_CIPHER_CTX_free(ctx);
+}
+
+bool
+ossl_aes256_encrypt_init(pg_cipher_ctx *ctx, uint8 *key)
+{
+	if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, NULL, NULL))
+		return false;
+	if (!EVP_CIPHER_CTX_set_key_length(ctx, PG_AES256_KEY_LEN))
+		return false;
+	if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL))
+		return false;
+
+	/*
+	 * Always enable padding. We don't need to check the return value as
+	 * EVP_CIPHER_CTX_set_padding always returns 1.
+	 */
+	EVP_CIPHER_CTX_set_padding(ctx, 1);
+
+	return true;
+}
+
+bool
+ossl_aes256_decrypt_init(pg_cipher_ctx *ctx, uint8 *key)
+{
+	if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, NULL, NULL))
+		return false;
+	if (!EVP_CIPHER_CTX_set_key_length(ctx, PG_AES256_KEY_LEN))
+		return false;
+	if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, NULL))
+		return false;
+
+	/*
+	 * Always enable padding. We don't need to check the return value as
+	 * EVP_CIPHER_CTX_set_padding always returns 1.
+	 */
+	EVP_CIPHER_CTX_set_padding(ctx, 1);
+
+	return true;
+}
+
+bool
+ossl_cipher_encrypt(pg_cipher_ctx *ctx,
+					const uint8 *in, int inlen,
+					const uint8 *iv, uint8 *out,
+					int *outlen)
+{
+	int			len;
+	int			enclen;
+
+	if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv))
+		return false;
+
+	if (!EVP_EncryptUpdate(ctx, out, &len, in, inlen))
+		return false;
+
+	enclen = len;
+
+	if (!EVP_EncryptFinal_ex(ctx, (uint8 *) ((char *) out + enclen),
+							 &len))
+		return false;
+
+	*outlen = enclen + len;
+
+	return true;
+}
+
+bool
+ossl_cipher_decrypt(pg_cipher_ctx *ctx,
+					const uint8 *in, int inlen,
+					const uint8 *iv, uint8 *out,
+					int *outlen)
+{
+	int			declen;
+	int			len;
+
+	if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv))
+		return false;
+
+	if (!EVP_DecryptUpdate(ctx, out, &len, in, inlen))
+		return false;
+
+	declen = len;
+
+	if (!EVP_DecryptFinal_ex(ctx, (uint8 *) ((char *) out + declen),
+							 &len))
+		return false;
+
+	*outlen = declen + len;
+
+	return true;
+}
+
+bool
+ossl_compute_HMAC(const uint8 *key, const uint8 *data,
+				  int data_size, uint8 *result,
+				  int *result_size)
+{
+	return HMAC(EVP_sha256(), key, PG_AES256_KEY_LEN, data,
+				(uint32) data_size, result, (uint32 *) result_size);
+}
diff --git a/src/common/kmgr_utils.c b/src/common/kmgr_utils.c
new file mode 100644
index 0000000000..759c706011
--- /dev/null
+++ b/src/common/kmgr_utils.c
@@ -0,0 +1,563 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr_utils.c
+ *	  Shared frontend/backend for cryptographic key management
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/kmgr_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef FRONTEND
+#include "common/logging.h"
+#endif
+#include "common/file_perm.h"
+#include "common/kmgr_utils.h"
+#include "common/sha2.h"
+#include "crypto/kmgr.h"
+#include "utils/elog.h"
+#include "storage/fd.h"
+
+#ifndef FRONTEND
+#include "pgstat.h"
+#include "storage/fd.h"
+#endif
+
+#define KMGR_PROMPT_MSG "Enter database encryption pass phrase:"
+
+static bool cipher_setup = false;
+
+#ifdef FRONTEND
+static FILE *open_pipe_stream(const char *command);
+static int	close_pipe_stream(FILE *file);
+#endif
+
+static void read_one_keyfile(const char *dataDir, uint32 id,
+							 CryptoKeyOnDisk *ckey_p);
+
+/*
+ * Return the key wrap context initialized with the given keys. Initialize the
+ * context for key wrapping if `for_wrap` is true, otherwise for unwrapping.
+ */
+KeyWrapCtx *
+create_keywrap_ctx(uint8 key[KMGR_KEY_LEN], uint8 hmackey[KMGR_HMACKEY_LEN],
+				   bool for_wrap)
+{
+	KeyWrapCtx *ctx;
+	int			ret;
+
+	if (!cipher_setup)
+	{
+		pg_cipher_setup();
+		cipher_setup = true;
+	}
+
+	ctx = (KeyWrapCtx *) palloc0(sizeof(KeyWrapCtx));
+
+	/* Create a cipher context */
+	ctx->cipher = pg_cipher_ctx_create();
+	if (ctx->cipher == NULL)
+		return NULL;
+
+	/* Initialize the cipher context */
+	if (for_wrap)
+		ret = pg_aes256_encrypt_init(ctx->cipher, key);
+	else
+		ret = pg_aes256_decrypt_init(ctx->cipher, key);
+
+	if (!ret)
+		return NULL;
+
+	/* Set encryption key and HMAC key */
+	memcpy(ctx->key, key, KMGR_KEY_LEN);
+	memcpy(ctx->hmackey, hmackey, KMGR_HMACKEY_LEN);
+
+	return ctx;
+}
+
+/* Free the given cipher context */
+void
+free_keywrap_ctx(KeyWrapCtx *ctx)
+{
+	if (!ctx)
+		return;
+
+	Assert(ctx->cipher);
+
+	pg_cipher_ctx_free(ctx->cipher);
+
+#ifndef FRONTEND
+	pfree(ctx);
+#else
+	pg_free(ctx);
+#endif
+}
+
+/*
+ * Verify the correctness of the given passphrase by unwrapping the `wrapped_key`
+ * by the keys extracted from the passphrase.  If the given passphrase is correct
+ * we set unwrapped keys to `raw_key` and return true.  Otherwise return false.
+ * The raw_key must have sufficient space for wrapped key length since unwrapping
+ * could use these space while unwrapped key is KMGR_KEY_AND_HMACKEY_LEN.
+ */
+bool
+kmgr_verify_passphrase(char *passphrase, int passlen,
+					   uint8 wrapped_key[KMGR_WRAPPED_KEY_LEN],
+					   uint8 raw_key[KMGR_WRAPPED_KEY_LEN])
+{
+	uint8		user_key[KMGR_KEY_LEN];
+	uint8		user_hmackey[KMGR_HMACKEY_LEN];
+	KeyWrapCtx *ctx;
+	int			keylen;
+
+	/* Extract encryption key and HMAC key from the passphrase */
+	kmgr_derive_keys(passphrase, passlen, user_key, user_hmackey);
+
+	ctx = create_keywrap_ctx(user_key, user_hmackey, false);
+	if (!kmgr_unwrap_key(ctx, wrapped_key, KMGR_WRAPPED_KEY_LEN,
+						 raw_key, &keylen))
+	{
+		/* The passphrase is not correct */
+		free_keywrap_ctx(ctx);
+		return false;
+	}
+
+	/* The passphrase is correct, free the cipher context */
+	free_keywrap_ctx(ctx);
+
+	return true;
+}
+
+/* Hash the given passphrase and extract it into encryption key and HMAC key */
+void
+kmgr_derive_keys(char *passphrase, Size passlen,
+				 uint8 key[KMGR_KEY_LEN],
+				 uint8 hmackey[KMGR_HMACKEY_LEN])
+{
+	uint8		keys[PG_SHA512_DIGEST_LENGTH];
+	pg_sha512_ctx ctx;
+
+	pg_sha512_init(&ctx);
+	pg_sha512_update(&ctx, (const uint8 *) passphrase, passlen);
+	pg_sha512_final(&ctx, keys);
+
+	/*
+	 * SHA-512 results 64 bytes. We extract it into two keys for each 32
+	 * bytes.
+	 */
+	if (key)
+		memcpy(key, keys, KMGR_KEY_LEN);
+	if (hmackey)
+		memcpy(hmackey, keys + KMGR_KEY_LEN, KMGR_HMACKEY_LEN);
+}
+
+/*
+ * Wrap the given key. Return true and set wrapped key to `out` if success.
+ * Otherwise return false. The caller must allocate sufficient space for
+ * wrapped key calculated by using SizeOfWrappedKey.
+ */
+bool
+kmgr_wrap_key(KeyWrapCtx *ctx, const uint8 *in, int inlen, uint8 *out, int *outlen)
+{
+	uint8		iv[AES_IV_SIZE];
+	uint8		hmac[KMGR_HMAC_LEN];
+	uint8	   *keyenc;
+	int			keylen;
+
+	Assert(ctx && in && out);
+
+	/* Generate IV */
+	if (!pg_strong_random(iv, AES_IV_SIZE))
+		return false;
+
+	/*
+	 * To avoid allocating the memory for encrypted data, we store encrypted
+	 * data directly into *out. Encrypted data places at the end.
+	 */
+	keyenc = (uint8 *) ((char *) out + KMGR_HMAC_LEN + AES_IV_SIZE);
+
+	if (!pg_cipher_encrypt(ctx->cipher, in, inlen, iv, keyenc, &keylen))
+		return false;
+
+	if (!kmgr_compute_HMAC(ctx, keyenc, keylen, hmac))
+		return false;
+
+	/*
+	 * Assemble the wrapped key. The order of the wrapped key is hmac, iv and
+	 * encrypted data.
+	 */
+	memcpy(out, hmac, KMGR_HMAC_LEN);
+	memcpy(out + KMGR_HMAC_LEN, iv, AES_IV_SIZE);
+
+	*outlen = SizeOfWrappedKey(inlen);
+
+	return true;
+}
+
+/*
+ * Unwrap the given key. Return true and set unwrapped key to `out` if success.
+ * Otherwise return false. The caller must allocate sufficient space for
+ * unwrapped key calculated by using SizeOfUnwrappedKey.
+ */
+bool
+kmgr_unwrap_key(KeyWrapCtx *ctx, const uint8 *in, int inlen, uint8 *out, int *outlen)
+{
+	uint8		hmac[KMGR_HMAC_LEN];
+	uint8	   *iv;
+	uint8	   *expected_hmac;
+	uint8	   *keyenc;
+	int			keylen;
+	char	   *p = (char *) in;;
+
+	Assert(ctx && in && out);
+
+	/* Disassemble the wrapped keys */
+	expected_hmac = (uint8 *) p;
+	p += KMGR_HMAC_LEN;
+	iv = (uint8 *) p;
+	p += AES_IV_SIZE;
+	keylen = inlen - (p - ((char *) in));
+	keyenc = (uint8 *) p;
+
+	/* Verify the correctness of HMAC */
+	if (!kmgr_compute_HMAC(ctx, keyenc, keylen, hmac))
+		return false;
+
+	if (memcmp(hmac, expected_hmac, KMGR_HMAC_LEN) != 0)
+		return false;
+
+	/* Decrypt encrypted data */
+	if (!pg_cipher_decrypt(ctx->cipher, keyenc, keylen, iv, out, outlen))
+		return false;
+
+	return true;
+}
+
+/*
+ * Compute HMAC of the given input. The HMAC is the fixed length,
+ * KMGR_HMAC_LEN bytes. The caller must allocate enough memory.
+ */
+bool
+kmgr_compute_HMAC(KeyWrapCtx *ctx, const uint8 *in, int inlen, uint8 *out)
+{
+	int			resultsize = 0;
+
+	Assert(ctx && in && out);
+	return pg_compute_HMAC(ctx->hmackey, in, inlen, out, &resultsize);
+}
+
+/*
+ * Run cluster passphrase command.
+ *
+ * prompt will be substituted for %p.
+ *
+ * The result will be put in buffer buf, which is of size size.
+ * The return value is the length of the actual result.
+ */
+int
+kmgr_run_cluster_passphrase_command(char *passphrase_command, char *buf,
+									int size)
+{
+	char		command[MAXPGPATH];
+	char	   *p;
+	char	   *dp;
+	char	   *endp;
+	FILE	   *fh;
+	int			pclose_rc;
+	size_t		len = 0;
+
+	Assert(size > 0);
+	buf[0] = '\0';
+
+	dp = command;
+	endp = command + MAXPGPATH - 1;
+	*endp = '\0';
+
+	for (p = passphrase_command; *p; p++)
+	{
+		if (p[0] == '%')
+		{
+			switch (p[1])
+			{
+				case 'p':
+					StrNCpy(dp, KMGR_PROMPT_MSG, strlen(KMGR_PROMPT_MSG));
+					dp += strlen(KMGR_PROMPT_MSG);
+					p++;
+					break;
+				case '%':
+					p++;
+					if (dp < endp)
+						*dp++ = *p;
+					break;
+				default:
+					if (dp < endp)
+						*dp++ = *p;
+					break;
+			}
+		}
+		else
+		{
+			if (dp < endp)
+				*dp++ = *p;
+		}
+	}
+	*dp = '\0';
+
+#ifdef FRONTEND
+	fh = open_pipe_stream(command);
+	if (fh == NULL)
+	{
+		pg_log_fatal("could not execute command \"%s\": %m",
+					 command);
+		exit(EXIT_FAILURE);
+	}
+#else
+	fh = OpenPipeStream(command, "r");
+	if (fh == NULL)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not execute command \"%s\": %m",
+						command)));
+#endif
+
+	if ((len = fread(buf, sizeof(char), size, fh)) < size)
+	{
+		if (ferror(fh))
+		{
+#ifdef FRONTEND
+			pg_log_fatal("could not read from command \"%s\": %m",
+						 command);
+			exit(EXIT_FAILURE);
+#else
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not read from command \"%s\": %m",
+							command)));
+#endif
+		}
+	}
+
+#ifdef FRONTEND
+	pclose_rc = close_pipe_stream(fh);
+#else
+	pclose_rc = ClosePipeStream(fh);
+#endif
+
+	if (pclose_rc == -1)
+	{
+#ifdef FRONTEND
+		pg_log_fatal("could not close pipe to external command: %m");
+		exit(EXIT_FAILURE);
+#else
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not close pipe to external command: %m")));
+#endif
+	}
+	else if (pclose_rc != 0)
+	{
+#ifdef FRONTEND
+		pg_log_fatal("command \"%s\" failed", command);
+		exit(EXIT_FAILURE);
+#else
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("command \"%s\" failed",
+						command),
+				 errdetail_internal("%s", wait_result_to_str(pclose_rc))));
+#endif
+	}
+
+	return len;
+}
+
+#ifdef FRONTEND
+static FILE *
+open_pipe_stream(const char *command)
+{
+	FILE	   *res;
+
+#ifdef WIN32
+	size_t		cmdlen = strlen(command);
+	char	   *buf;
+	int			save_errno;
+
+	buf = malloc(cmdlen + 2 + 1);
+	if (buf == NULL)
+	{
+		errno = ENOMEM;
+		return NULL;
+	}
+	buf[0] = '"';
+	mempcy(&buf[1], command, cmdlen);
+	buf[cmdlen + 1] = '"';
+	buf[cmdlen + 2] = '\0';
+
+	res = _popen(buf, "r");
+
+	save_errno = errno;
+	free(buf);
+	errno = save_errno;
+#else
+	res = popen(command, "r");
+#endif							/* WIN32 */
+	return res;
+}
+
+static int
+close_pipe_stream(FILE *file)
+{
+#ifdef WIN32
+	return _pclose(file);
+#else
+	return pclose(file);
+#endif							/* WIN32 */
+}
+#endif							/* FRONTEND */
+
+CryptoKeyOnDisk *
+kmgr_get_cryptokeys(const char *path, int *nkeys)
+{
+	DIR		   *dir;
+	struct dirent *de;
+	CryptoKeyOnDisk *keys;
+
+#ifndef FRONTEND
+	if ((dir = AllocateDir(path)) == NULL)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not open directory \"%s\": %m",
+						path)));
+#else
+	if ((dir = opendir(path)) == NULL)
+		pg_log_fatal("could not open directory \"%s\": %m", path);
+#endif
+
+	keys = (CryptoKeyOnDisk *) palloc0(sizeof(CryptoKeyOnDisk) * KMGR_MAX_INTERNAL_KEYS);
+	*nkeys = 0;
+
+#ifndef FRONTEND
+	while ((de = ReadDir(dir, KMGR_DIR)) != NULL)
+#else
+	while ((de = readdir(dir)) != NULL)
+#endif
+	{
+		if (strlen(de->d_name) == 4 &&
+			strspn(de->d_name, "0123456789ABCDEF") == 4)
+		{
+			uint32		id;
+
+			id = strtoul(de->d_name, NULL, 16);
+
+			if (id < 0 || id >= KMGR_MAX_INTERNAL_KEYS)
+			{
+#ifndef FRONTEND
+				elog(ERROR, "invalid cryptographic key identifier %u", id);
+#else
+				pg_log_fatal("invalid cryptographic key identifier %u", id);
+#endif
+			}
+
+			if (*nkeys >= KMGR_MAX_INTERNAL_KEYS)
+			{
+#ifndef FRONTEND
+				elog(ERROR, "too many cryptographic kes");
+#else
+				pg_log_fatal("too many cryptographic keys");
+#endif
+			}
+
+			read_one_keyfile(path, id, &(keys[id]));
+			(*nkeys)++;
+		}
+	}
+
+#ifndef FRONTEND
+	FreeDir(dir);
+#else
+	closedir(dir);
+#endif
+
+	return keys;
+}
+
+static void
+read_one_keyfile(const char *cryptoKeyDir, uint32 id, CryptoKeyOnDisk *ckey_p)
+{
+	char		path[MAXPGPATH];
+	int			fd;
+	int			r;
+
+	CryptoKeyFilePath(path, cryptoKeyDir, id);
+
+#ifndef FRONTEND
+	if ((fd = OpenTransientFile(path, O_RDONLY | PG_BINARY)) == -1)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not open file \"%s\" for reading: %m",
+						path)));
+#else
+	if ((fd = open(path, O_RDONLY | PG_BINARY, 0)) == -1)
+		pg_log_fatal("could not open file \"%s\" for reading: %m",
+					 path);
+#endif
+
+#ifndef FRONTEND
+	pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_READ);
+#endif
+
+	/* Get key bytes */
+	r = read(fd, ckey_p, sizeof(CryptoKeyOnDisk));
+	if (r != sizeof(CryptoKeyOnDisk))
+	{
+		if (r < 0)
+		{
+#ifndef FRONTEND
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not read file \"%s\": %m", path)));
+#else
+			pg_log_fatal("could not read file \"%s\": %m", path);
+#endif
+		}
+		else
+		{
+#ifndef FRONTEND
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg("could not read file \"%s\": read %d of %zu",
+							path, r, sizeof(CryptoKeyOnDisk))));
+#else
+			pg_log_fatal("could not read file \"%s\": read %d of %zu",
+						 path, r, sizeof(CryptoKeyOnDisk));
+#endif
+		}
+	}
+
+#ifndef FRONTEND
+	pgstat_report_wait_end();
+#endif
+
+#ifndef FRONTEND
+	if (CloseTransientFile(fd) != 0)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not close file \"%s\": %m",
+						path)));
+#else
+	if (close(fd) != 0)
+		pg_log_fatal("could not close file \"%s\": %m", path);
+#endif
+}
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 331497bcfb..e27f35fc34 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -15,6 +15,7 @@
 #include "access/xlogdefs.h"
 #include "access/xloginsert.h"
 #include "access/xlogreader.h"
+#include "crypto/kmgr.h"
 #include "datatype/timestamp.h"
 #include "lib/stringinfo.h"
 #include "nodes/pg_list.h"
@@ -293,6 +294,7 @@ extern void UpdateControlFile(void);
 extern uint64 GetSystemIdentifier(void);
 extern char *GetMockAuthenticationNonce(void);
 extern bool DataChecksumsEnabled(void);
+extern bool	KeyManagementEnabled(void);
 extern XLogRecPtr GetFakeLSNForUnloggedRel(void);
 extern Size XLOGShmemSize(void);
 extern void XLOGShmemInit(void);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index de5670e538..6709354334 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -17,6 +17,7 @@
 
 #include "access/transam.h"
 #include "access/xlogdefs.h"
+#include "crypto/kmgr.h"
 #include "pgtime.h"				/* for pg_time_t */
 #include "port/pg_crc32c.h"
 
@@ -226,6 +227,9 @@ typedef struct ControlFileData
 	 */
 	char		mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
 
+	/* Key management cipher. Off by default */
+	bool		key_management_enabled;
+
 	/* CRC of all above ... MUST BE LAST! */
 	pg_crc32c	crc;
 } ControlFileData;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87d25d4a4b..471c089de1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10819,4 +10819,17 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# function for key managements
+{ oid => '8200', descr => 'rotate cluter passphrase',
+  proname => 'pg_rotate_cluster_passphrase',
+  provolatile => 'v', prorettype => 'bool',
+  proargtypes => '', prosrc => 'pg_rotate_cluster_passphrase' },
+{ oid => '8201', descr => 'wrap the given data',
+  proname => 'pg_wrap',
+  provolatile => 'v', prorettype => 'bytea',
+  proargtypes => 'text', prosrc => 'pg_wrap' },
+{ oid => '8202', descr => 'unwrap the given data',
+  proname => 'pg_unwrap',
+  provolatile => 'v', prorettype => 'text',
+  proargtypes => 'bytea', prosrc => 'pg_unwrap' },
 ]
diff --git a/src/include/common/cipher.h b/src/include/common/cipher.h
new file mode 100644
index 0000000000..e46efe5650
--- /dev/null
+++ b/src/include/common/cipher.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher.h
+ *		Declarations for cryptographic functions
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/cipher.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CIPHER_H
+#define CIPHER_H
+
+#ifdef USE_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/conf.h>
+#include <openssl/err.h>
+#endif
+
+/* Key length of AES256 */
+#define PG_AES256_KEY_LEN		32
+
+/*
+ * The encrypted data is a series of blocks of size ENCRYPTION_BLOCK.
+ * Initialization vector(IV) is the same size of cipher block.
+ */
+#define AES_BLOCK_SIZE	16
+#define AES_IV_SIZE		(AES_BLOCK_SIZE)
+
+/* HMAC key and HMAC length. We use HMAC-SHA256 */
+#define PG_HMAC_SHA256_KEY_LEN		32
+#define PG_HMAC_SHA256_LEN	32
+
+#ifdef USE_OPENSSL
+typedef EVP_CIPHER_CTX pg_cipher_ctx;
+#else
+typedef void pg_cipher_ctx;
+#endif
+
+extern pg_cipher_ctx *pg_cipher_ctx_create(void);
+extern void pg_cipher_ctx_free(pg_cipher_ctx *ctx);
+extern void pg_cipher_setup(void);
+extern bool pg_aes256_encrypt_init(pg_cipher_ctx *ctx, uint8 *key);
+extern bool pg_aes256_decrypt_init(pg_cipher_ctx *ctx, uint8 *key);
+extern bool pg_cipher_encrypt(pg_cipher_ctx *ctx,
+							  const uint8 *input, int input_size,
+							  const uint8 *iv, uint8 *dest,
+							  int *dest_size);
+extern bool pg_cipher_decrypt(pg_cipher_ctx *ctx,
+							  const uint8 *input, int input_size,
+							  const uint8 *iv, uint8 *dest,
+							  int *dest_size);
+extern bool pg_compute_HMAC(const uint8 *key, const uint8 *data,
+							int data_size, uint8 *result,
+							int *result_size);
+
+#endif							/* CIPHER_H */
diff --git a/src/include/common/cipher_openssl.h b/src/include/common/cipher_openssl.h
new file mode 100644
index 0000000000..d55970b89d
--- /dev/null
+++ b/src/include/common/cipher_openssl.h
@@ -0,0 +1,39 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_openssl.h
+ *		Declarations for helper functions using OpenSSL
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/cipher_openssl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CIPHER_OPENSSL_H
+#define CIPHER_OPENSSL_H
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/cipher.h"
+
+extern pg_cipher_ctx *ossl_cipher_ctx_create(void);
+extern void ossl_cipher_ctx_free(pg_cipher_ctx *ctx);
+extern bool ossl_cipher_setup(void);
+extern bool ossl_aes256_encrypt_init(pg_cipher_ctx *ctx, uint8 *key);
+extern bool ossl_aes256_decrypt_init(pg_cipher_ctx *ctx, uint8 *key);
+extern bool ossl_cipher_encrypt(pg_cipher_ctx *ctx,
+								const uint8 *in, int inlen,
+								const uint8 *iv, uint8 *out,
+								int *outlen);
+extern bool ossl_cipher_decrypt(pg_cipher_ctx *ctx,
+								const uint8 *in, int inlen,
+								const uint8 *iv, uint8 *out,
+								int *outlen);
+extern bool ossl_compute_HMAC(const uint8 *key, const uint8 *data,
+							  int data_size, uint8 *result,
+							  int *result_size);
+#endif
diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h
new file mode 100644
index 0000000000..7af25c6dfd
--- /dev/null
+++ b/src/include/common/kmgr_utils.h
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr_utils.h
+ *		Declarations for utility function for key management
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/kmgr_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef KMGR_UTILS_H
+#define KMGR_UTILS_H
+
+#include "common/cipher.h"
+
+/*
+ * Directory where cryptographic keys reside within PGDATA. KMGR_DIR_TMP
+ * is used during cluster passphrase rotation.
+ */
+#define KMGR_DIR			"pg_cryptokeys"
+#define KMGR_TMP_DIR		"pg_cryptokeys_tmp"
+
+/* Identifiers of internal keys */
+#define KMGR_SQL_KEY_ID			0
+#define KMGR_TDE_BLOCk_KEY_ID	1
+#define KMGR_TDE_WAL_KEY_ID		2
+
+#define KMGR_MAX_INTERNAL_KEYS	3
+
+/* As of now key length supports only AES-256 key */
+#define KMGR_KEY_LEN		PG_AES256_KEY_LEN
+
+/* Key management uses HMAC-256 */
+#define KMGR_HMACKEY_LEN	PG_HMAC_SHA256_KEY_LEN
+#define KMGR_HMAC_LEN		PG_HMAC_SHA256_LEN
+
+/* Allowed length of cluster passphrase */
+#define KMGR_MIN_PASSPHRASE_LEN 64
+#define KMGR_MAX_PASSPHRASE_LEN	1024
+
+/* Wrapped key consists of HMAC of encrypted key, IV and encrypted key */
+#define KMGR_KEY_AND_HMACKEY_LEN	(KMGR_KEY_LEN + KMGR_HMACKEY_LEN)
+#define KMGR_WRAPPED_KEY_LEN \
+	(KMGR_HMAC_LEN + AES_IV_SIZE + SizeOfKeyWithPadding(KMGR_KEY_AND_HMACKEY_LEN))
+
+/*
+ * Size of encrypted key size with padding. We use PKCS#7 padding,
+ * described in RFC 5652.
+ */
+#define SizeOfKeyWithPadding(klen) \
+	((int)(klen) + (AES_BLOCK_SIZE - ((int)(klen) % AES_BLOCK_SIZE)))
+
+/*
+ * Macro to compute the size of wrapped and unwrapped key.  The wrapped
+ * key consists of HMAC of the encrypted data, IV and the encrypted data
+ * that is the same length as the input.
+ */
+#define SizeOfWrappedKey(klen) \
+	(KMGR_HMACKEY_LEN + AES_IV_SIZE + SizeOfKeyWithPadding((int)(klen)))
+#define SizeOfUnwrappedKey(klen) \
+	((int)(klen) - (KMGR_HMACKEY_LEN + AES_IV_SIZE))
+
+#define CryptoKeyFilePath(path, dir, id) \
+	snprintf((path), MAXPGPATH, "%s/%04X", (dir), (id))
+
+/* On-disk data of cryptographic keys */
+typedef struct CryptoKeyOnDisk
+{
+	/* The key's identifier */
+	uint32		id;
+
+	/* Wrapped key data */
+	uint8		key[KMGR_WRAPPED_KEY_LEN];
+} CryptoKeyOnDisk;
+
+/* Key wrapping cipher context */
+typedef struct KeyWrapCtx
+{
+	uint8		key[KMGR_KEY_LEN];
+	uint8		hmackey[KMGR_HMACKEY_LEN];
+	pg_cipher_ctx *cipher;
+} KeyWrapCtx;
+
+extern KeyWrapCtx *create_keywrap_ctx(uint8 key[KMGR_KEY_LEN],
+									  uint8 hmackey[KMGR_HMACKEY_LEN],
+									  bool for_wrap);
+extern void free_keywrap_ctx(KeyWrapCtx *ctx);
+extern void kmgr_derive_keys(char *passphrase, Size passlen,
+							 uint8 key[KMGR_KEY_LEN],
+							 uint8 hmackey[KMGR_HMACKEY_LEN]);
+extern bool kmgr_verify_passphrase(char *passphrase, int passlen,
+								   uint8 wrapped_key[KMGR_WRAPPED_KEY_LEN],
+								   uint8 raw_key[KMGR_WRAPPED_KEY_LEN]);
+extern bool kmgr_wrap_key(KeyWrapCtx *ctx, const uint8 *in, int inlen,
+						  uint8 *out, int *outlen);
+extern bool kmgr_unwrap_key(KeyWrapCtx *ctx, const uint8 *in, int inlen,
+							uint8 *out, int *outlen);
+extern bool kmgr_compute_HMAC(KeyWrapCtx *ctx, const uint8 *in, int inlen,
+							  uint8 *out);
+extern int	kmgr_run_cluster_passphrase_command(char *passphrase_command,
+												char *buf, int size);
+extern CryptoKeyOnDisk *kmgr_get_cryptokeys(const char *path, int *nkeys);
+
+#endif							/* KMGR_UTILS_H */
diff --git a/src/include/crypto/kmgr.h b/src/include/crypto/kmgr.h
new file mode 100644
index 0000000000..e3e2f87711
--- /dev/null
+++ b/src/include/crypto/kmgr.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr.h
+ *	  Key management module for transparent data encryption
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/crypto/kmgr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef KMGR_H
+#define KMGR_H
+
+#include "common/cipher.h"
+#include "common/kmgr_utils.h"
+#include "storage/relfilenode.h"
+#include "storage/bufpage.h"
+
+/* GUC parameters */
+extern bool key_management_enabled;
+extern char *cluster_passphrase_command;
+
+extern void BootStrapKmgr(void);
+extern void InitializeKmgr(void);
+
+#endif							/* KMGR_H */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 41ad209380..32b11ec72c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -385,6 +385,9 @@
 /* Define to 1 if you have the `OPENSSL_init_ssl' function. */
 #undef HAVE_OPENSSL_INIT_SSL
 
+/* Define to 1 if you have the `OPENSSL_init_crypto' function. */
+#undef HAVE_OPENSSL_INIT_CRYPTO
+
 /* Define to 1 if you have the <ossp/uuid.h> header file. */
 #undef HAVE_OSSP_UUID_H
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index a07012bf4b..6e0348ffde 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -880,6 +880,9 @@ typedef enum
 	WAIT_EVENT_DATA_FILE_TRUNCATE,
 	WAIT_EVENT_DATA_FILE_WRITE,
 	WAIT_EVENT_DSM_FILL_ZERO_WRITE,
+	WAIT_EVENT_KEY_FILE_READ,
+	WAIT_EVENT_KEY_FILE_WRITE,
+	WAIT_EVENT_KEY_FILE_SYNC,
 	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ,
 	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC,
 	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE,
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 454c2df487..c0c53b1e13 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -89,6 +89,7 @@ enum config_group
 	STATS,
 	STATS_MONITORING,
 	STATS_COLLECTOR,
+	ENCRYPTION,
 	AUTOVACUUM,
 	CLIENT_CONN,
 	CLIENT_CONN_STATEMENT,
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..5276c4184f 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -29,7 +29,7 @@ endif
 endif
 ifeq ($(with_openssl),yes)
 ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
-SUBDIRS += ssl
+SUBDIRS += ssl crypto
 endif
 endif
 
diff --git a/src/test/crypto/.gitignore b/src/test/crypto/.gitignore
new file mode 100644
index 0000000000..e07b677a7d
--- /dev/null
+++ b/src/test/crypto/.gitignore
@@ -0,0 +1,2 @@
+# Generated by regression tests
+/tmp_check/
diff --git a/src/test/crypto/Makefile b/src/test/crypto/Makefile
new file mode 100644
index 0000000000..b82e0cb554
--- /dev/null
+++ b/src/test/crypto/Makefile
@@ -0,0 +1,24 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/crypto
+#
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+#
+# src/test/crypto/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/crypto
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export with_openssl
+
+check:
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
+
+clean distclean maintainer-clean:
+	rm -rf tmp_check
diff --git a/src/test/crypto/t/001_basic.pl b/src/test/crypto/t/001_basic.pl
new file mode 100644
index 0000000000..db8323d281
--- /dev/null
+++ b/src/test/crypto/t/001_basic.pl
@@ -0,0 +1,52 @@
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More tests => 8;
+
+my $node = get_new_node('node');
+$node->init(enable_kms => 1);
+$node->start;
+
+sub test_wrap
+{
+	my ($node, $data, $test_name) = @_;
+
+	my $res = $node->safe_psql(
+		'postgres',
+		qq(
+		SELECT pg_unwrap(pg_wrap('$data'));
+		)
+	  );
+	is($res, $data, $test_name);
+}
+
+# Control file should know that checksums are disabled.
+command_like(
+	[ 'pg_controldata', $node->data_dir ],
+	qr/Key management:.*on/,
+	'key manager is enabled in control file');
+
+test_wrap($node, '123456', 'less block size');
+test_wrap($node, '1234567890123456', 'one block size');
+test_wrap($node, '12345678901234567890', 'more than one block size');
+
+# Get the token wrapped by the encryption key
+my $token = 'test_token';
+my $wrapped_token = $node->safe_psql('postgres',
+									 qq(SELECT pg_wrap('$token')));
+# Change the cluster passphrase command
+$node->safe_psql('postgres',
+				 qq(ALTER SYSTEM SET cluster_passphrase_command =
+				 'echo 1234123456789012345678901234567890123456789012345678901234567890';));
+$node->reload;
+
+my $ret = $node->safe_psql('postgres', 'SELECT pg_rotate_cluster_passphrase()');
+is($ret, 't', 'cluster passphrase rotation');
+
+$node->restart;
+
+# Unwrap the token after passphrase rotation.
+my $ret_token = $node->safe_psql('postgres',
+								 qq(SELECT pg_unwrap('$wrapped_token')));
+is($ret_token, $token, 'unwrap after passphrase rotation');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 9575268bd7..51cbd83e14 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -434,8 +434,19 @@ sub init
 	mkdir $self->backup_dir;
 	mkdir $self->archive_dir;
 
-	TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
-		@{ $params{extra} });
+	if ($params{enable_kms})
+	{
+		TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+								'--cluster-passphrase-command',
+								'echo 1234567890123456789012345678901234567890123456789012345678901234',
+								@{ $params{extra} });
+	}
+	else
+	{
+	  TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+							  @{ $params{extra} });
+	}
+
 	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
 		@{ $params{auth_extra} });
 
