Hi, 

Please find attached a reworked patch according Alvaro's comments.

Am Montag, dem 13.01.2025 um 17:06 +0100 schrieb Alvaro Herrera:
> Hello
> 
> I was passing by and I noticed that this needs badly pgindent, and
> some
> comments are enumerations that would lose formatting once through it.
> For example, this would happen which is not good:
> 
>     /*
> -    * 1. Start digest A
> -    * 2. Add the password string to digest A
> -    * 3. Add the salt to digest A
> +    * 1. Start digest A 2. Add the password string to digest A 3.
> Add the
> +    * salt to digest A
>      */
> 
> I suggest you can fix this by adding a "-" sign to the opening "/*"
> line
> so that pgindent doesn't mangle it (so it becomes /*- ).  This
> appears
> in several places.
> 

This is even documented:

https://www.postgresql.org/docs/devel/source-format.html

I ran pgindent locally and verified it works like you suggested.
> 

[...]

> Your test data (crypt-shacrypt.sql) looks a bit short.  I noticed
> that
> Drepper's SHA-crypt.txt file has a bunch of test lines (starting with
> the "Test vectors from FIPS 180-2: appendix B.1." comment line, as
> well
> as "appendix C.1" at the bottom) which perhaps could be incorporated
> into the .sql script, to ensure correctness (or at least,
> bug-compatibility with the reference implementation).  I'd also add a
> note that Drepper's implementation is public domain in crypt-sha.c's
> license block.
> 

These FIPS cases tests explicit against sha{256|512}_process_bytes() in
Drepper's code, which seem to be the equivalent to OpenSSL's
EVP_Digest*() API we're using. I am not sure it makes sense to adapt
them.

I left them out for now but adapted all the testcases for the password
hashes defined in the 2nd test cases to make sure we create the same
hashes.

> I think the "ascii_dollar" variable is a bit useless.  Why not just
> use the
> literal $ sign where needed (or a DOLLAR_CHR if we feel like avoiding
> a
> magic value there)?  Also, I wonder if it would be better to use a
> StringInfo instead of a fixed-size buffer, which would probably make
> some string manipulations easier ... Or maybe not, but let's not mix
> strlcat calls with strncat calls with no comments about why.

Result buffer via out_buf is wrapped into a StringInfo now.

> 
> Some of your elog(ERROR)s should probably be ereport(), and I'm not
> sure
> we want all the elog(DEBUG1)s.

Done, but i kept some of the DEBUG output for now. I am not sure if i
really should set the errcode for ereport() explicitly, but i did on
some places where i thought it might be useful.

This patch version also changes the original behavior of crypt() when
called for sha{256|512}crypt hashes. Before we didn't accept values for
the rounds parameter exceeding the minimum and maximum values. This was
along with gen_salt(), which also errors out in such cases. But
Drepper's code accepts them and changes them behind the scenes either
to the minimum or maximum and calculates the password hash based on
them, depending on the exceeded boundary.

So when passing a user defined salt string to crypt(), we do the same
now but i added a NOTICE to indicate that we changed the user submitted
parameter behind the scene. This also enables us to stress
px_crypt_shacrypt() with the same test cases as Drepper's code.



Thanks,

        Bernd
From 20ae93aead22cdc4886084b282741a854a163905 Mon Sep 17 00:00:00 2001
From: Bernd Helmle <Bernd Helmle maili...@oopsware.de>
Date: Mon, 20 Jan 2025 17:55:13 +0100
Subject: [PATCH] Add modern SHA-2 based password hashes to pgcrypto.

This adapts the publicly available reference implementation on
https://www.akkadia.org/drepper/SHA-crypt.txt and adds the new
hash algorithms sha256crypt and sha512crypt to crypt() and gen_salt()
respectively.
---
 contrib/pgcrypto/Makefile                    |   3 +-
 contrib/pgcrypto/crypt-gensalt.c             |  84 +++
 contrib/pgcrypto/crypt-sha.c                 | 620 +++++++++++++++++++
 contrib/pgcrypto/expected/crypt-shacrypt.out | 196 ++++++
 contrib/pgcrypto/meson.build                 |   2 +
 contrib/pgcrypto/px-crypt.c                  |  16 +
 contrib/pgcrypto/px-crypt.h                  |  31 +
 contrib/pgcrypto/sql/crypt-shacrypt.sql      |  99 +++
 doc/src/sgml/pgcrypto.sgml                   |  42 +-
 9 files changed, 1091 insertions(+), 2 deletions(-)
 create mode 100644 contrib/pgcrypto/crypt-sha.c
 create mode 100644 contrib/pgcrypto/expected/crypt-shacrypt.out
 create mode 100644 contrib/pgcrypto/sql/crypt-shacrypt.sql

diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 85f1c946813..19c124079fc 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -11,6 +11,7 @@ OBJS = \
 	crypt-des.o \
 	crypt-gensalt.o \
 	crypt-md5.o \
+	crypt-sha.o \
 	mbuf.o \
 	openssl.o \
 	pgcrypto.o \
@@ -43,7 +44,7 @@ REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
 	sha2 des 3des cast5 \
 	crypt-des crypt-md5 crypt-blowfish crypt-xdes \
 	pgp-armor pgp-decrypt pgp-encrypt pgp-encrypt-md5 $(CF_PGP_TESTS) \
-	pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-info
+	pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-info crypt-shacrypt
 
 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/contrib/pgcrypto/crypt-gensalt.c b/contrib/pgcrypto/crypt-gensalt.c
index 740f3612532..c5017bd667c 100644
--- a/contrib/pgcrypto/crypt-gensalt.c
+++ b/contrib/pgcrypto/crypt-gensalt.c
@@ -185,3 +185,87 @@ _crypt_gensalt_blowfish_rn(unsigned long count,
 
 	return output;
 }
+
+static char *
+_crypt_gensalt_sha(unsigned long count,
+				   const char *input, int size, char *output, int output_size)
+{
+	char * s_ptr = output;
+	unsigned int result_bufsize = PX_SHACRYPT_SALT_BUF_LEN;
+	int rc;
+
+	/* output buffer must be allocated with PX_MAX_SALT_LEN bytes */
+	if (PX_MAX_SALT_LEN < result_bufsize)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid size of salt")));
+	}
+
+	/*
+	 * Care must be taken to not exceed the buffer size allocated for
+	 * the input character buffer.
+	 */
+	if ((PX_SHACRYPT_SALT_LEN_MAX != size)
+		|| (output_size < size))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("invalid length of salt buffer")));
+	}
+
+	/* Skip magic bytes, set by callers */
+	s_ptr += 3;
+	if ((rc = pg_snprintf(s_ptr, 18, "rounds=%ld$", count)) <= 0)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("cannot format salt string")));
+	}
+
+	/* s_ptr should now be positioned at the start of the salt string */
+	s_ptr += rc;
+
+	/*
+	 * Normalize salt string
+	 *
+	 * size of input buffer was checked above to
+	 * not exceed PX_SHACRYPT_SALT_LEN_MAX.
+	 */
+	for (int i = 0; i < size; i++)
+	{
+		*s_ptr = _crypt_itoa64[input[i] & 0x3f];
+		s_ptr++;
+	}
+
+	/* We're done */
+	return output;
+}
+
+char *
+_crypt_gensalt_sha512_rn(unsigned long count,
+						 char const *input, int size,
+						 char *output, int output_size)
+{
+	memset(output, 0, output_size);
+	/* set magic byte for sha512crypt */
+	output[0] = '$';
+	output[1] = '6';
+	output[2] = '$';
+
+	return _crypt_gensalt_sha(count, input, size, output, output_size);
+}
+
+char *
+_crypt_gensalt_sha256_rn(unsigned long count,
+						 const char *input, int size,
+						 char *output, int output_size)
+{
+	memset(output, 0, output_size);
+	/* set magic byte for sha512crypt */
+	output[0] = '$';
+	output[1] = '5';
+	output[2] = '$';
+
+	return _crypt_gensalt_sha(count, input, size, output, output_size);
+}
\ No newline at end of file
diff --git a/contrib/pgcrypto/crypt-sha.c b/contrib/pgcrypto/crypt-sha.c
new file mode 100644
index 00000000000..383052a8bc8
--- /dev/null
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -0,0 +1,620 @@
+/*
+ * contrib/pgcrypto/crypt-sha.c
+ *
+ * This implements shacrypt password hash functions and follows the
+ * public available reference implementation from
+ *
+ * https://www.akkadia.org/drepper/SHA-crypt.txt
+ *
+ * This code is public domain.
+ *
+ * Please see the inline comments for details about the algorithm.
+ *
+ * Basically the following code implements password hashing with sha256 and
+ * sha512 digest via OpenSSL. Additionally, an extended salt generation (see
+ * crypt-gensalt.c for details) is provided, which generates a salt suitable
+ * for either sha256crypt and sha512crypt password hash generation.
+ *
+ * Official identifiers for suitable password hashes used in salts are
+ * 5 : sha256crypt and
+ * 6 : sha512crypt
+ *
+ * The hashing code below supports and uses salt length up to 16 bytes. Longer
+ * input is possible, but any additional byte of the input is disregarded.
+ * gen_salt(), when called with a sha256crypt or sha512crypt identifier will
+ * always generate a 16 byte long salt string.
+ *
+ * Output is compatible with any sha256crypt and sha512crypt output
+ * generated by e.g. OpenSSL or libc crypt().
+ *
+ * The described algorithm uses default computing rounds of 5000. Currently,
+ * even when no specific rounds specification is used, we always explicitly
+ * print out the rounds option flag with the final hash password string.
+ *
+ * The length of the specific password hash (without magic bytes and salt
+ * string) is:
+ *
+ * sha256crypt: 43 bytes and
+ * sha512crypt: 86 bytes.
+ *
+ * Overall hashed password length is:
+ *
+ * sha256crypt: 80 bytes and
+ * sha512crypt: 123 bytes
+ *
+ */
+#include "postgres.h"
+#include "miscadmin.h"
+
+#include "px-crypt.h"
+#include "px.h"
+
+typedef enum
+{
+	PGCRYPTO_SHA256CRYPT = 0,
+	PGCRYPTO_SHA512CRYPT = 1,
+	PGCRYPTO_SHA_UNKOWN
+}			PGCRYPTO_SHA_t;
+
+static unsigned char _crypt_itoa64[64 + 1] =
+"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+/*
+ * Modern UNIX password, based on SHA crypt hashes
+ */
+char *
+px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstlen)
+{
+	static const char rounds_prefix[] = "rounds=";
+	static char *magic_bytes[2] = {"$5$", "$6$"};
+
+	/* Used to create the password hash string */
+	StringInfo	out_buf = NULL;
+
+	PGCRYPTO_SHA_t type = PGCRYPTO_SHA_UNKOWN;
+	PX_MD	   *digestA = NULL;
+	PX_MD	   *digestB = NULL;
+	int			err;
+
+	const char *dec_salt_binary;	/* pointer into the real salt string */
+
+	unsigned char sha_buf[PX_SHACRYPT_DIGEST_MAX_LENGTH];
+	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LENGTH];	/* temporary buffer for
+																 * digests */
+
+	char		rounds_custom = 0;
+	char	   *p_bytes = NULL;
+	char	   *s_bytes = NULL;
+	char	   *cp = NULL;
+	const char *ep;				/* holds pointer to the end of the salt string */
+
+	size_t		buf_size = 0;	/* buffer size for sha256crypt/sha512crypt */
+	unsigned int block;			/* number of bytes processed */
+	unsigned long rounds = PX_SHACRYPT_ROUNDS_DEFAULT;
+
+	unsigned	len,
+				salt_len;
+
+	/* Init result buffer */
+	out_buf = makeStringInfoExt(PX_SHACRYPT_BUF_LEN);
+
+	/* Sanity checks */
+	if (!passwd)
+		return NULL;
+
+	if (pw == NULL)
+	{
+		elog(ERROR, "null value for password rejected");
+	}
+
+	if (salt == NULL)
+	{
+		elog(ERROR, "null value for salt rejected");
+	}
+
+	/*
+	 * Make sure result buffers are large enough.
+	 */
+	if (dstlen < PX_SHACRYPT_BUF_LEN)
+	{
+		elog(ERROR, "insufficient result buffer size to encrypt password");
+	}
+
+	/* Init contents of buffers properly */
+	memset(&sha_buf, '\0', sizeof(sha_buf));
+	memset(&sha_buf_tmp, '\0', sizeof(sha_buf_tmp));
+
+	/*
+	 * Decode the salt string. We need to know how many rounds and which
+	 * digest we have to use to hash the password.
+	 */
+	len = strlen(pw);
+	dec_salt_binary = salt;
+
+	/*
+	 * Analyze and prepare the salt string
+	 *
+	 * The magic string should be specified in the first three bytes of the
+	 * salt string. But do some sanity checks before.
+	 */
+	if (strlen(dec_salt_binary) < 3)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid salt")));
+	}
+
+	/*
+	 * Check format of magic bytes. These should define either 5=sha256crypt
+	 * or 6=sha512crypt in the second byte, enclosed by ascii dollar signs.
+	 */
+	if ((dec_salt_binary[0] != '$')
+		&& (dec_salt_binary[2] != '$'))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid format of salt")),
+				errhint("magic byte format for shacrypt is either \"$5$\" or \"$6$\""));
+	}
+
+	/*
+	 * Check magic byte for supported shacrypt digest.
+	 *
+	 * We're just interested in the very first 3 bytes of the salt string,
+	 * since this defines the digest length to use.
+	 */
+	if (strncmp(dec_salt_binary, magic_bytes[0], strlen(magic_bytes[0])) == 0)
+	{
+		type = PGCRYPTO_SHA256CRYPT;
+		dec_salt_binary += strlen(magic_bytes[0]);
+	}
+	else if (strncmp(dec_salt_binary, magic_bytes[1], strlen(magic_bytes[1])) == 0)
+	{
+		type = PGCRYPTO_SHA512CRYPT;
+		dec_salt_binary += strlen(magic_bytes[1]);
+	}
+
+	/*
+	 * dec_salt_binary pointer is positioned after the magic bytes now
+	 *
+	 * We extract any options in the following code branch. The only optional
+	 * setting we need to take care of is the "rounds" option. Note that the
+	 * salt generator already checked for invalid settings before, but we need
+	 * to do it here again to protect against injection of wrong values when
+	 * called without the generator.
+	 *
+	 * If there is any garbage added after the magic byte and the options/salt
+	 * string, we don't treat this special: This is just absorbed as part of
+	 * the salt with up to PX_SHACRYPT_SALT_LEN_MAX.
+	 *
+	 * Unknown magic byte is handled later below
+	 */
+	if (strncmp(dec_salt_binary,
+				rounds_prefix, sizeof(rounds_prefix) - 1) == 0)
+	{
+
+		const char *num = dec_salt_binary + sizeof(rounds_prefix) - 1;
+		char	   *endp;
+		long		srounds = strtoul(num, &endp, 10);
+
+		if (*endp == '$')
+		{
+			dec_salt_binary = endp + 1;
+
+			/*
+			 * We violate supported lower or upper bound of rounds, but in
+			 * this case we change this value to the supported lower or upper
+			 * value. We don't do this silently and print a NOTICE in such a
+			 * case.
+			 *
+			 * Note that a salt string generated with gen_salt() would never
+			 * generated such a salt string, since it would error out.
+			 *
+			 * But Drepper's upstream reference implementation supports this
+			 * when passing the salt string directly, so we maintain
+			 * compatibility here.
+			 */
+			if (srounds > PX_SHACRYPT_ROUNDS_MAX)
+			{
+				ereport(NOTICE,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("rounds=%ld exceeds maximum supported value (%ld), using %ld instead",
+							   srounds, PX_SHACRYPT_ROUNDS_MAX,
+							   PX_SHACRYPT_ROUNDS_MAX));
+				srounds = PX_SHACRYPT_ROUNDS_MAX;
+			}
+			else if (srounds < PX_SHACRYPT_ROUNDS_MIN)
+			{
+				ereport(NOTICE,
+						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+						errmsg("rounds=%ld is below supported value (%ld), using %ld instead",
+							   srounds, PX_SHACRYPT_ROUNDS_MIN,
+							   PX_SHACRYPT_ROUNDS_MIN));
+				srounds = PX_SHACRYPT_ROUNDS_MIN;
+			}
+
+			rounds = (unsigned long) srounds;
+			rounds_custom = 1;
+		}
+		else
+		{
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("could not parse salt options"));
+		}
+
+	}
+
+	/*
+	 * Choose the correct digest length and add the magic bytes to the result
+	 * buffer. Also handle possible invalid magic byte we've extracted above.
+	 */
+	switch (type)
+	{
+		case PGCRYPTO_SHA256CRYPT:
+			{
+				/* Two PX_MD objects required */
+				err = px_find_digest("sha256", &digestA);
+				if (err)
+					goto error;
+
+				err = px_find_digest("sha256", &digestB);
+				if (err)
+					goto error;
+
+				/* digest buffer length is 32 for sha256 */
+				buf_size = 32;
+
+				appendStringInfoString(out_buf, magic_bytes[0]);
+				break;
+			}
+
+		case PGCRYPTO_SHA512CRYPT:
+			{
+				/* Two PX_MD objects required */
+				err = px_find_digest("sha512", &digestA);
+				if (err)
+					goto error;
+
+				err = px_find_digest("sha512", &digestB);
+				if (err)
+					goto error;
+
+				buf_size = PX_SHACRYPT_DIGEST_MAX_LENGTH;
+
+				appendStringInfoString(out_buf, magic_bytes[1]);
+				break;
+			}
+
+		case PGCRYPTO_SHA_UNKOWN:
+			elog(ERROR, "unknown crypt identifier \"%c\"", salt[1]);
+	}
+
+	if (rounds_custom > 0)
+	{
+		appendStringInfo(out_buf, "rounds=%lu$", rounds);
+	}
+
+	/*
+	 * We need the real length of the decoded salt string, this is every
+	 * character before the last '$' in the preamble. Append every character
+	 * up to PX_SHACRYPT_SALT_LEN_MAX to the result buffer.
+	 */
+	for (ep = dec_salt_binary;
+		 *ep && *ep != '$' && ep < (dec_salt_binary + PX_SHACRYPT_SALT_LEN_MAX);
+		 ep++)
+		appendStringInfoCharMacro(out_buf, *ep);
+	salt_len = ep - dec_salt_binary;
+
+	elog(DEBUG1, "using salt len = %d", salt_len);
+
+	/*
+	 * Sanity check:
+	 *
+	 * At this point the salt string buffer must not exceed expected size
+	 */
+	if (out_buf->len > (3 + 17 * rounds_custom + salt_len))
+	{
+		elog(ERROR, "unexpected length of salt string");
+	}
+
+	/*-
+	 * 1. Start digest A
+	 * 2. Add the password string to digest A
+	 * 3. Add the salt to digest A
+	 */
+	px_md_update(digestA, (const unsigned char *) pw, len);
+	px_md_update(digestA, (const unsigned char *) dec_salt_binary, salt_len);
+
+	/*-
+	 * 4. Create digest B
+	 * 5. Add password to digest B
+	 * 6. Add the salt string to digest B
+	 * 7. Add the password again to digest B
+	 * 8. Finalize digest B
+	 */
+	px_md_update(digestB, (const unsigned char *) pw, len);
+	px_md_update(digestB, (const unsigned char *) dec_salt_binary, salt_len);
+	px_md_update(digestB, (const unsigned char *) pw, len);
+	px_md_finish(digestB, sha_buf);
+
+	/*-
+	 * 9. For each block of (excluding the NULL byte), add
+	 *    digest B to digest A.
+	 */
+	for (block = len; block > buf_size; block -= buf_size)
+	{
+		px_md_update(digestA, sha_buf, buf_size);
+	}
+
+	/*-
+	 * 10 For the remaining N bytes of the password string, add
+	 * the first N bytes of digest B to A */
+	px_md_update(digestA, sha_buf, block);
+
+	/*-
+	 * 11 For each bit of the binary representation of the length of the
+	 * password string up to and including the highest 1-digit, starting
+	 * from to lowest bit position (numeric value 1)
+	 *
+	 * a) for a 1-digit add digest B (sha_buf) to digest A
+	 * b) for a 0-digit add the password string
+	 */
+
+	block = len;
+	while (block)
+	{
+		px_md_update(digestA,
+					 (block & 1) ? sha_buf : (const unsigned char *) pw,
+					 (block & 1) ? buf_size : len);
+
+		/* right shift to next byte */
+		block >>= 1;
+	}
+
+	/* 12 Finalize digest A */
+	px_md_finish(digestA, sha_buf);
+
+	/* 13 Start digest DP */
+	px_md_reset(digestB);
+
+	/*-
+	 * 14 Add every byte of the password string (excluding trailing NULL)
+	 * to the digest DP
+	 */
+	for (block = len; block > 0; block--)
+	{
+		px_md_update(digestB, (const unsigned char *) pw, len);
+	}
+
+	/* 15 Finalize digest DP */
+	px_md_finish(digestB, sha_buf_tmp);
+
+	/*-
+	 * 16 produce byte sequence P with same length as password.
+	 *
+	 *     a) for each block of 32 or 64 bytes of length of the password
+	 *        string the entire digest DP is used
+	 *     b) for the remaining N (up to  31 or 63) bytes use the
+	 *         first N bytes of digest DP
+	 */
+	if ((p_bytes = palloc0(len)) == NULL)
+	{
+		goto error;
+	}
+
+	/* N step of 16, copy over the bytes from password */
+	for (cp = p_bytes, block = len; block > buf_size; block -= buf_size, cp += buf_size)
+		memcpy(cp, sha_buf_tmp, buf_size);
+	memcpy(cp, sha_buf_tmp, block);
+
+	/*
+	 * 17 Start digest DS
+	 */
+	px_md_reset(digestB);
+
+	/*-
+	 * 18 Repeat the following 16+A[0] times, where A[0] represents the first
+	 *    byte in digest A interpreted as an 8-bit unsigned value
+	 *    add the salt to digest DS
+	 */
+	for (block = 16 + sha_buf[0]; block > 0; block--)
+	{
+		px_md_update(digestB, (const unsigned char *) dec_salt_binary, salt_len);
+	}
+
+	/*
+	 * 19 Finalize digest DS
+	 */
+	px_md_finish(digestB, sha_buf_tmp);
+
+	/*-
+	 * 20 Produce byte sequence S of the same length as the salt string where
+	 *
+	 * a) for each block of 32 or 64 bytes of length of the salt string the
+	 *     entire digest DS is used
+	 *
+	 * b) for the remaining N (up to  31 or 63) bytes use the first N
+	 *    bytes of digest DS
+	 */
+	if ((s_bytes = palloc0(salt_len)) == NULL)
+		goto error;
+
+	for (cp = s_bytes, block = salt_len; block > buf_size; block -= buf_size, cp += buf_size)
+	{
+		memcpy(cp, sha_buf_tmp, buf_size);
+	}
+	memcpy(cp, sha_buf_tmp, block);
+
+	/* Make sure we don't leave something important behind */
+	px_memset(&sha_buf_tmp, 0, sizeof sha_buf);
+
+	/*-
+	 * 21 Repeat a loop according to the number specified in the rounds=<N>
+	 *    specification in the salt (or the default value if none is
+	 *    present).  Each round is numbered, starting with 0 and up to N-1.
+	 *
+	 *    The loop uses a digest as input.  In the first round it is the
+	 *    digest produced in step 12.  In the latter steps it is the digest
+	 *    produced in step 21.h of the previous round.  The following text
+	 *    uses the notation "digest A/B" to describe this behavior.
+	 */
+	for (block = 0; block < rounds; block++)
+	{
+
+		/*
+		 * Make it possible to abort in case large values for "rounds" are
+		 * specified.
+		 */
+		CHECK_FOR_INTERRUPTS();
+
+		/* a) start digest B */
+		px_md_reset(digestB);
+
+		/*
+		 * b) for odd round numbers add the byte sequense P to digest B c) for
+		 * even round numbers add digest A/B
+		 */
+		px_md_update(digestB,
+					 (block & 1) ? (const unsigned char *) p_bytes : sha_buf,
+					 (block & 1) ? len : buf_size);
+
+		/* d) for all round numbers not divisible by 3 add the byte sequence S */
+		if (block % 3)
+		{
+			px_md_update(digestB, (const unsigned char *) s_bytes, salt_len);
+		}
+
+		/* e) for all round numbers not divisible by 7 add the byte sequence P */
+		if (block % 7)
+		{
+			px_md_update(digestB, (const unsigned char *) p_bytes, len);
+		}
+
+		/*
+		 * f) for odd round numbers add digest A/C g) for even round numbers
+		 * add the byte sequence P
+		 */
+		px_md_update(digestB,
+					 (block & 1) ? sha_buf : (const unsigned char *) p_bytes,
+					 (block & 1) ? buf_size : len);
+
+		/* h) finish digest C. */
+		px_md_finish(digestB, sha_buf);
+
+	}
+
+	px_md_free(digestA);
+	px_md_free(digestB);
+
+	digestA = NULL;
+	digestB = NULL;
+
+	pfree(s_bytes);
+	pfree(p_bytes);
+
+	s_bytes = NULL;
+	p_bytes = NULL;
+
+	/* prepare final result buffer */
+	appendStringInfoCharMacro(out_buf, '$');
+
+#define b64_from_24bit(B2, B1, B0, N)                      \
+	do {                                                    \
+		unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
+		int i = (N);                                        \
+		while (i-- > 0)                                     \
+		{                                                   \
+			appendStringInfoCharMacro(out_buf, _crypt_itoa64[w & 0x3f]); \
+			w >>= 6;                                        \
+		}                                                   \
+	} while (0)
+
+	switch (type)
+	{
+		case PGCRYPTO_SHA256CRYPT:
+			{
+				b64_from_24bit(sha_buf[0], sha_buf[10], sha_buf[20], 4);
+				b64_from_24bit(sha_buf[21], sha_buf[1], sha_buf[11], 4);
+				b64_from_24bit(sha_buf[12], sha_buf[22], sha_buf[2], 4);
+				b64_from_24bit(sha_buf[3], sha_buf[13], sha_buf[23], 4);
+				b64_from_24bit(sha_buf[24], sha_buf[4], sha_buf[14], 4);
+				b64_from_24bit(sha_buf[15], sha_buf[25], sha_buf[5], 4);
+				b64_from_24bit(sha_buf[6], sha_buf[16], sha_buf[26], 4);
+				b64_from_24bit(sha_buf[27], sha_buf[7], sha_buf[17], 4);
+				b64_from_24bit(sha_buf[18], sha_buf[28], sha_buf[8], 4);
+				b64_from_24bit(sha_buf[9], sha_buf[19], sha_buf[29], 4);
+				b64_from_24bit(0, sha_buf[31], sha_buf[30], 3);
+
+				break;
+			}
+
+		case PGCRYPTO_SHA512CRYPT:
+			{
+				b64_from_24bit(sha_buf[0], sha_buf[21], sha_buf[42], 4);
+				b64_from_24bit(sha_buf[22], sha_buf[43], sha_buf[1], 4);
+				b64_from_24bit(sha_buf[44], sha_buf[2], sha_buf[23], 4);
+				b64_from_24bit(sha_buf[3], sha_buf[24], sha_buf[45], 4);
+				b64_from_24bit(sha_buf[25], sha_buf[46], sha_buf[4], 4);
+				b64_from_24bit(sha_buf[47], sha_buf[5], sha_buf[26], 4);
+				b64_from_24bit(sha_buf[6], sha_buf[27], sha_buf[48], 4);
+				b64_from_24bit(sha_buf[28], sha_buf[49], sha_buf[7], 4);
+				b64_from_24bit(sha_buf[50], sha_buf[8], sha_buf[29], 4);
+				b64_from_24bit(sha_buf[9], sha_buf[30], sha_buf[51], 4);
+				b64_from_24bit(sha_buf[31], sha_buf[52], sha_buf[10], 4);
+				b64_from_24bit(sha_buf[53], sha_buf[11], sha_buf[32], 4);
+				b64_from_24bit(sha_buf[12], sha_buf[33], sha_buf[54], 4);
+				b64_from_24bit(sha_buf[34], sha_buf[55], sha_buf[13], 4);
+				b64_from_24bit(sha_buf[56], sha_buf[14], sha_buf[35], 4);
+				b64_from_24bit(sha_buf[15], sha_buf[36], sha_buf[57], 4);
+				b64_from_24bit(sha_buf[37], sha_buf[58], sha_buf[16], 4);
+				b64_from_24bit(sha_buf[59], sha_buf[17], sha_buf[38], 4);
+				b64_from_24bit(sha_buf[18], sha_buf[39], sha_buf[60], 4);
+				b64_from_24bit(sha_buf[40], sha_buf[61], sha_buf[19], 4);
+				b64_from_24bit(sha_buf[62], sha_buf[20], sha_buf[41], 4);
+				b64_from_24bit(0, 0, sha_buf[63], 2);
+
+				break;
+			}
+
+		case PGCRYPTO_SHA_UNKOWN:
+			/* we shouldn't land here ... */
+			elog(ERROR, "unsupported digest length");
+
+	}
+
+	*cp = '\0';
+
+	/*
+	 * Copy over result to specified buffer.
+	 *
+	 * The passwd character buffer should have at least PX_SHACRYPT_BUF_LEN
+	 * allocated, since we checked above if dstlen is smaller than
+	 * PX_SHACRYPT_BUF_LEN (which also includes the NULL byte).
+	 *
+	 * In that case we would have failed above already.
+	 */
+	memcpy(passwd, out_buf->data, out_buf->len);
+
+	/* make sure nothing important is left behind */
+	px_memset(&sha_buf, 0, sizeof sha_buf);
+	destroyStringInfo(out_buf);
+
+	/* ...and we're done */
+	return passwd;
+
+error:
+	if (digestA != NULL)
+		px_md_free(digestA);
+
+	if (digestB != NULL)
+		px_md_free(digestB);
+
+	if (out_buf != NULL)
+		destroyStringInfo(out_buf);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_INTERNAL_ERROR),
+			 errmsg("cannot create encrypted password")));
+	return NULL;				/* keep compiler quiet */
+}
diff --git a/contrib/pgcrypto/expected/crypt-shacrypt.out b/contrib/pgcrypto/expected/crypt-shacrypt.out
new file mode 100644
index 00000000000..f3b76d5e913
--- /dev/null
+++ b/contrib/pgcrypto/expected/crypt-shacrypt.out
@@ -0,0 +1,196 @@
+--
+-- crypt() and gensalt: sha256crypt, sha512crypt
+--
+-- $5$ is sha256crypt
+SELECT crypt('', '$5$Szzz0yzz');
+                          crypt                          
+---------------------------------------------------------
+ $5$Szzz0yzz$cA.ZFZKqblRYjdsbrWtVTYa/qSwPQnt2uh0LBtyYAAD
+(1 row)
+
+SELECT crypt('foox', '$5$Szzz0yzz');
+                          crypt                          
+---------------------------------------------------------
+ $5$Szzz0yzz$7hI0rUWkO2QdBkzamh.vP.MIPlbZiwSvu2smhSi6064
+(1 row)
+
+CREATE TABLE ctest (data text, res text, salt text);
+INSERT INTO ctest VALUES ('password', '', '');
+-- generate a salt for sha256crypt, default rounds
+UPDATE ctest SET salt = gen_salt('sha256crypt');
+UPDATE ctest SET res = crypt(data, salt);
+SELECT res = crypt(data, res) AS "worked"
+FROM ctest;
+ worked 
+--------
+ t
+(1 row)
+
+-- generate a salt for sha256crypt, rounds 9999
+UPDATE ctest SET salt = gen_salt('sha256crypt', 9999);
+UPDATE ctest SET res = crypt(data, salt);
+SELECT res = crypt(data, res) AS "worked"
+FROM ctest;
+ worked 
+--------
+ t
+(1 row)
+
+-- should fail, below supported minimum rounds value
+UPDATE ctest SET salt = gen_salt('sha256crypt', 10);
+ERROR:  gen_salt: Incorrect number of rounds
+-- should fail, exceeds supported maximum rounds value
+UPDATE ctest SET salt = gen_salt('sha256crypt', 1000000000);
+ERROR:  gen_salt: Incorrect number of rounds
+TRUNCATE ctest;
+-- $6$ is sha512crypt
+SELECT crypt('', '$6$Szzz0yzz');
+                                               crypt                                                
+----------------------------------------------------------------------------------------------------
+ $6$Szzz0yzz$EGj.JLAovFyAtCJx3YD1DXD1yTXoO9gv4qgLyHBsJJ1lkpnLB8ZPHekm1qXjJCOBc/8thCuHpxNN8Y5xzRYU5.
+(1 row)
+
+SELECT crypt('foox', '$6$Szzz0yzz');
+                                               crypt                                                
+----------------------------------------------------------------------------------------------------
+ $6$Szzz0yzz$KqDw1Y8kze.VFapkvTc9Y5fbqzltjeRz1aPGC/pkHRhFQZ2aM6PmZpXQjcD7AOH88Bq0CSD.VlmymQzcBMEUl0
+(1 row)
+
+INSERT INTO ctest VALUES ('password', '', '');
+-- generate a salt for sha512crypt, default rounds
+UPDATE ctest SET salt = gen_salt('sha512crypt');
+UPDATE ctest SET res = crypt(data, salt);
+SELECT res = crypt(data, res) AS "worked"
+FROM ctest;
+ worked 
+--------
+ t
+(1 row)
+
+-- generate a salt for sha512crypt, rounds 9999
+UPDATE ctest SET salt = gen_salt('sha512crypt', 9999);
+UPDATE ctest SET res = crypt(data, salt);
+SELECT res = crypt(data, res) AS "worked"
+FROM ctest;
+ worked 
+--------
+ t
+(1 row)
+
+-- should fail, below supported minimum rounds value
+UPDATE ctest SET salt = gen_salt('sha512crypt', 10);
+ERROR:  gen_salt: Incorrect number of rounds
+-- should fail, exceeds supported maximum rounds value
+UPDATE ctest SET salt = gen_salt('sha512crypt', 1000000000);
+ERROR:  gen_salt: Incorrect number of rounds
+-- Extended tests taken from public domain code at
+-- https://www.akkadia.org/drepper/SHA-crypt.txt
+--
+-- We adapt the tests defined there to make sure we are compatible with the reference
+-- implementation.
+-- This tests sha256crypt (magic byte $5$ with salt and rounds)
+SELECT crypt('Hello world!', '$5$saltstring')
+           = '$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('Hello world!', '$5$rounds=10000$saltstringsaltstring')
+           = '$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('This is just a test', '$5$rounds=5000$toolongsaltstring')
+           = '$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8mGRcvxa5' AS result;
+ result 
+--------
+ t
+(1 row)
+
+ SELECT crypt('a very much longer text to encrypt.  This one even stretches over more'
+     'than one line.', '$5$rounds=1400$anotherlongsaltstring')
+            = '$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12oP84Bnq1' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('we have a short salt string but not a short password', '$5$rounds=77777$short')
+           = '$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('a short string', '$5$rounds=123456$asaltof16chars..')
+           = '$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/cZKmF/wJvD' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('the minimum number is still observed', '$5$rounds=10$roundstoolow')
+           = '$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL972bIC' AS result;
+NOTICE:  rounds=10 is below supported value (1000), using 1000 instead
+ result 
+--------
+ t
+(1 row)
+
+-- The following tests sha512crypt (magic byte $6$ with salt and rounds)
+SELECT crypt('Hello world!', '$6$saltstring')
+           = '$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('Hello world!', '$6$rounds=10000$saltstringsaltstring')
+           = '$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v.' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('This is just a test', '$6$rounds=5000$toolongsaltstring')
+           = '$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('a very much longer text to encrypt.  This one even stretches over more'
+                 'than one line.', '$6$rounds=1400$anotherlongsaltstring')
+           = '$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('we have a short salt string but not a short password', '$6$rounds=77777$short')
+           = '$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('a short string', '$6$rounds=123456$asaltof16chars..')
+           = '$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1' AS result;
+ result 
+--------
+ t
+(1 row)
+
+SELECT crypt('the minimum number is still observed', '$6$rounds=10$roundstoolow')
+           = '$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX.' AS result;
+NOTICE:  rounds=10 is below supported value (1000), using 1000 instead
+ result 
+--------
+ t
+(1 row)
+
+-- cleanup
+DROP TABLE ctest;
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index 0bcbe4cfe5a..2fdba051290 100644
--- a/contrib/pgcrypto/meson.build
+++ b/contrib/pgcrypto/meson.build
@@ -9,6 +9,7 @@ pgcrypto_sources = files(
   'crypt-des.c',
   'crypt-gensalt.c',
   'crypt-md5.c',
+  'crypt-sha.c',
   'mbuf.c',
   'pgcrypto.c',
   'pgp-armor.c',
@@ -52,6 +53,7 @@ pgcrypto_regress = [
   'pgp-pubkey-decrypt',
   'pgp-pubkey-encrypt',
   'pgp-info',
+  'crypt-shacrypt'
 ]
 
 pgcrypto_openssl_sources = files(
diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c
index 0913ff2c1bc..fe982f23805 100644
--- a/contrib/pgcrypto/px-crypt.c
+++ b/contrib/pgcrypto/px-crypt.c
@@ -67,6 +67,16 @@ run_crypt_bf(const char *psw, const char *salt,
 	return res;
 }
 
+static char *
+run_crypt_sha(const char *psw, const char *salt,
+			  char *buf, unsigned len)
+{
+	char *res;
+
+	res = px_crypt_shacrypt(psw, salt, buf, len);
+	return res;
+}
+
 struct px_crypt_algo
 {
 	char	   *id;
@@ -81,6 +91,8 @@ static const struct px_crypt_algo
 	{"$2x$", 4, run_crypt_bf},
 	{"$2$", 3, NULL},			/* N/A */
 	{"$1$", 3, run_crypt_md5},
+	{"$5$", 3, run_crypt_sha},
+	{"$6$", 3, run_crypt_sha},
 	{"_", 1, run_crypt_des},
 	{"", 0, run_crypt_des},
 	{NULL, 0, NULL}
@@ -125,6 +137,10 @@ static struct generator gen_list[] = {
 	{"md5", _crypt_gensalt_md5_rn, 6, 0, 0, 0},
 	{"xdes", _crypt_gensalt_extended_rn, 3, PX_XDES_ROUNDS, 1, 0xFFFFFF},
 	{"bf", _crypt_gensalt_blowfish_rn, 16, PX_BF_ROUNDS, 4, 31},
+	{"sha256crypt", _crypt_gensalt_sha256_rn, PX_SHACRYPT_SALT_LEN_MAX,
+	 PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
+	{"sha512crypt", _crypt_gensalt_sha512_rn, PX_SHACRYPT_SALT_LEN_MAX,
+	 PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
 	{NULL, NULL, 0, 0, 0, 0}
 };
 
diff --git a/contrib/pgcrypto/px-crypt.h b/contrib/pgcrypto/px-crypt.h
index 54de8069655..97241194fb3 100644
--- a/contrib/pgcrypto/px-crypt.h
+++ b/contrib/pgcrypto/px-crypt.h
@@ -45,6 +45,30 @@
 /* default for blowfish salt */
 #define PX_BF_ROUNDS		6
 
+/* Maximum salt string length of shacrypt.  */
+#define PX_SHACRYPT_SALT_LEN_MAX 16
+
+/* SHA buffer length */
+#define PX_SHACRYPT_DIGEST_MAX_LENGTH 64
+
+/* calculated buffer size of a buffer to store a shacrypt salt string */
+#define PX_SHACRYPT_SALT_BUF_LEN (3 + 7 + 10 + PX_SHACRYPT_SALT_LEN_MAX + 1)
+
+/*
+ * calculated buffer size of a buffer to store complete result of a shacrypt
+ * digest including salt
+ */
+#define PX_SHACRYPT_BUF_LEN PX_SHACRYPT_SALT_BUF_LEN + 86 + 1
+
+/* Default number of rounds of shacrypt if not explicitly specified.  */
+#define PX_SHACRYPT_ROUNDS_DEFAULT 5000
+
+/* Minimum number of rounds of shacrypt.  */
+#define PX_SHACRYPT_ROUNDS_MIN 1000l
+
+/* Maximum number of rounds of shacrypt.  */
+#define PX_SHACRYPT_ROUNDS_MAX 999999999l
+
 /*
  * main interface
  */
@@ -64,6 +88,10 @@ char	   *_crypt_gensalt_md5_rn(unsigned long count,
 								  const char *input, int size, char *output, int output_size);
 char	   *_crypt_gensalt_blowfish_rn(unsigned long count,
 									   const char *input, int size, char *output, int output_size);
+char	   *_crypt_gensalt_sha256_rn(unsigned long count,
+									 const char *input, int size, char *output, int output_size);
+char	   *_crypt_gensalt_sha512_rn(unsigned long count,
+									 const char *input, int size, char *output, int output_size);
 
 /* disable 'extended DES crypt' */
 /* #define DISABLE_XDES */
@@ -79,4 +107,7 @@ char	   *px_crypt_des(const char *key, const char *setting);
 char	   *px_crypt_md5(const char *pw, const char *salt,
 						 char *passwd, unsigned dstlen);
 
+/* crypt-sha.c */
+char       *px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstlen);
+
 #endif							/* _PX_CRYPT_H */
diff --git a/contrib/pgcrypto/sql/crypt-shacrypt.sql b/contrib/pgcrypto/sql/crypt-shacrypt.sql
new file mode 100644
index 00000000000..3ee826f61aa
--- /dev/null
+++ b/contrib/pgcrypto/sql/crypt-shacrypt.sql
@@ -0,0 +1,99 @@
+--
+-- crypt() and gensalt: sha256crypt, sha512crypt
+--
+
+-- $5$ is sha256crypt
+SELECT crypt('', '$5$Szzz0yzz');
+
+SELECT crypt('foox', '$5$Szzz0yzz');
+
+CREATE TABLE ctest (data text, res text, salt text);
+INSERT INTO ctest VALUES ('password', '', '');
+
+-- generate a salt for sha256crypt, default rounds
+UPDATE ctest SET salt = gen_salt('sha256crypt');
+UPDATE ctest SET res = crypt(data, salt);
+SELECT res = crypt(data, res) AS "worked"
+FROM ctest;
+
+-- generate a salt for sha256crypt, rounds 9999
+UPDATE ctest SET salt = gen_salt('sha256crypt', 9999);
+UPDATE ctest SET res = crypt(data, salt);
+SELECT res = crypt(data, res) AS "worked"
+FROM ctest;
+
+-- should fail, below supported minimum rounds value
+UPDATE ctest SET salt = gen_salt('sha256crypt', 10);
+
+-- should fail, exceeds supported maximum rounds value
+UPDATE ctest SET salt = gen_salt('sha256crypt', 1000000000);
+
+TRUNCATE ctest;
+
+-- $6$ is sha512crypt
+SELECT crypt('', '$6$Szzz0yzz');
+
+SELECT crypt('foox', '$6$Szzz0yzz');
+
+INSERT INTO ctest VALUES ('password', '', '');
+
+-- generate a salt for sha512crypt, default rounds
+UPDATE ctest SET salt = gen_salt('sha512crypt');
+UPDATE ctest SET res = crypt(data, salt);
+SELECT res = crypt(data, res) AS "worked"
+FROM ctest;
+
+-- generate a salt for sha512crypt, rounds 9999
+UPDATE ctest SET salt = gen_salt('sha512crypt', 9999);
+UPDATE ctest SET res = crypt(data, salt);
+SELECT res = crypt(data, res) AS "worked"
+FROM ctest;
+
+-- should fail, below supported minimum rounds value
+UPDATE ctest SET salt = gen_salt('sha512crypt', 10);
+
+-- should fail, exceeds supported maximum rounds value
+UPDATE ctest SET salt = gen_salt('sha512crypt', 1000000000);
+
+-- Extended tests taken from public domain code at
+-- https://www.akkadia.org/drepper/SHA-crypt.txt
+--
+-- We adapt the tests defined there to make sure we are compatible with the reference
+-- implementation.
+
+-- This tests sha256crypt (magic byte $5$ with salt and rounds)
+SELECT crypt('Hello world!', '$5$saltstring')
+           = '$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5' AS result;
+SELECT crypt('Hello world!', '$5$rounds=10000$saltstringsaltstring')
+           = '$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA' AS result;
+SELECT crypt('This is just a test', '$5$rounds=5000$toolongsaltstring')
+           = '$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8mGRcvxa5' AS result;
+ SELECT crypt('a very much longer text to encrypt.  This one even stretches over more'
+     'than one line.', '$5$rounds=1400$anotherlongsaltstring')
+            = '$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12oP84Bnq1' AS result;
+SELECT crypt('we have a short salt string but not a short password', '$5$rounds=77777$short')
+           = '$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/' AS result;
+SELECT crypt('a short string', '$5$rounds=123456$asaltof16chars..')
+           = '$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/cZKmF/wJvD' AS result;
+SELECT crypt('the minimum number is still observed', '$5$rounds=10$roundstoolow')
+           = '$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL972bIC' AS result;
+
+-- The following tests sha512crypt (magic byte $6$ with salt and rounds)
+SELECT crypt('Hello world!', '$6$saltstring')
+           = '$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1' AS result;
+SELECT crypt('Hello world!', '$6$rounds=10000$saltstringsaltstring')
+           = '$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v.' AS result;
+SELECT crypt('This is just a test', '$6$rounds=5000$toolongsaltstring')
+           = '$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0' AS result;
+SELECT crypt('a very much longer text to encrypt.  This one even stretches over more'
+                 'than one line.', '$6$rounds=1400$anotherlongsaltstring')
+           = '$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1' AS result;
+SELECT crypt('we have a short salt string but not a short password', '$6$rounds=77777$short')
+           = '$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0' AS result;
+SELECT crypt('a short string', '$6$rounds=123456$asaltof16chars..')
+           = '$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1' AS result;
+SELECT crypt('the minimum number is still observed', '$6$rounds=10$roundstoolow')
+           = '$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX.' AS result;
+
+-- cleanup
+DROP TABLE ctest;
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 396c67f0cde..281d9859b54 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -189,6 +189,29 @@ hmac(data bytea, key bytea, type text) returns bytea
       <entry>13</entry>
       <entry>Original UNIX crypt</entry>
      </row>
+     <row>
+      <entry><literal>sha256crypt</literal></entry>
+      <entry>unlimited</entry>
+      <entry>yes</entry>
+      <entry>up to 32</entry>
+      <entry>80</entry>
+      <entry>Adapted from publicly available reference implementation
+       <ulink url="https://www.akkadia.org/drepper/SHA-crypt.txt";>Unix crypt using SHA-256 and SHA-512
+       </ulink>
+      </entry>
+     </row>
+     <row>
+      <entry><literal>sha512crypt</literal></entry>
+      <entry>unlimited</entry>
+      <entry>yes</entry>
+      <entry>up to 32</entry>
+      <entry>123</entry>
+      <entry>Adapted from publicly available reference implementation
+       <ulink url="https://www.akkadia.org/drepper/SHA-crypt.txt";>Unix crypt using SHA-256 and SHA-512
+       </ulink>
+      </entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
@@ -245,7 +268,9 @@ gen_salt(type text [, iter_count integer ]) returns text
    <para>
     The <parameter>type</parameter> parameter specifies the hashing algorithm.
     The accepted types are: <literal>des</literal>, <literal>xdes</literal>,
-    <literal>md5</literal> and <literal>bf</literal>.
+    <literal>md5</literal>, <literal>bf</literal>, <literal>sha256crypt</literal> and
+    <literal>sha512crypt</literal>. The last two, <literal>sha256crypt</literal> and
+    <literal>sha512crypt</literal> are modern <literal>SHA-2</literal> based password hashes.
    </para>
 
    <para>
@@ -284,6 +309,12 @@ gen_salt(type text [, iter_count integer ]) returns text
        <entry>4</entry>
        <entry>31</entry>
       </row>
+      <row>
+       <entry><literal>sha256crypt, sha512crypt</literal></entry>
+       <entry>5000</entry>
+       <entry>1000</entry>
+       <entry>999999999</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -313,6 +344,15 @@ gen_salt(type text [, iter_count integer ]) returns text
     <function>gen_salt</function>.
    </para>
 
+   <para>
+   The default <parameter>iter_count</parameter> for <literal>sha256crypt</literal> and
+   <literal>sha512crypt</literal> of <literal>5000</literal> is considered too low for modern
+   hardware, but can be adjusted to generate stronger password hashes. Currently the generated hash
+   string always includes the <parameter>rounds</parameter> even when just the default is used.
+   Otherwise both hashes, <literal>sha256crypt</literal> and <literal>sha512crypt</literal> are
+   considered safe.
+   </para>
+
    <table id="pgcrypto-hash-speed-table">
     <title>Hash Algorithm Speeds</title>
     <tgroup cols="5">
-- 
2.48.1

Reply via email to