pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
Modern SHA2- based password hashes for pgcrypto
38+ messages / 7 participants
[nested] [flat]

* Modern SHA2- based password hashes for pgcrypto
@ 2024-12-31 16:06  Bernd Helmle <mailings@oopsware.de>
  0 siblings, 4 replies; 38+ messages in thread

From: Bernd Helmle @ 2024-12-31 16:06 UTC (permalink / raw)
  To: pgsql-hackers

Hi Hackers,

Some of you might already arrived 2025, so first a Happy New Year to
everyone already there ;)

Please find attached a patch to pgcrypto to add modern SHA-2 based
password hashes sha256crypt (256 bit) and sha512crypt (512 bit) for
crypt() and gen_salt() respectively. This is compatible on what crypt()
currently does on FreeBSD and Linux and both algorithms are considered
more secure than the currently implemented hashes.

I adapted the code from the publicly available reference implementation
at [1]. It's based on our existing OpenSSL infrastructure in pgcrypto
and produces compatible password hashes with crypt() and "openssl
passwd" with "-5" and "-6" switches.

I documented the new supported hashes for pgcrypto, but didn't do
anything to update the benchmark table for the supported password
hashes.

Modern OS (at least Linux, BSDs) implementations for crypt() also
support yescrypt, which is the recommended (and default) password hash
algorithm there. I am also looking to implement that, but thought it
would be useful to have the SHA-2 based hashes first in the review.

I am going to add this patch to the upcoming january commitfest for
initial review.

[1] https://www.akkadia.org/drepper/SHA-crypt.txt


-- 
Thanks,
	Bernd

Attachments:

  [text/x-patch] 0001-Add-modern-SHA-2-based-password-hashes-to-pgcrypto.patch (31.2K, ../../c763235a2757e2f5f9e3e27268b9028349cef659.camel@oopsware.de/2-0001-Add-modern-SHA-2-based-password-hashes-to-pgcrypto.patch)
  download | inline diff:
From 131b03729123967b93925d7f94bc04d5b5ab347c Mon Sep 17 00:00:00 2001
From: Bernd Helmle <Bernd Helmle mailings@oopsware.de>
Date: Tue, 31 Dec 2024 16:35:35 +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             |  89 +++
 contrib/pgcrypto/crypt-sha.c                 | 546 +++++++++++++++++++
 contrib/pgcrypto/expected/crypt-shacrypt.out |  75 +++
 contrib/pgcrypto/meson.build                 |   2 +
 contrib/pgcrypto/px-crypt.c                  |  16 +
 contrib/pgcrypto/px-crypt.h                  |  31 ++
 contrib/pgcrypto/sql/crypt-shacrypt.sql      |  47 ++
 doc/src/sgml/pgcrypto.sgml                   |  42 +-
 9 files changed, 849 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..015511d44af 100644
--- a/contrib/pgcrypto/crypt-gensalt.c
+++ b/contrib/pgcrypto/crypt-gensalt.c
@@ -185,3 +185,92 @@ _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;                       /* null byte */
+	int rc;
+
+	/* output buffer must be allocated with PX_MAX_SALT_LEN bytes */
+	if (PX_MAX_SALT_LEN < result_bufsize)
+	{
+		elog(ERROR, "invalid size of salt");
+	}
+
+	/* shacrypt salt len must not exceed PX_MAX_SALT_LEN */
+	Assert(PX_SHACRYPT_SALT_LEN_MAX <= PX_MAX_SALT_LEN);
+	if (PX_SHACRYPT_SALT_LEN_MAX > PX_MAX_SALT_LEN)
+	{
+		elog(ERROR, "result buffer too small for salt");
+	}
+
+	/*
+	 * Care must be taken to not exceed the buffer size allocated for
+	 * the input character buffer.
+	 */
+
+	if (PX_SHACRYPT_SALT_LEN_MAX != size)
+	{
+		elog(ERROR, "invalid length of salt string");
+	}
+
+	if (output_size < size) {
+		elog(ERROR, "invalid size of result salt buffer");
+	}
+
+	/* Skip magic bytes, set by callers */
+	s_ptr += 3;
+	if ((rc = pg_snprintf(s_ptr, 18, "rounds=%ld$", count)) <= 0)
+	{
+		elog(ERROR, "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..6117321dbcc
--- /dev/null
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -0,0 +1,546 @@
+/*
+ * 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
+ *
+ * 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 identifers for suitables 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 explicitely
+ * 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 "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$" };
+	static const char ascii_dollar[] = { 0x24, 0x00 };
+
+	/* "$n$rounds=<N>$......salt......$...shahash(up to 86 chars)...\0" */
+	char out_buf[PX_SHACRYPT_BUF_LEN]; /* resulting encrypted password buffer */
+
+	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 int rounds = PX_SHACRYPT_ROUNDS_DEFAULT;
+
+	unsigned len, salt_len;
+
+	/* Sanity checks */
+	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(&out_buf, '\0', sizeof(out_buf));
+	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)
+	{
+		elog(ERROR, "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] != ascii_dollar[0])
+		&& (dec_salt_binary[2] != ascii_dollar[0]))
+	{
+		elog(ERROR, "invalid format of salt");
+	}
+
+	/*
+	 * Check magic byte for supported shacrypt digest.
+	 */
+	if (strncmp(dec_salt_binary, magic_bytes[0], strlen(magic_bytes[0])) == 0)
+	{
+		type = PGCRYPTO_SHA256CRYPT;
+		dec_salt_binary += strlen(magic_bytes[0]);
+	}
+
+	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.
+	 *
+	 * Unknown magic byte is handled 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;
+		unsigned long int srounds = strtoul (num, &endp, 10);
+		if (*endp == '$') {
+			dec_salt_binary = endp + 1;
+			if (srounds > PX_SHACRYPT_ROUNDS_MAX)
+				rounds = PX_SHACRYPT_ROUNDS_MAX;
+			else if (srounds < PX_SHACRYPT_ROUNDS_MIN)
+				rounds = PX_SHACRYPT_ROUNDS_MIN;
+			else
+				rounds = (unsigned int)srounds;
+			rounds_custom = 1;
+		} else {
+			elog(ERROR, "could not parse salt options");
+		}
+
+	}
+
+	elog(DEBUG1, "using rounds = %d", rounds);
+
+	/*
+	 * We need the real length of the decoded salt string, this is every
+	 * character before the last '$' in the preamble. After the options,
+	 * dec_salt_binary is now positioned at the beginning of the salt string.
+	 */
+	for (ep = dec_salt_binary;
+		*ep && *ep != '$' && ep < (dec_salt_binary + PX_SHACRYPT_SALT_LEN_MAX);
+		ep++) continue;
+	salt_len = ep - dec_salt_binary;
+
+	/*
+	 * 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;
+
+			elog(DEBUG1,
+				 "using sha256crypt as requested by magic byte in salt");
+			strlcat(out_buf, magic_bytes[0], sizeof(out_buf));
+			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;
+
+			elog(DEBUG1,
+				 "using sha512crypt as requested by magic byte in salt");
+			strlcat(out_buf, magic_bytes[1], sizeof(out_buf));
+			break;
+		}
+
+		case PGCRYPTO_SHA_UNKOWN:
+			elog(ERROR, "unknown crypt identifier \"%c\"", salt[1]);
+	}
+
+	if (rounds_custom > 0)
+	{
+
+		char tmp_buf[80]; /* "rounds=999999999" */
+
+		memset(&tmp_buf, '\0', sizeof(tmp_buf));
+		snprintf(tmp_buf, sizeof(tmp_buf), "rounds=%u", rounds);
+		strlcat(out_buf, tmp_buf, sizeof(out_buf));
+		strlcat(out_buf, ascii_dollar, sizeof(out_buf));
+
+	}
+
+	elog(DEBUG1, "using salt len = %d", salt_len);
+	strncat(out_buf, dec_salt_binary, salt_len);
+
+	if (strlen(out_buf) > 3 + 17 * rounds_custom + salt_len)
+	{
+		elog(ERROR, "invalid 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++) {
+
+		/* 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 */
+	cp = out_buf + strlen(out_buf);
+	*cp++ = ascii_dollar[0];
+
+# define b64_from_24bit(B2, B1, B0, N)                      \
+	do {                                                    \
+		unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
+		int i = (N);                                        \
+		while (i-- > 0)                                     \
+		{                                                   \
+			*cp++ = _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;
+		}
+
+		default:
+			goto error;
+	}
+
+	*cp = '\0';
+
+	/* copy over result to specified buffer ... */
+	memcpy(passwd, out_buf, dstlen);
+
+	/* make sure nothing important is left behind */
+	px_memset(&sha_buf, 0, sizeof sha_buf);
+
+	/* ...and we're done */
+	return passwd;
+
+error:
+	if (digestA != NULL)
+		px_md_free(digestA);
+
+	if (digestB != NULL)
+		px_md_free(digestB);
+
+	elog(ERROR, "cannot create encrypted password");
+}
diff --git a/contrib/pgcrypto/expected/crypt-shacrypt.out b/contrib/pgcrypto/expected/crypt-shacrypt.out
new file mode 100644
index 00000000000..89ae9f95cc7
--- /dev/null
+++ b/contrib/pgcrypto/expected/crypt-shacrypt.out
@@ -0,0 +1,75 @@
+--
+-- 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)
+
+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)
+
+-- cleanup
+DROP TABLE ctest;
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index 85948915482..e391a9573db 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..7895ccb1c60 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 + 1 + 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_LEN_MAX + 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 1000
+
+/* Maximum number of rounds of shacrypt.  */
+#define PX_SHACRYPT_ROUNDS_MAX 999999999
+
 /*
  * 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..ca99aa98651
--- /dev/null
+++ b/contrib/pgcrypto/sql/crypt-shacrypt.sql
@@ -0,0 +1,47 @@
+--
+-- 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;
+
+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;
+
+-- cleanup
+DROP TABLE ctest;
\ No newline at end of file
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.47.1



^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2024-12-31 16:36  Bernd Helmle <mailings@oopsware.de>
  parent: Bernd Helmle <mailings@oopsware.de>
  3 siblings, 0 replies; 38+ messages in thread

From: Bernd Helmle @ 2024-12-31 16:36 UTC (permalink / raw)
  To: pgsql-hackers@lists.postgresql.org

Am Dienstag, dem 31.12.2024 um 17:06 +0100 schrieb Bernd Helmle:
> I am going to add this patch to the upcoming january commitfest for
> initial review.

I see cfbot fails Debian Bookworm with autoconf and on macOS with
meson. I will look into it.







^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-02 14:57  Daniel Gustafsson <daniel@yesql.se>
  parent: Bernd Helmle <mailings@oopsware.de>
  3 siblings, 1 reply; 38+ messages in thread

From: Daniel Gustafsson @ 2025-01-02 14:57 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: pgsql-hackers

> On 31 Dec 2024, at 17:06, Bernd Helmle <mailings@oopsware.de> wrote:

> I adapted the code from the publicly available reference implementation
> at [1]. It's based on our existing OpenSSL infrastructure in pgcrypto
> and produces compatible password hashes with crypt() and "openssl
> passwd" with "-5" and "-6" switches.

Potentially daft question, but since we require OpenSSL to build pgcrypto, why
do we need to include sha2 code instead of using the sha2 implementation in
libcrypto? How complicated would it be to use the OpenSSL API instead?

--
Daniel Gustafsson






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-02 15:17  Bernd Helmle <mailings@oopsware.de>
  parent: Daniel Gustafsson <daniel@yesql.se>
  0 siblings, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-01-02 15:17 UTC (permalink / raw)
  To: Daniel Gustafsson <daniel@yesql.se>; +Cc: pgsql-hackers

Am Donnerstag, dem 02.01.2025 um 15:57 +0100 schrieb Daniel Gustafsson:
> > I adapted the code from the publicly available reference
> > implementation
> > at [1]. It's based on our existing OpenSSL infrastructure in
> > pgcrypto
> > and produces compatible password hashes with crypt() and "openssl
> > passwd" with "-5" and "-6" switches.
> 
> Potentially daft question, but since we require OpenSSL to build
> pgcrypto, why
> do we need to include sha2 code instead of using the sha2
> implementation in
> libcrypto? How complicated would it be to use the OpenSSL API
> instead?

Not sure i got you, but i use OpenSSL and the SHA2 implementation
there. See the pgcrypto px_* API (px.h and openssl.c respectively) i am
using to create the digests.

Thanks,
	Bernd






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-02 15:28  Daniel Gustafsson <daniel@yesql.se>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 0 replies; 38+ messages in thread

From: Daniel Gustafsson @ 2025-01-02 15:28 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: pgsql-hackers

> On 2 Jan 2025, at 16:17, Bernd Helmle <mailings@oopsware.de> wrote:
> 
> Am Donnerstag, dem 02.01.2025 um 15:57 +0100 schrieb Daniel Gustafsson:
>>> I adapted the code from the publicly available reference
>>> implementation
>>> at [1]. It's based on our existing OpenSSL infrastructure in
>>> pgcrypto
>>> and produces compatible password hashes with crypt() and "openssl
>>> passwd" with "-5" and "-6" switches.
>> 
>> Potentially daft question, but since we require OpenSSL to build
>> pgcrypto, why
>> do we need to include sha2 code instead of using the sha2
>> implementation in
>> libcrypto? How complicated would it be to use the OpenSSL API
>> instead?
> 
> Not sure i got you, but i use OpenSSL and the SHA2 implementation
> there. See the pgcrypto px_* API (px.h and openssl.c respectively) i am
> using to create the digests.

Sorry, skimming the patch I misread it, nevermind.

--
Daniel Gustafsson






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-03 15:58  Japin Li <japinli@hotmail.com>
  parent: Bernd Helmle <mailings@oopsware.de>
  3 siblings, 0 replies; 38+ messages in thread

From: Japin Li @ 2025-01-03 15:58 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: pgsql-hackers

On Tue, 31 Dec 2024 at 17:06, Bernd Helmle <mailings@oopsware.de> wrote:
> Hi Hackers,
>
> Some of you might already arrived 2025, so first a Happy New Year to
> everyone already there ;)
>
> Please find attached a patch to pgcrypto to add modern SHA-2 based
> password hashes sha256crypt (256 bit) and sha512crypt (512 bit) for
> crypt() and gen_salt() respectively. This is compatible on what crypt()
> currently does on FreeBSD and Linux and both algorithms are considered
> more secure than the currently implemented hashes.
>
> I adapted the code from the publicly available reference implementation
> at [1]. It's based on our existing OpenSSL infrastructure in pgcrypto
> and produces compatible password hashes with crypt() and "openssl
> passwd" with "-5" and "-6" switches.
>
> I documented the new supported hashes for pgcrypto, but didn't do
> anything to update the benchmark table for the supported password
> hashes.
>
> Modern OS (at least Linux, BSDs) implementations for crypt() also
> support yescrypt, which is the recommended (and default) password hash
> algorithm there. I am also looking to implement that, but thought it
> would be useful to have the SHA-2 based hashes first in the review.
>
> I am going to add this patch to the upcoming january commitfest for
> initial review.
>
> [1] https://www.akkadia.org/drepper/SHA-crypt.txt
>

Greate!  I have some questions after using it.

When I use the following query, it crashed!

[local]:4012204 postgres=# select crypt('hello', '$5$$6$rounds=10000$/Zg436s2vmTwsoSz');
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
The connection to the server was lost. Attempting reset: Failed.
: !?>

It is caused by checking the shacrypt digest.  The following can fix this crash,
but I'm unsure if it is correct.

@@ -151,8 +152,7 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
         type = PGCRYPTO_SHA256CRYPT;
         dec_salt_binary += strlen(magic_bytes[0]);
     }
-
-    if (strncmp(dec_salt_binary, magic_bytes[1], strlen(magic_bytes[1])) == 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]);

Another, if I run crypt with big rounds, it cannot be canceled, for example:

select crypt('hello', '$5$rounds=9999999999$/Zg436s2vmTwsoSz');

Maybe we should add CHECK_FOR_INTERRUPTS() in step 21.

@@ -411,6 +411,7 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
      *    uses the notation "digest A/B" to describe this behavior.
      */
     for (block = 0; block < rounds; block++) {
+        CHECK_FOR_INTERRUPTS();

         /* a) start digest B */
         px_md_reset(digestB);

-- 
Regrads,
Japin Li





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-03 16:55  Bernd Helmle <mailings@oopsware.de>
  parent: Bernd Helmle <mailings@oopsware.de>
  3 siblings, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-01-03 16:55 UTC (permalink / raw)
  To: Japin Li <japinli@hotmail.com>; +Cc: pgsql-hackers

Am Freitag, dem 03.01.2025 um 23:57 +0800 schrieb Japin Li:
> 
> Greate!  I have some questions after using it.
> 
> When I use the following query, it crashed!
> 
> [local]:4012204 postgres=# select crypt('hello',
> '$5$$6$rounds=10000$/Zg436s2vmTwsoSz');
> server closed the connection unexpectedly
>         This probably means the server terminated abnormally
>         before or while processing the request.
> The connection to the server was lost. Attempting reset: Failed.
> The connection to the server was lost. Attempting reset: Failed.
> : !?>
> 
> It is caused by checking the shacrypt digest.  The following can fix
> this crash,
> but I'm unsure if it is correct.
> 

Hmm, can you provide a backtrace? I am currently debugging the CI
results and i currently have a thinko in my code at crypt-sha.c:530, i
am using the wrong length to copy the result buffer, see 

https://api.cirrus-ci.com/v1/artifact/task/6395274957946880/log/contrib/pgcrypto/log/postmaster.log

==33750==ERROR: AddressSanitizer: stack-buffer-overflow on address
0x7ffc385204c7 at pc 0x7fd65a24814b bp 0x7ffc38520240 sp 0x7ffc3851f9f0
READ of size 128 at 0x7ffc385204c7 thread T0
    #0 0x7fd65a24814a in __interceptor_memcpy
../../../../src/libsanitizer/sanitizer_common/sanitizer_common_intercep
tors.inc:827
    #1 0x7fd64c37c25f in px_crypt_shacrypt /tmp/cirrus-ci-
build/contrib/pgcrypto/crypt-sha.c:530
    #2 0x7fd64c3993f0 in run_crypt_sha /tmp/cirrus-ci-
build/contrib/pgcrypto/px-crypt.c:76

But i cannot reproduce your crash in this case, maybe this is related
to the above issue...

> @@ -151,8 +152,7 @@ px_crypt_shacrypt(const char *pw, const char
> *salt, char *passwd, unsigned dstle
>          type = PGCRYPTO_SHA256CRYPT;
>          dec_salt_binary += strlen(magic_bytes[0]);
>      }
> -
> -    if (strncmp(dec_salt_binary, magic_bytes[1],
> strlen(magic_bytes[1])) == 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]);
> 

Yeah, seems the check is not strict enough for the magic byte and i
need to be more strict here. I am going to look into this. "$5$$6$"
certainly is bogus enough that we should error out instead.

> Another, if I run crypt with big rounds, it cannot be canceled, for
> example:
> 
> select crypt('hello', '$5$rounds=9999999999$/Zg436s2vmTwsoSz');
> 
> Maybe we should add CHECK_FOR_INTERRUPTS() in step 21.
> 
> @@ -411,6 +411,7 @@ px_crypt_shacrypt(const char *pw, const char
> *salt, char *passwd, unsigned dstle
>       *    uses the notation "digest A/B" to describe this behavior.
>       */
>      for (block = 0; block < rounds; block++) {
> +        CHECK_FOR_INTERRUPTS();
> 
>          /* a) start digest B */
>          px_md_reset(digestB);
> 

Hmm not sure how expensive it is to check for interrupts for each
block. Maybe its better to check for each 10.000 blocks or so. But i
like the idea. Will investigate.


Thanks for your review,

	Bernd






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-04 00:19  Japin Li <japinli@hotmail.com>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 1 reply; 38+ messages in thread

From: Japin Li @ 2025-01-04 00:19 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: pgsql-hackers

On Fri, 03 Jan 2025 at 17:55, Bernd Helmle <mailings@oopsware.de> wrote:
> Am Freitag, dem 03.01.2025 um 23:57 +0800 schrieb Japin Li:
>> 
>> Greate!  I have some questions after using it.
>> 
>> When I use the following query, it crashed!
>> 
>> [local]:4012204 postgres=# select crypt('hello',
>> '$5$$6$rounds=10000$/Zg436s2vmTwsoSz');
>> server closed the connection unexpectedly
>>         This probably means the server terminated abnormally
>>         before or while processing the request.
>> The connection to the server was lost. Attempting reset: Failed.
>> The connection to the server was lost. Attempting reset: Failed.
>> : !?>
>> 
>> It is caused by checking the shacrypt digest.  The following can fix
>> this crash,
>> but I'm unsure if it is correct.
>> 
>
> Hmm, can you provide a backtrace? I am currently debugging the CI
> results and i currently have a thinko in my code at crypt-sha.c:530, i
> am using the wrong length to copy the result buffer, see 
>
Here is the backtrace.

(gdb) bt
#0  __pthread_kill_implementation (no_tid=0, signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3  0x00007ba852a4526e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ba852a288ff in __GI_abort () at ./stdlib/abort.c:79
#5  0x00007ba852a297b6 in __libc_message_impl (fmt=fmt@entry=0x7ba852bce765 "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:132
#6  0x00007ba852b36c19 in __GI___fortify_fail (msg=msg@entry=0x7ba852bce77d "stack smashing detected") at ./debug/fortify_fail.c:24
#7  0x00007ba852b37ea4 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
#8  0x00007ba853d8cb2e in px_crypt_shacrypt (pw=0x64e191229370 "hello", salt=0x64e191229388 "$5$$6$rounds=10000$/Zg436s2vmTwsoSz", passwd=0x64e1912293d8 "$6$rounds=10000$/Zg436s2vmTwsoSz$TTCnOO7S5pkJHBVJ.oL74WN1Yt0n1RfQOWd60CRb4xtd9q7ChipyZ00jwYZfhDGRRJOoJNOgYKAVGpdmA8qhT1", dstlen=128) at /data/Codes/pg/master/build/../contrib/pgcrypto/crypt-sha.c:546
#9  0x00007ba853d9d9f8 in run_crypt_sha (psw=<error reading variable: Cannot access memory at address 0x31546871384155>, salt=<error reading variable: Cannot access memory at address 0x3154687138414d>, buf=<error reading variable: Cannot access memory at address 0x31546871384145>, len=<error reading variable: Cannot access memory at address 0x31546871384141>) at /data/Codes/pg/master/build/../contrib/pgcrypto/px-crypt.c:76
Backtrace stopped: Cannot access memory at address 0x31546871384175

-- 
Regrads,
Japin Li





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-07 17:48  Bernd Helmle <mailings@oopsware.de>
  parent: Japin Li <japinli@hotmail.com>
  0 siblings, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-01-07 17:48 UTC (permalink / raw)
  To: Japin Li <japinli@hotmail.com>; +Cc: pgsql-hackers

Am Samstag, dem 04.01.2025 um 08:19 +0800 schrieb Japin Li:
> (gdb) bt
> #0  __pthread_kill_implementation (no_tid=0, signo=6,
> threadid=<optimized out>) at ./nptl/pthread_kill.c:44
> #1  __pthread_kill_internal (signo=6, threadid=<optimized out>) at
> ./nptl/pthread_kill.c:78
> #2  __GI___pthread_kill (threadid=<optimized out>,
> signo=signo@entry=6) at ./nptl/pthread_kill.c:89
> #3  0x00007ba852a4526e in __GI_raise (sig=sig@entry=6) at
> ../sysdeps/posix/raise.c:26
> #4  0x00007ba852a288ff in __GI_abort () at ./stdlib/abort.c:79
> #5  0x00007ba852a297b6 in __libc_message_impl
> (fmt=fmt@entry=0x7ba852bce765 "*** %s ***: terminated\n") at
> ../sysdeps/posix/libc_fatal.c:132
> #6  0x00007ba852b36c19 in __GI___fortify_fail
> (msg=msg@entry=0x7ba852bce77d "stack smashing detected") at
> ./debug/fortify_fail.c:24
> #7  0x00007ba852b37ea4 in __stack_chk_fail () at
> ./debug/stack_chk_fail.c:24
> #8  0x00007ba853d8cb2e in px_crypt_shacrypt (pw=0x64e191229370
> "hello", salt=0x64e191229388 "$5$$6$rounds=10000$/Zg436s2vmTwsoSz",
> passwd=0x64e1912293d8
> "$6$rounds=10000$/Zg436s2vmTwsoSz$TTCnOO7S5pkJHBVJ.oL74WN1Yt0n1RfQOWd
> 60CRb4xtd9q7ChipyZ00jwYZfhDGRRJOoJNOgYKAVGpdmA8qhT1", dstlen=128) at
> /data/Codes/pg/master/build/../contrib/pgcrypto/crypt-sha.c:546

Thank you very much.

This points to the same area i've investigated and it turned out i
confused length macros for the password salt, leading to the out_buf
buffer in px_crypt_shacrypt() being too small, stupid me.

Attached is a new version of the patch:

- Use correct length for the out_buf result buffer in
px_crypt_shacrypt()

- Silent a compiler warning in the error goto branch in
px_crypt_shacrypt()

- Don't accept rounds values lower or larger than supported (previously
they were silently changed to minimum/maximum).

- Per your suggestions, use CHECK_FOR_INTERRUPTS() during block
calculation to make it possible to interrupt the code when using large
values for the rounds option and just rely on the very first three
bytes when parsing the magic bytes of the salt string.

Please test again.
	
	Bernd

Attachments:

  [text/x-patch] 0001-Add-modern-SHA-2-based-password-hashes-to-pgcrypto-v2.patch (32.2K, ../../7bde94157f8357a59a9d6de1f74a1b6d7705c5b0.camel@oopsware.de/2-0001-Add-modern-SHA-2-based-password-hashes-to-pgcrypto-v2.patch)
  download | inline diff:
From beb40e992b9bb78ce245cb00bff39d49ded40eab Mon Sep 17 00:00:00 2001
From: Bernd Helmle <Bernd Helmle mailings@oopsware.de>
Date: Tue, 7 Jan 2025 18:30:38 +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             |  89 +++
 contrib/pgcrypto/crypt-sha.c                 | 579 +++++++++++++++++++
 contrib/pgcrypto/expected/crypt-shacrypt.out |  75 +++
 contrib/pgcrypto/meson.build                 |   2 +
 contrib/pgcrypto/px-crypt.c                  |  16 +
 contrib/pgcrypto/px-crypt.h                  |  31 +
 contrib/pgcrypto/sql/crypt-shacrypt.sql      |  47 ++
 doc/src/sgml/pgcrypto.sgml                   |  42 +-
 9 files changed, 882 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..0c0f82a888b 100644
--- a/contrib/pgcrypto/crypt-gensalt.c
+++ b/contrib/pgcrypto/crypt-gensalt.c
@@ -185,3 +185,92 @@ _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)
+	{
+		elog(ERROR, "invalid size of salt");
+	}
+
+	/* shacrypt salt len must not exceed PX_MAX_SALT_LEN */
+	Assert(PX_SHACRYPT_SALT_LEN_MAX <= PX_MAX_SALT_LEN);
+	if (PX_SHACRYPT_SALT_LEN_MAX > PX_MAX_SALT_LEN)
+	{
+		elog(ERROR, "result buffer too small for salt");
+	}
+
+	/*
+	 * Care must be taken to not exceed the buffer size allocated for
+	 * the input character buffer.
+	 */
+
+	if (PX_SHACRYPT_SALT_LEN_MAX != size)
+	{
+		elog(ERROR, "invalid length of salt string");
+	}
+
+	if (output_size < size) {
+		elog(ERROR, "invalid size of result salt buffer");
+	}
+
+	/* Skip magic bytes, set by callers */
+	s_ptr += 3;
+	if ((rc = pg_snprintf(s_ptr, 18, "rounds=%ld$", count)) <= 0)
+	{
+		elog(ERROR, "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..386f0dfea77
--- /dev/null
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -0,0 +1,579 @@
+/*
+ * 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
+ *
+ * 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 identifers for suitables 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 explicitely
+ * 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$" };
+	static const char ascii_dollar[] = { 0x24, 0x00 };
+
+	/* "$n$rounds=<N>$......salt......$...shahash(up to 86 chars)...\0" */
+	char out_buf[PX_SHACRYPT_BUF_LEN]; /* resulting encrypted password buffer */
+
+	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;
+
+	/* 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(&out_buf, '\0', sizeof(out_buf));
+	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)
+	{
+		elog(ERROR, "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] != ascii_dollar[0])
+		&& (dec_salt_binary[2] != ascii_dollar[0]))
+	{
+		elog(ERROR, "invalid format of salt");
+	}
+
+	/*
+	 * 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 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;
+			if (srounds > PX_SHACRYPT_ROUNDS_MAX)
+				elog(ERROR, "maximum rounds supported: \"%d\"",
+					 PX_SHACRYPT_ROUNDS_MAX);
+			else if (srounds < PX_SHACRYPT_ROUNDS_MIN)
+				elog(ERROR, "minimum rounds supported: \"%d\"",
+					 PX_SHACRYPT_ROUNDS_MIN);
+			else
+				rounds = (unsigned long)srounds;
+			rounds_custom = 1;
+		} else {
+			elog(ERROR, "could not parse salt options");
+		}
+
+	}
+
+	elog(DEBUG1, "using rounds = %lu", rounds);
+
+	/*
+	 * We need the real length of the decoded salt string, this is every
+	 * character before the last '$' in the preamble. After the options,
+	 * dec_salt_binary is now positioned at the beginning of the salt string.
+	 */
+	for (ep = dec_salt_binary;
+		*ep && *ep != '$' && ep < (dec_salt_binary + PX_SHACRYPT_SALT_LEN_MAX);
+		ep++) continue;
+	salt_len = ep - dec_salt_binary;
+
+	/*
+	 * 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;
+
+			elog(DEBUG1,
+				 "using sha256crypt as requested by magic byte in salt");
+			strlcat(out_buf, magic_bytes[0], sizeof(out_buf));
+			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;
+
+			elog(DEBUG1,
+				 "using sha512crypt as requested by magic byte in salt");
+			strlcat(out_buf, magic_bytes[1], sizeof(out_buf));
+			break;
+		}
+
+		case PGCRYPTO_SHA_UNKOWN:
+			elog(ERROR, "unknown crypt identifier \"%c\"", salt[1]);
+	}
+
+	if (rounds_custom > 0)
+	{
+
+		char tmp_buf[80]; /* "rounds=999999999" */
+
+		memset(&tmp_buf, '\0', sizeof(tmp_buf));
+		snprintf(tmp_buf, sizeof(tmp_buf), "rounds=%lu", rounds);
+		strlcat(out_buf, tmp_buf, sizeof(out_buf));
+		strlcat(out_buf, ascii_dollar, sizeof(out_buf));
+
+	}
+
+	elog(DEBUG1, "using salt len = %d", salt_len);
+	strncat(out_buf, dec_salt_binary, salt_len);
+
+	/* Sanity check:
+	 *
+	 * At this point the salt string buffer must not exceed expected size
+	 */
+	if (strlen(out_buf) > 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 */
+	cp = out_buf + strlen(out_buf);
+	*cp++ = ascii_dollar[0];
+
+# define b64_from_24bit(B2, B1, B0, N)                      \
+	do {                                                    \
+		unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
+		int i = (N);                                        \
+		while (i-- > 0)                                     \
+		{                                                   \
+			*cp++ = _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, PX_SHACRYPT_BUF_LEN);
+
+	/* make sure nothing important is left behind */
+	px_memset(&sha_buf, 0, sizeof sha_buf);
+
+	/* ...and we're done */
+	return passwd;
+
+error:
+	if (digestA != NULL)
+		px_md_free(digestA);
+
+	if (digestB != NULL)
+		px_md_free(digestB);
+
+	elog(ERROR, "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..89ae9f95cc7
--- /dev/null
+++ b/contrib/pgcrypto/expected/crypt-shacrypt.out
@@ -0,0 +1,75 @@
+--
+-- 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)
+
+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)
+
+-- 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..4231fe02845 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 1000
+
+/* Maximum number of rounds of shacrypt.  */
+#define PX_SHACRYPT_ROUNDS_MAX 999999999
+
 /*
  * 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..ca99aa98651
--- /dev/null
+++ b/contrib/pgcrypto/sql/crypt-shacrypt.sql
@@ -0,0 +1,47 @@
+--
+-- 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;
+
+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;
+
+-- cleanup
+DROP TABLE ctest;
\ No newline at end of file
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.47.1



^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-13 16:06  Alvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 2 replies; 38+ messages in thread

From: Alvaro Herrera @ 2025-01-13 16:06 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

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.

It's been said in my presence that pgcrypto is obsolete and shouldn't be
used anymore.  I'm not sure I believe that, but even if that's true,
it's clear that there's plenty of people who has an interest on it, so I
don't see that as an objection to reject this work.  So let's move on.


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.

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.

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

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/
"El que vive para el futuro es un iluso, y el que vive para el pasado,
un imbécil" (Luis Adler, "Los tripulantes de la noche")





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-14 09:34  Bernd Helmle <mailings@oopsware.de>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  1 sibling, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-01-14 09:34 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Hi Alvaro,

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 didn't think about testing pg_ident, sorry.

> 
> 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.

Will try.

> 
> It's been said in my presence that pgcrypto is obsolete and shouldn't
> be
> used anymore.  I'm not sure I believe that, but even if that's true,
> it's clear that there's plenty of people who has an interest on it,
> so I
> don't see that as an objection to reject this work.  So let's move
> on.
> 

Oh, that's news to me. Is there a plan for it somewhere? I agree that
pgcrypto is widley used and needs a proper replacement when we decide
to deprecate it.

> 
> 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.

Good idea. Will do.

> 
> 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.
> 

I am going to give it a try, probably helps to get rid of the
strncat()/strlcat() mess.

I originally thought about StringInfo but went with just the fixed
length character buffers since we access them directly anyways (and the
px_*/OpenSSL API needs char * ).

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

I added them during development. I am not married to them, but found
them useful during testing. If we come to the conclusion they're not
really that important, i drop them entirely.


Thanks for your comments!

	Bernd






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-14 10:47  Alvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 1 reply; 38+ messages in thread

From: Alvaro Herrera @ 2025-01-14 10:47 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Hello Bernd,

On 2025-Jan-14, Bernd Helmle wrote:

> > It's been said in my presence that pgcrypto is obsolete and
> > shouldn't be used anymore.  I'm not sure I believe that, but even if
> > that's true, it's clear that there's plenty of people who has an
> > interest on it, so I don't see that as an objection to reject this
> > work.  So let's move on.
> 
> Oh, that's news to me. Is there a plan for it somewhere? I agree that
> pgcrypto is widley used and needs a proper replacement when we decide
> to deprecate it.

I don't know about a plan, but
https://www.youtube.com/watch?v=pp6xdr3TuWM&t=1088s

> I originally thought about StringInfo but went with just the fixed
> length character buffers since we access them directly anyways (and the
> px_*/OpenSSL API needs char * ).

Note that you can access the char * via StringInfo->data.  Just don't
modify it without the StringInfo APIs.

> > Some of your elog(ERROR)s should probably be ereport(), and I'm not
> > sure we want all the elog(DEBUG1)s.
> 
> I added them during development. I am not married to them, but found
> them useful during testing. If we come to the conclusion they're not
> really that important, i drop them entirely.

Yeah, the DEBUGs are a pretty minor issue -- it's easy to remove them
afterwards.  For any actual error condition that's not a "can't happen"
one, please use ereport() for consistency.  (There's no translation
support for contrib, so they won't be translated anyway.)

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"No deja de ser humillante para una persona de ingenio saber
que no hay tonto que no le pueda enseñar algo." (Jean B. Say)





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-14 11:41  Bernd Helmle <mailings@oopsware.de>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  0 siblings, 0 replies; 38+ messages in thread

From: Bernd Helmle @ 2025-01-14 11:41 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Am Dienstag, dem 14.01.2025 um 11:47 +0100 schrieb Alvaro Herrera:
> > Oh, that's news to me. Is there a plan for it somewhere? I agree
> > that
> > pgcrypto is widley used and needs a proper replacement when we
> > decide
> > to deprecate it.
> 
> I don't know about a plan, but
> https://www.youtube.com/watch?v=pp6xdr3TuWM&t=1088s


Hmm, I understand this like Peter tries to make a point regarding
transparent data encryption solutions, which pgcrypto doesn't serve
very well. Maybe it's not a point for total deprecation but that we
need additional, better solutions (for sure)...

Thanks,

	Bernd






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-20 17:41  Bernd Helmle <mailings@oopsware.de>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  1 sibling, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-01-20 17:41 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

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

Attachments:

  [text/x-patch] 0001-Add-modern-SHA-2-based-password-hashes-to-pgcrypto-v3.patch (41.0K, ../../3e48cd7c02f437ed980ee57340cb5c54f60e359c.camel@oopsware.de/2-0001-Add-modern-SHA-2-based-password-hashes-to-pgcrypto-v3.patch)
  download | inline diff:
From 20ae93aead22cdc4886084b282741a854a163905 Mon Sep 17 00:00:00 2001
From: Bernd Helmle <Bernd Helmle mailings@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



^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-23 13:36  Japin Li <japinli@hotmail.com>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 1 reply; 38+ messages in thread

From: Japin Li @ 2025-01-23 13:36 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>; pgsql-hackers

On Mon, 20 Jan 2025 at 18:41, Bernd Helmle <mailings@oopsware.de> wrote:
> 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.
>
>

Hi, Bernd Helmle

Thanks for updating the patch.  Here are some comments for v3 patch.

1.
+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 */

s/sha512crypt/sha256crypt/g

2.
Both PX_SHACRYPT_BUF_LEN and PX_SHACRYPT_DIGEST_MAX_LENGTH are macros for
length; however, they use different suffixes.  How about unifying them?
I prefer to use the LEN suffix.

3.
+       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$\""));
+       }
...
+       if (strncmp(dec_salt_binary, magic_bytes[0], strlen(magic_bytes[0])) == 0)
...
+       else if (strncmp(dec_salt_binary, magic_bytes[1], strlen(magic_bytes[1])) == 0)

IMO, we could change the above code as:

+       if (strncmp(dec_salt_binary, magic_bytes[0], strlen(magic_bytes[0])) == 0)
...
+       else if (strncmp(dec_salt_binary, magic_bytes[1], strlen(magic_bytes[1])) == 0)
...
+       else
+       {
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("invalid format of salt")),
+                               errhint("magic byte format for shacrypt is either \"$5$\" or \"$6$\""));
+       }

4.
+/* Default number of rounds of shacrypt if not explicitly specified.  */
+#define PX_SHACRYPT_ROUNDS_DEFAULT 5000

It seems you were missing the `l` suffix, since both PX_SHACRYPT_ROUNDS_MIN and
PX_SHACRYPT_ROUNDS_MAX have this suffix.

5.
Does the following work as expected?

postgres=# select crypt('hello', '$5$$6$rounds=10000$/Zg436s2vmTwsoSz');
                      crypt
-------------------------------------------------
 $5$$TCRu/ts4Npu8OJyeWy2WnUHCe/6bKVMSi0sROUrPh48
(1 row)

postgres=# select crypt('hello', '$5$rounds=10000$/Zg436s2vmTwsoSz');
                                    crypt
------------------------------------------------------------------------------
 $5$rounds=10000$/Zg436s2vmTwsoSz$N4Ad0oGzE6ak30z4EGSEXOc2OXQOpmauicPT3560lY2
(1 row)

According to the documentation, I think the first should output as following:

$5$rounds=5000$TCRu/ts4Npu8OJyeWy2WnUHCe/6bKVMSi0sROUrPh48

-- 
Regrads,
Japin Li





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-24 17:14  Bernd Helmle <mailings@oopsware.de>
  parent: Japin Li <japinli@hotmail.com>
  0 siblings, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-01-24 17:14 UTC (permalink / raw)
  To: Japin Li <japinli@hotmail.com>; +Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>; pgsql-hackers

Am Donnerstag, dem 23.01.2025 um 21:36 +0800 schrieb Japin Li:

Thanks for your review again. I am going to work on the other items,
but this one might need further discussion:

> 5.
> Does the following work as expected?
> 
> postgres=# select crypt('hello',
> '$5$$6$rounds=10000$/Zg436s2vmTwsoSz');
>                       crypt
> -------------------------------------------------
>  $5$$TCRu/ts4Npu8OJyeWy2WnUHCe/6bKVMSi0sROUrPh48
> (1 row)
> 
> postgres=# select crypt('hello', '$5$rounds=10000$/Zg436s2vmTwsoSz');
>                                     crypt
> ---------------------------------------------------------------------
> ---------
>  $5$rounds=10000$/Zg436s2vmTwsoSz$N4Ad0oGzE6ak30z4EGSEXOc2OXQOpmauicP
> T3560lY2
> (1 row)
> 
> According to the documentation, I think the first should output as
> following:
> 
> $5$rounds=5000$TCRu/ts4Npu8OJyeWy2WnUHCe/6bKVMSi0sROUrPh48

So the password hash/salt string used here is broken. '$' is exclusive
to define the boundaries of each part of the password hash string, so
it's very ambiguous what this preample in your example should do.

Note that the code i'm using is nearly identical to what the current
implementation of 'md5' does:

bernd@localhost:bernd :1 #= select crypt('hello',
'$1$$6$/Zg436s2vmTwsoSz');
DEBUG:  md5 salt len = 0, salt = $6$/Zg436s2vmTwsoSz
           crypt            
────────────────────────────
 $1$$/PWPe740uvaQxKzRH.Zxj1
(1 row)

The DEBUG output was added by me. Since the salt len is evaluated to 0,
effectively no salt is handled at all.

So we behave exactly the same way as px_crypt_md5(): It stops after the
first '$' after the magic byte preamble. For shacrypt, this could be
the next '$' after the closing one of the non-mandatory 'rounds'
option, but with your example this doesn't happen since it gets never
parsed. The salt length will be set to 0.

Furthermore, EVP_DigestUpdate() will do nothing if a count parameter
with 0 is handed over, which happens in our case here when adding the
salt. It happily returns 1 (success), so px_update_digest() will never
ERROR out.

Btw., blowfish seems more strict about this: playing around with it a
little i couldn't make it accept a garbaged password hash string like
the current example.

So, I am not sure what to do about this. Originally i had the feeling
we just throw an ERROR if the salt couldn't be evaluated properly, e.g
when we couldn't examine any salt by checking its length being larger
than 0. And '$' is not even a valid character within a salt string. But
then decided to go along with px_crypt_md5(). 

In short: If you throw a garbaged password hash string as the 2nd
argument to crypt() for md5/shacrypt, you currently might get something
obscure back.

I'd like to hear other opinions on this issue.


	Bernd






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-24 18:06  Alvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 2 replies; 38+ messages in thread

From: Alvaro Herrera @ 2025-01-24 18:06 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

On 2025-Jan-24, Bernd Helmle wrote:

> So we behave exactly the same way as px_crypt_md5(): It stops after the
> first '$' after the magic byte preamble. For shacrypt, this could be
> the next '$' after the closing one of the non-mandatory 'rounds'
> option, but with your example this doesn't happen since it gets never
> parsed. The salt length will be set to 0.

IMO silently using no salt or 0 iterations because the input is somewhat
broken is bad security and should be rejected.  If we did so in the past
without noticing, that's bad already, but we should not replicate that
behavior any further.

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/
"Doing what he did amounts to sticking his fingers under the hood of the
implementation; if he gets his fingers burnt, it's his problem."  (Tom Lane)





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-27 04:28  Japin Li <japinli@hotmail.com>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  1 sibling, 0 replies; 38+ messages in thread

From: Japin Li @ 2025-01-27 04:28 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Bernd Helmle <mailings@oopsware.de>; pgsql-hackers

On Fri, 24 Jan 2025 at 19:06, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
> On 2025-Jan-24, Bernd Helmle wrote:
>
>> So we behave exactly the same way as px_crypt_md5(): It stops after the
>> first '$' after the magic byte preamble. For shacrypt, this could be
>> the next '$' after the closing one of the non-mandatory 'rounds'
>> option, but with your example this doesn't happen since it gets never
>> parsed. The salt length will be set to 0.
>
> IMO silently using no salt or 0 iterations because the input is somewhat
> broken is bad security and should be rejected.  If we did so in the past
> without noticing, that's bad already, but we should not replicate that
> behavior any further.
>

I agree with this point, so maybe we should fix this for px_crypt_md().

-- 
Regrads,
Japin Li





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-01-28 10:25  Bernd Helmle <mailings@oopsware.de>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  1 sibling, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-01-28 10:25 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Am Freitag, dem 24.01.2025 um 19:06 +0100 schrieb Alvaro Herrera:
> > So we behave exactly the same way as px_crypt_md5(): It stops after
> > the
> > first '$' after the magic byte preamble. For shacrypt, this could
> > be
> > the next '$' after the closing one of the non-mandatory 'rounds'
> > option, but with your example this doesn't happen since it gets
> > never
> > parsed. The salt length will be set to 0.
> 
> IMO silently using no salt or 0 iterations because the input is
> somewhat
> broken is bad security and should be rejected.  If we did so in the
> past
> without noticing, that's bad already, but we should not replicate
> that
> behavior any further.

Following Japin's example we parse the broken input after the magic
byte into

$6$rounds=10000$/Zg436s2vmTwsoSz

So this is what gets passed over to the next steps after extracting the
magic byte.

We then try to parse the optional rounds option, which is expected to
be at the very first characters at this stage, which isn't true. In
that case we fall back to default 5000 iterations. So no problem with
iterations here. Note that we don't output the rounds option in that
case. I adapted this from Drepper's code to be compatible with the
tests (it suppresses the rounds option in case the default is used). I
forgot to adjust the docs about this change.

But we don't extract the salt because of the first '$', which confused
the input handling that there isn't anything interesting for it. And
that get unnoticed currently, which might be a problem for the caller.
Well, you see that something is wrong when looking at the result, but
if that happens programmatically it might be hidden.

I've looked what others do:

Drepper's code with the input above produces the following:

$5$=10000$cWSkAaJa1mL912.HQqjIhODJ9b3S7hmgsb/k9Efp7.7

It parses the salt from the input as "=10000".

Python's passlib is very strict when it comes to supported characters
within a salt string. It rejects everything thats not matching '[./0-
9A-Za-z]'. So when you provide the example above you get

  File "/usr/lib/python3.13/site-packages/passlib/utils/handlers.py",
line 1459, in _norm_salt
    raise ValueError("invalid characters in %s salt" % cls.name)
ValueError: invalid characters in sha256_crypt salt

openssl-passwd does this:

echo test | openssl passwd -5 -stdin -salt
'$6$rounds=10000$/Zg436s2vmTwsoSz'

$5$$6$rounds=10000$$BFtTxJrvpb/ra8SnnXkiNCJ1HGZ3OOmwvyQ2TJZx44B

It absorbs everything up to 16 bytes and uses that as the salt string.

Interestingly, python-passlib and Drepper's code accept both empty
salt, openssl-passwd seems to be buggy:

echo test | openssl passwd -5 -stdin -salt ''                         
<NULL>

So at least, they do *something* with the input and don't silently omit
salting the input passphrase, but the results aren't the same hash.

So i think we have two options: adapt Drepper's code and throw away
everything or be very strict like python-passlib. I myself can find
support for both, so not sure.


	Bernd








^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-02-06 10:20  Alvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 1 reply; 38+ messages in thread

From: Alvaro Herrera @ 2025-02-06 10:20 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

On 2025-Jan-28, Bernd Helmle wrote:

> Python's passlib is very strict when it comes to supported characters
> within a salt string. It rejects everything thats not matching '[./0-
> 9A-Za-z]'. So when you provide the example above you get

The reason it uses these chars is that in their scheme the salt bytes
are base64-encoded.

The passlib docs has this page about the "modular crypt format":
https://passlib.readthedocs.io/en/stable/modular_crypt_format.html

and they point this other page as a "modern, non-ambiguous standard":
https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
About the salt, this last document says:

  The role of salts is to achieve uniqueness. A random salt is fine for
  that as long as its length is sufficient; a 16-byte salt would work
  well (by definition, UUID are very good salts, and they encode over
  exactly 16 bytes). 16 bytes encode as 22 characters in B64. Functions
  should disallow salt values that are too small for security (4 bytes
  should be viewed as an absolute minimum).

This "Password Hashing Competition" organization hardly seems an
authority though.  It'd be great to have an IETF standard about this ...

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-02-07 08:17  Japin Li <japinli@hotmail.com>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  0 siblings, 1 reply; 38+ messages in thread

From: Japin Li @ 2025-02-07 08:17 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Bernd Helmle <mailings@oopsware.de>; pgsql-hackers

On Thu, 06 Feb 2025 at 11:20, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
> On 2025-Jan-28, Bernd Helmle wrote:
>
>> Python's passlib is very strict when it comes to supported characters
>> within a salt string. It rejects everything thats not matching '[./0-
>> 9A-Za-z]'. So when you provide the example above you get
>
> The reason it uses these chars is that in their scheme the salt bytes
> are base64-encoded.
>
> The passlib docs has this page about the "modular crypt format":
> https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
>
> and they point this other page as a "modern, non-ambiguous standard":
> https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
> About the salt, this last document says:
>
>   The role of salts is to achieve uniqueness. A random salt is fine for
>   that as long as its length is sufficient; a 16-byte salt would work
>   well (by definition, UUID are very good salts, and they encode over
>   exactly 16 bytes). 16 bytes encode as 22 characters in B64. Functions
>   should disallow salt values that are too small for security (4 bytes
>   should be viewed as an absolute minimum).
>
> This "Password Hashing Competition" organization hardly seems an
> authority though.  It'd be great to have an IETF standard about this ...

Yeah.

Since there is no standard, how do we handle this?  I prefer to use the strict
mode like passlib.

-- 
Regrads,
Japin Li





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-02-07 09:31  Alvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Japin Li <japinli@hotmail.com>
  0 siblings, 2 replies; 38+ messages in thread

From: Alvaro Herrera @ 2025-02-07 09:31 UTC (permalink / raw)
  To: Japin Li <japinli@hotmail.com>; +Cc: Bernd Helmle <mailings@oopsware.de>; pgsql-hackers

On 2025-Feb-07, Japin Li wrote:

> Since there is no standard, how do we handle this?  I prefer to use
> the strict mode like passlib.

I definitely like that passlib have documented their thought process
thoroughly.

I think using their strict mode is good on principle, but if we're going
to do that, then the salt string should not be used verbatim, but
instead base64-decoded first to get the actual salt bytes, like they do.
Does this break compabitibility with other systems?  Are
passlib-generated password hashes incompatible with, say, "openssl
passwd" which you (Bernd) mentioned at the beginning of the thread?
Maybe if the password hashes are no longer compatible, then we should
ditch the idea of restricting salts to base64 chars and accept the whole
range of bytes, like Drepper.

But in any case ISTM we should reject, as they suggest, the use of less
than 4 bytes of salt (and should perhaps settle for a default of 16, as
passlib suggests).  I suppose this is why passlib returns NULL with
empty salt.  What we should do in that case IMO is ereport(ERROR).  

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/
"Porque Kim no hacía nada, pero, eso sí,
con extraordinario éxito" ("Kim", Kipling)





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-02-08 02:16  Japin Li <japinli@hotmail.com>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  1 sibling, 0 replies; 38+ messages in thread

From: Japin Li @ 2025-02-08 02:16 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Bernd Helmle <mailings@oopsware.de>; pgsql-hackers

On Fri, 07 Feb 2025 at 10:31, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
> On 2025-Feb-07, Japin Li wrote:
>
>> Since there is no standard, how do we handle this?  I prefer to use
>> the strict mode like passlib.
>
> I definitely like that passlib have documented their thought process
> thoroughly.
>
> I think using their strict mode is good on principle, but if we're going
> to do that, then the salt string should not be used verbatim, but
> instead base64-decoded first to get the actual salt bytes, like they do.
> Does this break compabitibility with other systems?  Are
> passlib-generated password hashes incompatible with, say, "openssl
> passwd" which you (Bernd) mentioned at the beginning of the thread?
> Maybe if the password hashes are no longer compatible, then we should
> ditch the idea of restricting salts to base64 chars and accept the whole
> range of bytes, like Drepper.

Thinking about compatibility, the Drepper's behavior is a good choice.

Or we can accept the whole range of bytes except the $ character since it is
a separator.  Of course, it is also not compatible with other systems.

>
> But in any case ISTM we should reject, as they suggest, the use of less
> than 4 bytes of salt (and should perhaps settle for a default of 16, as
> passlib suggests).  I suppose this is why passlib returns NULL with
> empty salt.  What we should do in that case IMO is ereport(ERROR).

+1

-- 
Regrads,
Japin Li





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-03-11 16:58  Bernd Helmle <mailings@oopsware.de>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  1 sibling, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-03-11 16:58 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; Japin Li <japinli@hotmail.com>; +Cc: pgsql-hackers

Hi,

> I definitely like that passlib have documented their thought process
> thoroughly.
> 

Please find attached v4 of this patch. I added the following changes:

- Check for non-supported characters in the salt like passlib does.
- Check for reserved tokens when parsing the salt string (i find this
very strict, but it covers the cases Japin Li pointed out).

I didn't implement being more strict when it comes to the salt length:
the salt might be provided from external sources and the password hash
is just used for validating within the database.

When hashing the password within postgres, users always should use
gen_salt(), which generates a reasonable salt string. 

Maybe, in case of empty salts, we should issue a WARNING instead of
erroring out and put additional documentation on how to use it right.

> I think using their strict mode is good on principle, but if we're
> going
> to do that, then the salt string should not be used verbatim, but
> instead base64-decoded first to get the actual salt bytes, like they
> do.
> Does this break compabitibility with other systems?  Are
> passlib-generated password hashes incompatible with, say, "openssl
> passwd" which you (Bernd) mentioned at the beginning of the thread?
> Maybe if the password hashes are no longer compatible, then we should
> ditch the idea of restricting salts to base64 chars and accept the
> whole
> range of bytes, like Drepper.
> 
> But in any case ISTM we should reject, as they suggest, the use of
> less
> than 4 bytes of salt (and should perhaps settle for a default of 16,
> as
> passlib suggests).  I suppose this is why passlib returns NULL with
> empty salt.  What we should do in that case IMO is ereport(ERROR). 
> 


Hmm, i didn't understand that passlib does decode them first, i thought
they use it encoded... at least, in our current form we're pretty much
compatible with Drepper, passlib and OpenSSL, as far as i tested:

Empty salt
----------

# Drepper reference code

shacrypt password "" sha512crypt
$6$$bLTg4cpho8PIUrjfsE7qlU08Qx2UEfw..xOc6I1wpGVtyVYToGrr7BzRdAAnEr5lYFr
1Z9WcCf1xNZ1HG9qFW1

# Patch

SELECT crypt('password', '$6$$');
                                           crypt                      
───────────────────────────────────────────────────────────────────────
─────────────────────
$6$$bLTg4cpho8PIUrjfsE7qlU08Qx2UEfw..xOc6I1wpGVtyVYToGrr7BzRdAAnEr5lYFr1
Z9WcCf1xNZ1HG9qFW1
(1 row)

# Passlib

from passlib.hash import sha256_crypt,sha512_crypt
hash = sha512_crypt.using(salt='').using(rounds=5000).hash("password")
print(hash)
$6$$bLTg4cpho8PIUrjfsE7qlU08Qx2UEfw..xOc6I1wpGVtyVYToGrr7BzRdAAnEr5lYFr
1Z9WcCf1xNZ1HG9qFW1

# OpenSSL

echo password | openssl passwd -6 -stdin -salt ''             
<NULL>

Short salt
----------

# Drepper reference code

./shacrypt password abcdef sha512crypt
$6$abcdef$2yYFwtar3.rlLovG0bXbxXMjZe7Z/1EhQ0gEs0nXalsCW04p2iy6unkvbrBFH
1SXexabbdNXXOO11F7sdyurk/

# Patch

SELECT crypt('password', '$6$abcdef$');
                                              crypt                   
───────────────────────────────────────────────────────────────────────
───────────────────────────
$6$abcdef$2yYFwtar3.rlLovG0bXbxXMjZe7Z/1EhQ0gEs0nXalsCW04p2iy6unkvbrBFH1
SXexabbdNXXOO11F7sdyurk/
(1 row)

# Passlib

from passlib.hash import sha256_crypt,sha512_crypt
hash =
sha512_crypt.using(salt='abcdef').using(rounds=5000).hash("password")
print(hash)
$6$abcdef$2yYFwtar3.rlLovG0bXbxXMjZe7Z/1EhQ0gEs0nXalsCW04p2iy6unkvbrBFH
1SXexabbdNXXOO11F7sdyurk/

# OpenSSL

echo password | openssl passwd -6 -stdin -salt 'abcdef'
$6$abcdef$2yYFwtar3.rlLovG0bXbxXMjZe7Z/1EhQ0gEs0nXalsCW04p2iy6unkvbrBFH
1SXexabbdNXXOO11F7sdyurk/

Salt > MAXLEN(16 Bytes)
-----------------------

# Drepper reference code
(truncates salt)

shacrypt password abcdefghijklmnopqrstuvwxyz sha512crypt
$6$abcdefghijklmnop$0aenUFHf897F9u0tURIHOeACWajSuVGa7jgJGyq.DKZm/WXl/IZ
FvPbneFydBjomEOgM.Sh1m0L3KsS1.H5b//

# Patch
(truncates salt)

SELECT crypt('password', '$6$abcdefghijklmnopqrstuvwxyz$');
                                                   crypt              
───────────────────────────────────────────────────────────────────────
─────────────────────────────────────
$6$abcdefghijklmnop$0aenUFHf897F9u0tURIHOeACWajSuVGa7jgJGyq.DKZm/WXl/IZF
vPbneFydBjomEOgM.Sh1m0L3KsS1.H5b//
(1 row)

# Passlib

Errors out with

ValueError: salt too large (sha512_crypt requires <= 16 chars)

# OpenSSL
(truncates salt)

echo password | openssl passwd -6 -stdin -salt
'abcdefghijklmnopqrstuvwxyz'
$6$abcdefghijklmnop$0aenUFHf897F9u0tURIHOeACWajSuVGa7jgJGyq.DKZm/WXl/IZ
FvPbneFydBjomEOgM.Sh1m0L3KsS1.H5b//

Salt with "invalid" character
-----------------------------

# Drepper reference code

./shacrypt password abcdefghi%jklmno sha512crypt
$6$abcdefghi%jklmno$LMN/V1pW97IoK0rWSDVqCo9EYd6zpqP0TdTX9.cxFAFqsdSMWQM
jehkmMtDzL36VBKeG6dg.kFAQKoFvZpK0G.

# Patch
current version V4 (attached)

Errors out:

SELECT crypt('password', '$6$abcdefghi%jklmno$');
FEHLER:  invalid character in salt string: "%"
Time: 0,217 ms

# Passlib

Errors out

ValueError: invalid characters in sha512_crypt salt

# OpenSSL

echo password | openssl passwd -6 -stdin -salt 'abcdefghi%jklmno'     
$6$abcdefghi%jklmno$LMN/V1pW97IoK0rWSDVqCo9EYd6zpqP0TdTX9.cxFAFqsdSMWQM
jehkmMtDzL36VBKeG6dg.kFAQKoFvZpK0G.


So the hashes we produce are identical, but with being more strict we
differ in the handling of the provided salt.

	Bernd

Attachments:

  [text/x-patch] 0001-Add-modern-SHA-2-based-password-hashes-to-pgcrypto-v4.patch (43.3K, ../../e1590224d57bafb3caeb9b378daed1517e0e7357.camel@oopsware.de/2-0001-Add-modern-SHA-2-based-password-hashes-to-pgcrypto-v4.patch)
  download | inline diff:
From dccaa0c9cea257edb7350d0ff3ab7f1752383278 Mon Sep 17 00:00:00 2001
From: Bernd Helmle <Bernd Helmle mailings@oopsware.de>
Date: Tue, 11 Mar 2025 17:31:39 +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                 | 694 +++++++++++++++++++
 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                   |  41 +-
 9 files changed, 1164 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 11c4455dd05..69afa375011 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..b9aded897d1 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_MAX_LEN != 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 sha256crypt */
+	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..ec51e865a5e
--- /dev/null
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -0,0 +1,694 @@
+/*
+ * 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 */
+	StringInfo decoded_salt = NULL; /* decoded salt string */
+
+	unsigned char sha_buf[PX_SHACRYPT_DIGEST_MAX_LEN];
+	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];	/* temporary buffer for
+																 * digests */
+
+	char		rounds_custom = 0;
+	char	   *p_bytes = NULL;
+	char	   *s_bytes = NULL;
+	char	   *cp = NULL;
+	const char *fp = NULL;				/* intermediate pointer within salt string */
+	const char *ep = NULL;				/* 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 = 0;
+
+	/* Init result buffer */
+	out_buf = makeStringInfoExt(PX_SHACRYPT_BUF_LEN);
+	decoded_salt = makeStringInfoExt(PX_SHACRYPT_SALT_MAX_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_LEN;
+
+				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 decoded salt string from salt input, this is every
+	 * character before the last '$' in the preamble. Append every
+	 * compatible character up to PX_SHACRYPT_SALT_MAX_LEN to the result buffer.
+	 * Note that depending on the input, there might be no '$' marker after
+	 * the salt, when there is no password hash attached at the end.
+	 *
+	 * We try hard to recognize mistakes, but since we might get an input
+	 * string which might also have the password hash after the salt string
+	 * section we give up as soon we reach the end of the input or if there
+	 * are any bytes consumed for the salt string until we reach the
+	 * first '$' marker thereafter.
+	 */
+	for (ep = dec_salt_binary;
+		 *ep && ep < (dec_salt_binary + PX_SHACRYPT_SALT_MAX_LEN);
+		 ep++)
+	{
+		/*
+		 * Filter out any string which shouldn't be here.
+		 *
+		 * First check for accidentally embedded magic strings here. We don't
+		 * support '$' in salt strings anyways and seeing a magic byte trying
+		 * to identify shacrypt hashes might indicate that something went
+		 * wrong when generating this salt string. Note that we later check for
+		 * non-supported literals anyways, but any '$' here confuses us
+		 * at this point.
+		 */
+		fp = strstr(dec_salt_binary, magic_bytes[0]);
+
+		if (fp != NULL)
+		{
+			elog(ERROR, "bogus magic byte found in salt string");
+		}
+
+		fp = strstr(dec_salt_binary, magic_bytes[1]);
+
+		if (fp != NULL)
+		{
+			elog(ERROR, "bogus magic byte found in salt string");
+		}
+
+		/*
+		 * This looks very strict, but we assume the caller did something
+		 * wrong when we see a "rounds=" option here.
+		 */
+		fp = strstr(dec_salt_binary, rounds_prefix);
+
+		if (fp != NULL)
+		{
+			elog(ERROR, "invalid rounds option specified in salt string");
+		}
+
+		if (*ep != '$')
+		{
+			if (isalpha(*ep) || isdigit(*ep) || (*ep == '.') || (*ep == '/'))
+			{
+				appendStringInfoCharMacro(decoded_salt, *ep);
+			}
+			else
+			{
+				elog(ERROR, "invalid character in salt string: \"%c\"", *ep);
+			}
+		}
+		else
+		{
+			/* We encountered a '$' marker. Check if we already absorbed
+			 * some bytes from input. If true, we are optimistic and
+			 * terminate at this stage. Of not, we try further.
+			 *
+			 * If we already consumed enough bytes for the salt string, everything
+			 * that is after this marker is considered to be part of an
+			 * optionally specified password hash and ignored.
+			 */
+			if (decoded_salt->len > 0)
+				break;
+		}
+
+	}
+
+	salt_len = decoded_salt->len;
+	appendStringInfoString(out_buf, decoded_salt->data);
+	elog(DEBUG1, "using salt \"%s\", salt len = %d, rounds = %lu",
+		 decoded_salt->data, decoded_salt->len, rounds);
+
+	/*
+	 * 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 *) decoded_salt->data, 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);
+	destroyStringInfo(decoded_salt);
+
+	/* ...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 7a4e8e76d64..7d5ef9b6d32 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 96ce9384aff..df9875b9433 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}
@@ -127,6 +139,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_MAX_LEN,
+	 PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
+	{"sha512crypt", _crypt_gensalt_sha512_rn, PX_SHACRYPT_SALT_MAX_LEN,
+	 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..9287c33efe1 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_MAX_LEN 16
+
+/* SHA buffer length */
+#define PX_SHACRYPT_DIGEST_MAX_LEN 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_MAX_LEN + 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 5000l
+
+/* 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 f87668dfaed..b567b5c16e8 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,14 @@ 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.
+   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



^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-03 18:39  Alvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 2 replies; 38+ messages in thread

From: Alvaro Herrera @ 2025-04-03 18:39 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

On 2025-Mar-11, Bernd Helmle wrote:

> Please find attached v4 of this patch. I added the following changes:
> 
> - Check for non-supported characters in the salt like passlib does.
> - Check for reserved tokens when parsing the salt string (i find this
> very strict, but it covers the cases Japin Li pointed out).
> 
> I didn't implement being more strict when it comes to the salt length:
> the salt might be provided from external sources and the password hash
> is just used for validating within the database.
> 
> When hashing the password within postgres, users always should use
> gen_salt(), which generates a reasonable salt string. 

This sounds reasonable to me.

> Maybe, in case of empty salts, we should issue a WARNING instead of
> erroring out and put additional documentation on how to use it right.

I don't know, that doesn't seem ideal to me, because it's very easy to
run stuff and never see the warnings.  If we find that people are
desperate to use empty salts, we can relax that later (turn the error to
a warning), but I'd rather not have it in the first cut.

> Hmm, i didn't understand that passlib does decode them first, i thought
> they use it encoded... at least, in our current form we're pretty much
> compatible with Drepper, passlib and OpenSSL, as far as i tested:

I am ready to believe that I misinterpreted what I read.

> So the hashes we produce are identical, but with being more strict we
> differ in the handling of the provided salt.

Okay.


I can offer a few cosmetic changes.  0001 is a pgindent run, and 0002 is
some manual adjustments after that.  There are only two nontrivial
changes

1. the calculation for rounds was using type long, which is confusing
because the range is different according to the platform.  Since it's
limited by the macro definitions to no more than 999999999, we can make
it an int32 instead.  So we use strtoint() instead of strtoul() to parse
the value, and remove the "l" suffixes from the macros that define the
limits and default, which were bugging me a bit when used in the
gen_list struct.

2. I changed "if (block % 3)" to "if ((block % 3) != 0)".

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"El destino baraja y nosotros jugamos" (A. Schopenhauer)
From 864395b6468d62f17d2e8f0e593408849564edc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@alvh.no-ip.org>
Date: Wed, 2 Apr 2025 17:25:20 +0200
Subject: [PATCH 1/2] pgindent

---
 contrib/pgcrypto/crypt-gensalt.c | 14 +++++------
 contrib/pgcrypto/crypt-sha.c     | 42 +++++++++++++++++---------------
 contrib/pgcrypto/px-crypt.c      |  6 ++---
 contrib/pgcrypto/px-crypt.h      |  2 +-
 4 files changed, 33 insertions(+), 31 deletions(-)

diff --git a/contrib/pgcrypto/crypt-gensalt.c b/contrib/pgcrypto/crypt-gensalt.c
index b9aded897d1..922f9eea6fa 100644
--- a/contrib/pgcrypto/crypt-gensalt.c
+++ b/contrib/pgcrypto/crypt-gensalt.c
@@ -190,9 +190,9 @@ static char *
 _crypt_gensalt_sha(unsigned long count,
 				   const char *input, int size, char *output, int output_size)
 {
-	char * s_ptr = output;
+	char	   *s_ptr = output;
 	unsigned int result_bufsize = PX_SHACRYPT_SALT_BUF_LEN;
-	int rc;
+	int			rc;
 
 	/* output buffer must be allocated with PX_MAX_SALT_LEN bytes */
 	if (PX_MAX_SALT_LEN < result_bufsize)
@@ -203,8 +203,8 @@ _crypt_gensalt_sha(unsigned long count,
 	}
 
 	/*
-	 * Care must be taken to not exceed the buffer size allocated for
-	 * the input character buffer.
+	 * Care must be taken to not exceed the buffer size allocated for the
+	 * input character buffer.
 	 */
 	if ((PX_SHACRYPT_SALT_MAX_LEN != size)
 		|| (output_size < size))
@@ -229,8 +229,8 @@ _crypt_gensalt_sha(unsigned long count,
 	/*
 	 * Normalize salt string
 	 *
-	 * size of input buffer was checked above to
-	 * not exceed PX_SHACRYPT_SALT_LEN_MAX.
+	 * size of input buffer was checked above to not exceed
+	 * PX_SHACRYPT_SALT_LEN_MAX.
 	 */
 	for (int i = 0; i < size; i++)
 	{
@@ -268,4 +268,4 @@ _crypt_gensalt_sha256_rn(unsigned long count,
 	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
index ec51e865a5e..13ee8913052 100644
--- a/contrib/pgcrypto/crypt-sha.c
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -77,24 +77,25 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	int			err;
 
 	const char *dec_salt_binary;	/* pointer into the real salt string */
-	StringInfo decoded_salt = NULL; /* decoded salt string */
+	StringInfo	decoded_salt = NULL;	/* decoded salt string */
 
 	unsigned char sha_buf[PX_SHACRYPT_DIGEST_MAX_LEN];
 	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];	/* temporary buffer for
-																 * digests */
+															 * digests */
 
 	char		rounds_custom = 0;
 	char	   *p_bytes = NULL;
 	char	   *s_bytes = NULL;
 	char	   *cp = NULL;
-	const char *fp = NULL;				/* intermediate pointer within salt string */
-	const char *ep = NULL;				/* holds pointer to the end of the salt string */
+	const char *fp = NULL;		/* intermediate pointer within salt string */
+	const char *ep = NULL;		/* 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 = 0;
+	unsigned	len,
+				salt_len = 0;
 
 	/* Init result buffer */
 	out_buf = makeStringInfoExt(PX_SHACRYPT_BUF_LEN);
@@ -299,16 +300,16 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 	/*
 	 * We need the real decoded salt string from salt input, this is every
-	 * character before the last '$' in the preamble. Append every
-	 * compatible character up to PX_SHACRYPT_SALT_MAX_LEN to the result buffer.
-	 * Note that depending on the input, there might be no '$' marker after
-	 * the salt, when there is no password hash attached at the end.
+	 * character before the last '$' in the preamble. Append every compatible
+	 * character up to PX_SHACRYPT_SALT_MAX_LEN to the result buffer. Note
+	 * that depending on the input, there might be no '$' marker after the
+	 * salt, when there is no password hash attached at the end.
 	 *
 	 * We try hard to recognize mistakes, but since we might get an input
 	 * string which might also have the password hash after the salt string
 	 * section we give up as soon we reach the end of the input or if there
-	 * are any bytes consumed for the salt string until we reach the
-	 * first '$' marker thereafter.
+	 * are any bytes consumed for the salt string until we reach the first '$'
+	 * marker thereafter.
 	 */
 	for (ep = dec_salt_binary;
 		 *ep && ep < (dec_salt_binary + PX_SHACRYPT_SALT_MAX_LEN);
@@ -320,9 +321,9 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		 * First check for accidentally embedded magic strings here. We don't
 		 * support '$' in salt strings anyways and seeing a magic byte trying
 		 * to identify shacrypt hashes might indicate that something went
-		 * wrong when generating this salt string. Note that we later check for
-		 * non-supported literals anyways, but any '$' here confuses us
-		 * at this point.
+		 * wrong when generating this salt string. Note that we later check
+		 * for non-supported literals anyways, but any '$' here confuses us at
+		 * this point.
 		 */
 		fp = strstr(dec_salt_binary, magic_bytes[0]);
 
@@ -362,13 +363,14 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		}
 		else
 		{
-			/* We encountered a '$' marker. Check if we already absorbed
-			 * some bytes from input. If true, we are optimistic and
-			 * terminate at this stage. Of not, we try further.
+			/*
+			 * We encountered a '$' marker. Check if we already absorbed some
+			 * bytes from input. If true, we are optimistic and terminate at
+			 * this stage. Of not, we try further.
 			 *
-			 * If we already consumed enough bytes for the salt string, everything
-			 * that is after this marker is considered to be part of an
-			 * optionally specified password hash and ignored.
+			 * If we already consumed enough bytes for the salt string,
+			 * everything that is after this marker is considered to be part
+			 * of an optionally specified password hash and ignored.
 			 */
 			if (decoded_salt->len > 0)
 				break;
diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c
index df9875b9433..a786277aeac 100644
--- a/contrib/pgcrypto/px-crypt.c
+++ b/contrib/pgcrypto/px-crypt.c
@@ -71,7 +71,7 @@ static char *
 run_crypt_sha(const char *psw, const char *salt,
 			  char *buf, unsigned len)
 {
-	char *res;
+	char	   *res;
 
 	res = px_crypt_shacrypt(psw, salt, buf, len);
 	return res;
@@ -140,9 +140,9 @@ static struct generator gen_list[] = {
 	{"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_MAX_LEN,
-	 PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
+	PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
 	{"sha512crypt", _crypt_gensalt_sha512_rn, PX_SHACRYPT_SALT_MAX_LEN,
-	 PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_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 9287c33efe1..3829ec6c347 100644
--- a/contrib/pgcrypto/px-crypt.h
+++ b/contrib/pgcrypto/px-crypt.h
@@ -108,6 +108,6 @@ 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);
+char	   *px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstlen);
 
 #endif							/* _PX_CRYPT_H */
-- 
2.39.5
From 567d4b3706295a1f2bed53eb0b2f4acb95898899 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@alvh.no-ip.org>
Date: Thu, 3 Apr 2025 20:14:52 +0200
Subject: [PATCH 2/2] review

---
 contrib/pgcrypto/crypt-gensalt.c |  26 ++--
 contrib/pgcrypto/crypt-sha.c     | 248 ++++++++++++-------------------
 contrib/pgcrypto/px-crypt.c      |  14 +-
 contrib/pgcrypto/px-crypt.h      |   6 +-
 4 files changed, 121 insertions(+), 173 deletions(-)

diff --git a/contrib/pgcrypto/crypt-gensalt.c b/contrib/pgcrypto/crypt-gensalt.c
index 922f9eea6fa..ef68ae0e49e 100644
--- a/contrib/pgcrypto/crypt-gensalt.c
+++ b/contrib/pgcrypto/crypt-gensalt.c
@@ -186,6 +186,9 @@ _crypt_gensalt_blowfish_rn(unsigned long count,
 	return output;
 }
 
+/*
+ * Helper for _crypt_gensalt_sha256_rn and _crypt_gensalt_sha512_rn
+ */
 static char *
 _crypt_gensalt_sha(unsigned long count,
 				   const char *input, int size, char *output, int output_size)
@@ -196,32 +199,25 @@ _crypt_gensalt_sha(unsigned long count,
 
 	/* 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")));
-	}
+				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_MAX_LEN != size)
-		|| (output_size < size))
-	{
+	if ((PX_SHACRYPT_SALT_MAX_LEN != size) || (output_size < size))
 		ereport(ERROR,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("invalid length of salt buffer")));
-	}
+				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")));
-	}
+				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;
@@ -242,6 +238,7 @@ _crypt_gensalt_sha(unsigned long count,
 	return output;
 }
 
+/* gen_list->gen function for sha512 */
 char *
 _crypt_gensalt_sha512_rn(unsigned long count,
 						 char const *input, int size,
@@ -256,6 +253,7 @@ _crypt_gensalt_sha512_rn(unsigned long count,
 	return _crypt_gensalt_sha(count, input, size, output, output_size);
 }
 
+/* gen_list->gen function for sha256 */
 char *
 _crypt_gensalt_sha256_rn(unsigned long count,
 						 const char *input, int size,
diff --git a/contrib/pgcrypto/crypt-sha.c b/contrib/pgcrypto/crypt-sha.c
index 13ee8913052..862f3fb9a68 100644
--- a/contrib/pgcrypto/crypt-sha.c
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -44,6 +44,8 @@
  *
  */
 #include "postgres.h"
+
+#include "common/string.h"
 #include "miscadmin.h"
 
 #include "px-crypt.h"
@@ -78,23 +80,20 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 	const char *dec_salt_binary;	/* pointer into the real salt string */
 	StringInfo	decoded_salt = NULL;	/* decoded salt string */
-
 	unsigned char sha_buf[PX_SHACRYPT_DIGEST_MAX_LEN];
-	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];	/* temporary buffer for
-															 * digests */
 
+	/* temporary buffer for digests */
+	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];
 	char		rounds_custom = 0;
 	char	   *p_bytes = NULL;
 	char	   *s_bytes = NULL;
 	char	   *cp = NULL;
 	const char *fp = NULL;		/* intermediate pointer within salt string */
 	const char *ep = NULL;		/* 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,
+	uint32		rounds = PX_SHACRYPT_ROUNDS_DEFAULT;
+	unsigned int len,
 				salt_len = 0;
 
 	/* Init result buffer */
@@ -106,22 +105,16 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		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));
@@ -138,27 +131,22 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * 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.
+	 * salt string.  Do some sanity checks first.
 	 */
 	if (strlen(dec_salt_binary) < 3)
-	{
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid salt")));
-	}
+				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] != '$'))
-	{
+	if ((dec_salt_binary[0] != '$') || (dec_salt_binary[2] != '$'))
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid format of salt")),
+				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.
@@ -190,62 +178,54 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * 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
+	 * Unknown magic byte is handled further 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);
+		int			srounds = strtoint(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
-		{
+		if (*endp != '$')
 			ereport(ERROR,
 					errcode(ERRCODE_SYNTAX_ERROR),
 					errmsg("could not parse salt options"));
+
+		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.
+		 */
+		if (srounds > PX_SHACRYPT_ROUNDS_MAX)
+		{
+			ereport(NOTICE,
+					errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					errmsg("rounds=%d exceeds maximum supported value (%d), using %d 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=%d is below supported value (%d), using %d instead",
+						   srounds, PX_SHACRYPT_ROUNDS_MIN,
+						   PX_SHACRYPT_ROUNDS_MIN));
+			srounds = PX_SHACRYPT_ROUNDS_MIN;
 		}
 
+		rounds = (uint32) srounds;
+		rounds_custom = 1;
 	}
 
 	/*
@@ -294,9 +274,7 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	}
 
 	if (rounds_custom > 0)
-	{
-		appendStringInfo(out_buf, "rounds=%lu$", rounds);
-	}
+		appendStringInfo(out_buf, "rounds=%u$", rounds);
 
 	/*
 	 * We need the real decoded salt string from salt input, this is every
@@ -326,47 +304,34 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		 * this point.
 		 */
 		fp = strstr(dec_salt_binary, magic_bytes[0]);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "bogus magic byte found in salt string");
-		}
 
 		fp = strstr(dec_salt_binary, magic_bytes[1]);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "bogus magic byte found in salt string");
-		}
 
 		/*
 		 * This looks very strict, but we assume the caller did something
 		 * wrong when we see a "rounds=" option here.
 		 */
 		fp = strstr(dec_salt_binary, rounds_prefix);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "invalid rounds option specified in salt string");
-		}
 
 		if (*ep != '$')
 		{
 			if (isalpha(*ep) || isdigit(*ep) || (*ep == '.') || (*ep == '/'))
-			{
 				appendStringInfoCharMacro(decoded_salt, *ep);
-			}
 			else
-			{
 				elog(ERROR, "invalid character in salt string: \"%c\"", *ep);
-			}
 		}
 		else
 		{
 			/*
 			 * We encountered a '$' marker. Check if we already absorbed some
 			 * bytes from input. If true, we are optimistic and terminate at
-			 * this stage. Of not, we try further.
+			 * this stage. If not, we try further.
 			 *
 			 * If we already consumed enough bytes for the salt string,
 			 * everything that is after this marker is considered to be part
@@ -375,23 +340,19 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 			if (decoded_salt->len > 0)
 				break;
 		}
-
 	}
 
 	salt_len = decoded_salt->len;
 	appendStringInfoString(out_buf, decoded_salt->data);
-	elog(DEBUG1, "using salt \"%s\", salt len = %d, rounds = %lu",
+	elog(DEBUG1, "using salt \"%s\", salt len = %d, rounds = %u",
 		 decoded_salt->data, decoded_salt->len, rounds);
 
 	/*
-	 * Sanity check:
-	 *
-	 * At this point the salt string buffer must not exceed expected size
+	 * 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
@@ -413,29 +374,26 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	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.
+	/*
+	 * 9. For each block (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 */
+	 * 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)
+	 * 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)
 	{
@@ -447,10 +405,10 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		block >>= 1;
 	}
 
-	/* 12 Finalize digest A */
+	/* 12. Finalize digest A */
 	px_md_finish(digestA, sha_buf);
 
-	/* 13 Start digest DP */
+	/* 13. Start digest DP */
 	px_md_reset(digestB);
 
 	/*-
@@ -458,20 +416,17 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * to the digest DP
 	 */
 	for (block = len; block > 0; block--)
-	{
 		px_md_update(digestB, (const unsigned char *) pw, len);
-	}
 
-	/* 15 Finalize digest DP */
+	/* 15. Finalize digest DP */
 	px_md_finish(digestB, sha_buf_tmp);
 
 	/*-
-	 * 16 produce byte sequence P with same length as password.
-	 *
+	 * 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
+	 *        first N bytes of digest DP
 	 */
 	if ((p_bytes = palloc0(len)) == NULL)
 	{
@@ -484,30 +439,28 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	memcpy(cp, sha_buf_tmp, block);
 
 	/*
-	 * 17 Start digest DS
+	 * 17. Start digest DS
 	 */
 	px_md_reset(digestB);
 
 	/*-
-	 * 18 Repeat the following 16+A[0] times, where A[0] represents the first
+	 * 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
+	 * 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
+	 * 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
+	 *    entire digest DS is used
 	 *
 	 * b) for the remaining N (up to  31 or 63) bytes use the first N
 	 *    bytes of digest DS
@@ -516,27 +469,24 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		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.
+	 * 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.
+	 *     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.
@@ -546,29 +496,25 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		/* 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
+		/*-
+		 * b) for odd round numbers add the byte sequence 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)
-		{
+		if ((block % 3) != 0)
 			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)
-		{
+		if ((block % 7) != 0)
 			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
+		/*-
+		 * 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,
@@ -576,7 +522,6 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 		/* h) finish digest C. */
 		px_md_finish(digestB, sha_buf);
-
 	}
 
 	px_md_free(digestA);
@@ -594,15 +539,15 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	/* 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)                                     \
-		{                                                   \
+#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;                                        \
-		}                                                   \
+			w >>= 6;                                                     \
+		}                                                                \
 	} while (0)
 
 	switch (type)
@@ -655,7 +600,6 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		case PGCRYPTO_SHA_UNKOWN:
 			/* we shouldn't land here ... */
 			elog(ERROR, "unsupported digest length");
-
 	}
 
 	*cp = '\0';
@@ -690,7 +634,7 @@ error:
 		destroyStringInfo(out_buf);
 
 	ereport(ERROR,
-			(errcode(ERRCODE_INTERNAL_ERROR),
-			 errmsg("cannot create encrypted password")));
+			errcode(ERRCODE_INTERNAL_ERROR),
+			errmsg("cannot create encrypted password"));
 	return NULL;				/* keep compiler quiet */
 }
diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c
index a786277aeac..d7729eec9bc 100644
--- a/contrib/pgcrypto/px-crypt.c
+++ b/contrib/pgcrypto/px-crypt.c
@@ -139,10 +139,16 @@ 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_MAX_LEN,
-	PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
-	{"sha512crypt", _crypt_gensalt_sha512_rn, PX_SHACRYPT_SALT_MAX_LEN,
-	PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
+	{
+		"sha256crypt", _crypt_gensalt_sha256_rn,
+		PX_SHACRYPT_SALT_MAX_LEN, PX_SHACRYPT_ROUNDS_DEFAULT,
+		PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX
+	},
+	{
+		"sha512crypt", _crypt_gensalt_sha512_rn,
+		PX_SHACRYPT_SALT_MAX_LEN, 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 3829ec6c347..bb03cf5592a 100644
--- a/contrib/pgcrypto/px-crypt.h
+++ b/contrib/pgcrypto/px-crypt.h
@@ -61,13 +61,13 @@
 #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 5000l
+#define PX_SHACRYPT_ROUNDS_DEFAULT 5000
 
 /* Minimum number of rounds of shacrypt.  */
-#define PX_SHACRYPT_ROUNDS_MIN 1000l
+#define PX_SHACRYPT_ROUNDS_MIN 1000
 
 /* Maximum number of rounds of shacrypt.  */
-#define PX_SHACRYPT_ROUNDS_MAX 999999999l
+#define PX_SHACRYPT_ROUNDS_MAX 999999999
 
 /*
  * main interface
-- 
2.39.5

Attachments:

  [text/plain] 0001-pgindent.patch.txt (7.7K, ../../202504031839.ly54uxe6hqed@alvherre.pgsql/2-0001-pgindent.patch.txt)
  download | inline diff:
From 864395b6468d62f17d2e8f0e593408849564edc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@alvh.no-ip.org>
Date: Wed, 2 Apr 2025 17:25:20 +0200
Subject: [PATCH 1/2] pgindent

---
 contrib/pgcrypto/crypt-gensalt.c | 14 +++++------
 contrib/pgcrypto/crypt-sha.c     | 42 +++++++++++++++++---------------
 contrib/pgcrypto/px-crypt.c      |  6 ++---
 contrib/pgcrypto/px-crypt.h      |  2 +-
 4 files changed, 33 insertions(+), 31 deletions(-)

diff --git a/contrib/pgcrypto/crypt-gensalt.c b/contrib/pgcrypto/crypt-gensalt.c
index b9aded897d1..922f9eea6fa 100644
--- a/contrib/pgcrypto/crypt-gensalt.c
+++ b/contrib/pgcrypto/crypt-gensalt.c
@@ -190,9 +190,9 @@ static char *
 _crypt_gensalt_sha(unsigned long count,
 				   const char *input, int size, char *output, int output_size)
 {
-	char * s_ptr = output;
+	char	   *s_ptr = output;
 	unsigned int result_bufsize = PX_SHACRYPT_SALT_BUF_LEN;
-	int rc;
+	int			rc;
 
 	/* output buffer must be allocated with PX_MAX_SALT_LEN bytes */
 	if (PX_MAX_SALT_LEN < result_bufsize)
@@ -203,8 +203,8 @@ _crypt_gensalt_sha(unsigned long count,
 	}
 
 	/*
-	 * Care must be taken to not exceed the buffer size allocated for
-	 * the input character buffer.
+	 * Care must be taken to not exceed the buffer size allocated for the
+	 * input character buffer.
 	 */
 	if ((PX_SHACRYPT_SALT_MAX_LEN != size)
 		|| (output_size < size))
@@ -229,8 +229,8 @@ _crypt_gensalt_sha(unsigned long count,
 	/*
 	 * Normalize salt string
 	 *
-	 * size of input buffer was checked above to
-	 * not exceed PX_SHACRYPT_SALT_LEN_MAX.
+	 * size of input buffer was checked above to not exceed
+	 * PX_SHACRYPT_SALT_LEN_MAX.
 	 */
 	for (int i = 0; i < size; i++)
 	{
@@ -268,4 +268,4 @@ _crypt_gensalt_sha256_rn(unsigned long count,
 	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
index ec51e865a5e..13ee8913052 100644
--- a/contrib/pgcrypto/crypt-sha.c
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -77,24 +77,25 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	int			err;
 
 	const char *dec_salt_binary;	/* pointer into the real salt string */
-	StringInfo decoded_salt = NULL; /* decoded salt string */
+	StringInfo	decoded_salt = NULL;	/* decoded salt string */
 
 	unsigned char sha_buf[PX_SHACRYPT_DIGEST_MAX_LEN];
 	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];	/* temporary buffer for
-																 * digests */
+															 * digests */
 
 	char		rounds_custom = 0;
 	char	   *p_bytes = NULL;
 	char	   *s_bytes = NULL;
 	char	   *cp = NULL;
-	const char *fp = NULL;				/* intermediate pointer within salt string */
-	const char *ep = NULL;				/* holds pointer to the end of the salt string */
+	const char *fp = NULL;		/* intermediate pointer within salt string */
+	const char *ep = NULL;		/* 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 = 0;
+	unsigned	len,
+				salt_len = 0;
 
 	/* Init result buffer */
 	out_buf = makeStringInfoExt(PX_SHACRYPT_BUF_LEN);
@@ -299,16 +300,16 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 	/*
 	 * We need the real decoded salt string from salt input, this is every
-	 * character before the last '$' in the preamble. Append every
-	 * compatible character up to PX_SHACRYPT_SALT_MAX_LEN to the result buffer.
-	 * Note that depending on the input, there might be no '$' marker after
-	 * the salt, when there is no password hash attached at the end.
+	 * character before the last '$' in the preamble. Append every compatible
+	 * character up to PX_SHACRYPT_SALT_MAX_LEN to the result buffer. Note
+	 * that depending on the input, there might be no '$' marker after the
+	 * salt, when there is no password hash attached at the end.
 	 *
 	 * We try hard to recognize mistakes, but since we might get an input
 	 * string which might also have the password hash after the salt string
 	 * section we give up as soon we reach the end of the input or if there
-	 * are any bytes consumed for the salt string until we reach the
-	 * first '$' marker thereafter.
+	 * are any bytes consumed for the salt string until we reach the first '$'
+	 * marker thereafter.
 	 */
 	for (ep = dec_salt_binary;
 		 *ep && ep < (dec_salt_binary + PX_SHACRYPT_SALT_MAX_LEN);
@@ -320,9 +321,9 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		 * First check for accidentally embedded magic strings here. We don't
 		 * support '$' in salt strings anyways and seeing a magic byte trying
 		 * to identify shacrypt hashes might indicate that something went
-		 * wrong when generating this salt string. Note that we later check for
-		 * non-supported literals anyways, but any '$' here confuses us
-		 * at this point.
+		 * wrong when generating this salt string. Note that we later check
+		 * for non-supported literals anyways, but any '$' here confuses us at
+		 * this point.
 		 */
 		fp = strstr(dec_salt_binary, magic_bytes[0]);
 
@@ -362,13 +363,14 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		}
 		else
 		{
-			/* We encountered a '$' marker. Check if we already absorbed
-			 * some bytes from input. If true, we are optimistic and
-			 * terminate at this stage. Of not, we try further.
+			/*
+			 * We encountered a '$' marker. Check if we already absorbed some
+			 * bytes from input. If true, we are optimistic and terminate at
+			 * this stage. Of not, we try further.
 			 *
-			 * If we already consumed enough bytes for the salt string, everything
-			 * that is after this marker is considered to be part of an
-			 * optionally specified password hash and ignored.
+			 * If we already consumed enough bytes for the salt string,
+			 * everything that is after this marker is considered to be part
+			 * of an optionally specified password hash and ignored.
 			 */
 			if (decoded_salt->len > 0)
 				break;
diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c
index df9875b9433..a786277aeac 100644
--- a/contrib/pgcrypto/px-crypt.c
+++ b/contrib/pgcrypto/px-crypt.c
@@ -71,7 +71,7 @@ static char *
 run_crypt_sha(const char *psw, const char *salt,
 			  char *buf, unsigned len)
 {
-	char *res;
+	char	   *res;
 
 	res = px_crypt_shacrypt(psw, salt, buf, len);
 	return res;
@@ -140,9 +140,9 @@ static struct generator gen_list[] = {
 	{"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_MAX_LEN,
-	 PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
+	PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
 	{"sha512crypt", _crypt_gensalt_sha512_rn, PX_SHACRYPT_SALT_MAX_LEN,
-	 PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_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 9287c33efe1..3829ec6c347 100644
--- a/contrib/pgcrypto/px-crypt.h
+++ b/contrib/pgcrypto/px-crypt.h
@@ -108,6 +108,6 @@ 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);
+char	   *px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstlen);
 
 #endif							/* _PX_CRYPT_H */
-- 
2.39.5

  [text/plain] 0002-review.patch.txt (21.1K, ../../202504031839.ly54uxe6hqed@alvherre.pgsql/3-0002-review.patch.txt)
  download | inline diff:
From 567d4b3706295a1f2bed53eb0b2f4acb95898899 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@alvh.no-ip.org>
Date: Thu, 3 Apr 2025 20:14:52 +0200
Subject: [PATCH 2/2] review

---
 contrib/pgcrypto/crypt-gensalt.c |  26 ++--
 contrib/pgcrypto/crypt-sha.c     | 248 ++++++++++++-------------------
 contrib/pgcrypto/px-crypt.c      |  14 +-
 contrib/pgcrypto/px-crypt.h      |   6 +-
 4 files changed, 121 insertions(+), 173 deletions(-)

diff --git a/contrib/pgcrypto/crypt-gensalt.c b/contrib/pgcrypto/crypt-gensalt.c
index 922f9eea6fa..ef68ae0e49e 100644
--- a/contrib/pgcrypto/crypt-gensalt.c
+++ b/contrib/pgcrypto/crypt-gensalt.c
@@ -186,6 +186,9 @@ _crypt_gensalt_blowfish_rn(unsigned long count,
 	return output;
 }
 
+/*
+ * Helper for _crypt_gensalt_sha256_rn and _crypt_gensalt_sha512_rn
+ */
 static char *
 _crypt_gensalt_sha(unsigned long count,
 				   const char *input, int size, char *output, int output_size)
@@ -196,32 +199,25 @@ _crypt_gensalt_sha(unsigned long count,
 
 	/* 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")));
-	}
+				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_MAX_LEN != size)
-		|| (output_size < size))
-	{
+	if ((PX_SHACRYPT_SALT_MAX_LEN != size) || (output_size < size))
 		ereport(ERROR,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("invalid length of salt buffer")));
-	}
+				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")));
-	}
+				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;
@@ -242,6 +238,7 @@ _crypt_gensalt_sha(unsigned long count,
 	return output;
 }
 
+/* gen_list->gen function for sha512 */
 char *
 _crypt_gensalt_sha512_rn(unsigned long count,
 						 char const *input, int size,
@@ -256,6 +253,7 @@ _crypt_gensalt_sha512_rn(unsigned long count,
 	return _crypt_gensalt_sha(count, input, size, output, output_size);
 }
 
+/* gen_list->gen function for sha256 */
 char *
 _crypt_gensalt_sha256_rn(unsigned long count,
 						 const char *input, int size,
diff --git a/contrib/pgcrypto/crypt-sha.c b/contrib/pgcrypto/crypt-sha.c
index 13ee8913052..862f3fb9a68 100644
--- a/contrib/pgcrypto/crypt-sha.c
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -44,6 +44,8 @@
  *
  */
 #include "postgres.h"
+
+#include "common/string.h"
 #include "miscadmin.h"
 
 #include "px-crypt.h"
@@ -78,23 +80,20 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 	const char *dec_salt_binary;	/* pointer into the real salt string */
 	StringInfo	decoded_salt = NULL;	/* decoded salt string */
-
 	unsigned char sha_buf[PX_SHACRYPT_DIGEST_MAX_LEN];
-	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];	/* temporary buffer for
-															 * digests */
 
+	/* temporary buffer for digests */
+	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];
 	char		rounds_custom = 0;
 	char	   *p_bytes = NULL;
 	char	   *s_bytes = NULL;
 	char	   *cp = NULL;
 	const char *fp = NULL;		/* intermediate pointer within salt string */
 	const char *ep = NULL;		/* 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,
+	uint32		rounds = PX_SHACRYPT_ROUNDS_DEFAULT;
+	unsigned int len,
 				salt_len = 0;
 
 	/* Init result buffer */
@@ -106,22 +105,16 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		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));
@@ -138,27 +131,22 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * 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.
+	 * salt string.  Do some sanity checks first.
 	 */
 	if (strlen(dec_salt_binary) < 3)
-	{
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid salt")));
-	}
+				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] != '$'))
-	{
+	if ((dec_salt_binary[0] != '$') || (dec_salt_binary[2] != '$'))
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid format of salt")),
+				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.
@@ -190,62 +178,54 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * 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
+	 * Unknown magic byte is handled further 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);
+		int			srounds = strtoint(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
-		{
+		if (*endp != '$')
 			ereport(ERROR,
 					errcode(ERRCODE_SYNTAX_ERROR),
 					errmsg("could not parse salt options"));
+
+		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.
+		 */
+		if (srounds > PX_SHACRYPT_ROUNDS_MAX)
+		{
+			ereport(NOTICE,
+					errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					errmsg("rounds=%d exceeds maximum supported value (%d), using %d 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=%d is below supported value (%d), using %d instead",
+						   srounds, PX_SHACRYPT_ROUNDS_MIN,
+						   PX_SHACRYPT_ROUNDS_MIN));
+			srounds = PX_SHACRYPT_ROUNDS_MIN;
 		}
 
+		rounds = (uint32) srounds;
+		rounds_custom = 1;
 	}
 
 	/*
@@ -294,9 +274,7 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	}
 
 	if (rounds_custom > 0)
-	{
-		appendStringInfo(out_buf, "rounds=%lu$", rounds);
-	}
+		appendStringInfo(out_buf, "rounds=%u$", rounds);
 
 	/*
 	 * We need the real decoded salt string from salt input, this is every
@@ -326,47 +304,34 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		 * this point.
 		 */
 		fp = strstr(dec_salt_binary, magic_bytes[0]);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "bogus magic byte found in salt string");
-		}
 
 		fp = strstr(dec_salt_binary, magic_bytes[1]);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "bogus magic byte found in salt string");
-		}
 
 		/*
 		 * This looks very strict, but we assume the caller did something
 		 * wrong when we see a "rounds=" option here.
 		 */
 		fp = strstr(dec_salt_binary, rounds_prefix);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "invalid rounds option specified in salt string");
-		}
 
 		if (*ep != '$')
 		{
 			if (isalpha(*ep) || isdigit(*ep) || (*ep == '.') || (*ep == '/'))
-			{
 				appendStringInfoCharMacro(decoded_salt, *ep);
-			}
 			else
-			{
 				elog(ERROR, "invalid character in salt string: \"%c\"", *ep);
-			}
 		}
 		else
 		{
 			/*
 			 * We encountered a '$' marker. Check if we already absorbed some
 			 * bytes from input. If true, we are optimistic and terminate at
-			 * this stage. Of not, we try further.
+			 * this stage. If not, we try further.
 			 *
 			 * If we already consumed enough bytes for the salt string,
 			 * everything that is after this marker is considered to be part
@@ -375,23 +340,19 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 			if (decoded_salt->len > 0)
 				break;
 		}
-
 	}
 
 	salt_len = decoded_salt->len;
 	appendStringInfoString(out_buf, decoded_salt->data);
-	elog(DEBUG1, "using salt \"%s\", salt len = %d, rounds = %lu",
+	elog(DEBUG1, "using salt \"%s\", salt len = %d, rounds = %u",
 		 decoded_salt->data, decoded_salt->len, rounds);
 
 	/*
-	 * Sanity check:
-	 *
-	 * At this point the salt string buffer must not exceed expected size
+	 * 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
@@ -413,29 +374,26 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	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.
+	/*
+	 * 9. For each block (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 */
+	 * 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)
+	 * 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)
 	{
@@ -447,10 +405,10 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		block >>= 1;
 	}
 
-	/* 12 Finalize digest A */
+	/* 12. Finalize digest A */
 	px_md_finish(digestA, sha_buf);
 
-	/* 13 Start digest DP */
+	/* 13. Start digest DP */
 	px_md_reset(digestB);
 
 	/*-
@@ -458,20 +416,17 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * to the digest DP
 	 */
 	for (block = len; block > 0; block--)
-	{
 		px_md_update(digestB, (const unsigned char *) pw, len);
-	}
 
-	/* 15 Finalize digest DP */
+	/* 15. Finalize digest DP */
 	px_md_finish(digestB, sha_buf_tmp);
 
 	/*-
-	 * 16 produce byte sequence P with same length as password.
-	 *
+	 * 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
+	 *        first N bytes of digest DP
 	 */
 	if ((p_bytes = palloc0(len)) == NULL)
 	{
@@ -484,30 +439,28 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	memcpy(cp, sha_buf_tmp, block);
 
 	/*
-	 * 17 Start digest DS
+	 * 17. Start digest DS
 	 */
 	px_md_reset(digestB);
 
 	/*-
-	 * 18 Repeat the following 16+A[0] times, where A[0] represents the first
+	 * 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
+	 * 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
+	 * 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
+	 *    entire digest DS is used
 	 *
 	 * b) for the remaining N (up to  31 or 63) bytes use the first N
 	 *    bytes of digest DS
@@ -516,27 +469,24 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		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.
+	 * 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.
+	 *     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.
@@ -546,29 +496,25 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		/* 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
+		/*-
+		 * b) for odd round numbers add the byte sequence 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)
-		{
+		if ((block % 3) != 0)
 			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)
-		{
+		if ((block % 7) != 0)
 			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
+		/*-
+		 * 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,
@@ -576,7 +522,6 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 		/* h) finish digest C. */
 		px_md_finish(digestB, sha_buf);
-
 	}
 
 	px_md_free(digestA);
@@ -594,15 +539,15 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	/* 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)                                     \
-		{                                                   \
+#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;                                        \
-		}                                                   \
+			w >>= 6;                                                     \
+		}                                                                \
 	} while (0)
 
 	switch (type)
@@ -655,7 +600,6 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		case PGCRYPTO_SHA_UNKOWN:
 			/* we shouldn't land here ... */
 			elog(ERROR, "unsupported digest length");
-
 	}
 
 	*cp = '\0';
@@ -690,7 +634,7 @@ error:
 		destroyStringInfo(out_buf);
 
 	ereport(ERROR,
-			(errcode(ERRCODE_INTERNAL_ERROR),
-			 errmsg("cannot create encrypted password")));
+			errcode(ERRCODE_INTERNAL_ERROR),
+			errmsg("cannot create encrypted password"));
 	return NULL;				/* keep compiler quiet */
 }
diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c
index a786277aeac..d7729eec9bc 100644
--- a/contrib/pgcrypto/px-crypt.c
+++ b/contrib/pgcrypto/px-crypt.c
@@ -139,10 +139,16 @@ 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_MAX_LEN,
-	PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
-	{"sha512crypt", _crypt_gensalt_sha512_rn, PX_SHACRYPT_SALT_MAX_LEN,
-	PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
+	{
+		"sha256crypt", _crypt_gensalt_sha256_rn,
+		PX_SHACRYPT_SALT_MAX_LEN, PX_SHACRYPT_ROUNDS_DEFAULT,
+		PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX
+	},
+	{
+		"sha512crypt", _crypt_gensalt_sha512_rn,
+		PX_SHACRYPT_SALT_MAX_LEN, 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 3829ec6c347..bb03cf5592a 100644
--- a/contrib/pgcrypto/px-crypt.h
+++ b/contrib/pgcrypto/px-crypt.h
@@ -61,13 +61,13 @@
 #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 5000l
+#define PX_SHACRYPT_ROUNDS_DEFAULT 5000
 
 /* Minimum number of rounds of shacrypt.  */
-#define PX_SHACRYPT_ROUNDS_MIN 1000l
+#define PX_SHACRYPT_ROUNDS_MIN 1000
 
 /* Maximum number of rounds of shacrypt.  */
-#define PX_SHACRYPT_ROUNDS_MAX 999999999l
+#define PX_SHACRYPT_ROUNDS_MAX 999999999
 
 /*
  * main interface
-- 
2.39.5

^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-05 09:47  Alvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  1 sibling, 0 replies; 38+ messages in thread

From: Alvaro Herrera @ 2025-04-05 09:47 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Hello,

I triggered a run of this on CI on all platforms.  It seems to have gone
well, so unless I hear complaints, I intend to get this out later today.

https://cirrus-ci.com/build/4613871211642880

Thanks,

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-05 10:45  Bernd Helmle <mailings@oopsware.de>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  1 sibling, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-04-05 10:45 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Am Donnerstag, dem 03.04.2025 um 20:39 +0200 schrieb Alvaro Herrera:
> 
> > Maybe, in case of empty salts, we should issue a WARNING instead of
> > erroring out and put additional documentation on how to use it
> > right.
> 
> I don't know, that doesn't seem ideal to me, because it's very easy
> to
> run stuff and never see the warnings.  If we find that people are
> desperate to use empty salts, we can relax that later (turn the error
> to
> a warning), but I'd rather not have it in the first cut.
> 

That's a good idea, Let's go with that. Thanks again for working on
this.

> > Hmm, i didn't understand that passlib does decode them first, i
> > thought
> > they use it encoded... at least, in our current form we're pretty
> > much
> > compatible with Drepper, passlib and OpenSSL, as far as i tested:
> 
> I am ready to believe that I misinterpreted what I read.
> 

I hope i didn't parse it wrong either. But i didn't see forcing
something like this according in either passlib and Drepper's code.
Maybe we need have to look closer again ...

[...]

> 
> 
> I can offer a few cosmetic changes.  0001 is a pgindent run, and 0002
> is
> some manual adjustments after that.  There are only two nontrivial
> changes
> 
> 1. the calculation for rounds was using type long, which is confusing
> because the range is different according to the platform.  Since it's
> limited by the macro definitions to no more than 999999999, we can
> make
> it an int32 instead.  So we use strtoint() instead of strtoul() to
> parse
> the value, and remove the "l" suffixes from the macros that define
> the
> limits and default, which were bugging me a bit when used in the
> gen_list struct.

+1



-- 
Bernd Helmle

Blücherstrasse 17
41061 Mönchengladbach

Tel.: +49 172 726 99 66






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-05 17:22  Alvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 3 replies; 38+ messages in thread

From: Alvaro Herrera @ 2025-04-05 17:22 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Hello,

I have pushed this now, hoping it won't explode.

Thanks!

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/
"No es bueno caminar con un hombre muerto"





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-06 17:39  Bernd Helmle <mailings@oopsware.de>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  2 siblings, 0 replies; 38+ messages in thread

From: Bernd Helmle @ 2025-04-06 17:39 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Am Samstag, dem 05.04.2025 um 19:22 +0200 schrieb Alvaro Herrera:
> Hello,
> 
> I have pushed this now, hoping it won't explode.
> 
> Thanks!

Very cool, i keep my fingers crossed.

Thanks,
	Bernd






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-06 19:43  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  2 siblings, 2 replies; 38+ messages in thread

From: Tom Lane @ 2025-04-06 19:43 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Bernd Helmle <mailings@oopsware.de>; Japin Li <japinli@hotmail.com>; pgsql-hackers

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
> I have pushed this now, hoping it won't explode.

mamba is not happy:

ccache cc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -O2 -Werror -fPIC -DPIC -fvisibility=hidden -I. -I. -I../../src/include   -I/usr/pkg/include/libxml2 -I/usr/pkg/include -I/usr/pkg/include  -I/usr/pkg/include  -c -o pgcrypto.o pgcrypto.c
In file included from /usr/include/ctype.h:100,
                 from ../../src/include/port.h:16,
                 from ../../src/include/c.h:1345,
                 from ../../src/include/postgres.h:48,
                 from crypt-sha.c:46:
crypt-sha.c: In function 'px_crypt_shacrypt':
crypt-sha.c:324:16: error: array subscript has type 'char' [-Werror=char-subscripts]
  324 |    if (isalpha(*ep) || isdigit(*ep) || (*ep == '.') || (*ep == '/'))
      |                ^
crypt-sha.c:324:32: error: array subscript has type 'char' [-Werror=char-subscripts]
  324 |    if (isalpha(*ep) || isdigit(*ep) || (*ep == '.') || (*ep == '/'))
      |                                ^
cc1: all warnings being treated as errors
gmake[1]: *** [<builtin>: crypt-sha.o] Error 1

What this is on about is that portable use of isalpha() or isdigit()
requires casting a "char" value to "unsigned char".  I was about to
make that simple change when I started to question if we actually
want to be using <ctype.h> here at all.  Do you REALLY intend that
any random non-ASCII letter is okay to include in the decoded_salt?
Are you okay with that filter being locale-dependent?

I'd be more comfortable with a check like

	if (strchr("...valid chars...", *ep) != NULL)

It looks like "_crypt_itoa64" might be directly usable as the
valid-chars string, too.  (BTW, why is _crypt_itoa64 not
marked const?)

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-07 03:02  Andres Freund <andres@anarazel.de>
  parent: Alvaro Herrera <alvherre@alvh.no-ip.org>
  2 siblings, 1 reply; 38+ messages in thread

From: Andres Freund @ 2025-04-07 03:02 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Bernd Helmle <mailings@oopsware.de>; Japin Li <japinli@hotmail.com>; pgsql-hackers

Hi,

On 2025-04-05 19:22:58 +0200, Alvaro Herrera wrote:
> I have pushed this now, hoping it won't explode.

I have a WIP patch that adds gcc specific allocator attributes for palloc et
al. Just rebased that. It warns on the new code as follows:

[1489/1804 42  82%] Compiling C object contrib/pgcrypto/pgcrypto.so.p/crypt-sha.c.o
../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-sha.c: In function 'px_crypt_shacrypt':
../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-sha.c:605:13: warning: pointer 'cp' may be used after 'pfree' [-Wuse-after-free]
  605 |         *cp = '\0';
      |         ~~~~^~~~~~
../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-sha.c:533:9: note: call to 'pfree' here
  533 |         pfree(s_bytes);
      |         ^~~~~~~~~~~~~~

And it sure seems to have a point.  I'm surprised this isn't causing wider
issues...

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-07 07:03  Bernd Helmle <mailings@oopsware.de>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 38+ messages in thread

From: Bernd Helmle @ 2025-04-07 07:03 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Am Sonntag, dem 06.04.2025 um 15:43 -0400 schrieb Tom Lane:
> What this is on about is that portable use of isalpha() or isdigit()
> requires casting a "char" value to "unsigned char".  I was about to
> make that simple change when I started to question if we actually
> want to be using <ctype.h> here at all.  Do you REALLY intend that
> any random non-ASCII letter is okay to include in the decoded_salt?
> Are you okay with that filter being locale-dependent?
> 
> I'd be more comfortable with a check like
> 
> 	if (strchr("...valid chars...", *ep) != NULL)
> 

Hmm, the idea was to allow a wider range of letters for the salt. This
came from my experiments that openssl allows more than just the ones
from _crypt_itoa64. But after reading this, i realized even isalpha()
isn't enough, since e.g. mulitbyte character wouldn't work anyways,
like openssl allows:

echo -n password | openssl passwd -5 -salt ÄÖÜ -stdin
$5$ÄÖÜ$NduBUgCdzvnW1lW8EMxW9DuxN6HmT0niS7H4ftRxuX0

So we would have to test for these cases, too. I looked again into
pyhon's passlib, and they don't accept any non-ASCII fancy characters,
neither.

So i think Tom has a point, we can restrict this to the characters from
_crypt_itoa64 and go that route. I am not sure about externally
generated hashes which are going to be stored in postgres and validated
later there. This can restrict the use case perhaps.

> It looks like "_crypt_itoa64" might be directly usable as the
> valid-chars string, too.  (BTW, why is _crypt_itoa64 not
> marked const?)

That's an oversight by me.

I can create a patch with the fixes (and the one Andres' reported) for
today.

Thanks

	Bernd






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-07 07:09  Bernd Helmle <mailings@oopsware.de>
  parent: Andres Freund <andres@anarazel.de>
  0 siblings, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-04-07 07:09 UTC (permalink / raw)
  To: Andres Freund <andres@anarazel.de>; Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Am Sonntag, dem 06.04.2025 um 23:02 -0400 schrieb Andres Freund:
> Hi,
> 
> On 2025-04-05 19:22:58 +0200, Alvaro Herrera wrote:
> > I have pushed this now, hoping it won't explode.
> 
> I have a WIP patch that adds gcc specific allocator attributes for
> palloc et
> al. Just rebased that. It warns on the new code as follows:
> 
> [1489/1804 42  82%] Compiling C object
> contrib/pgcrypto/pgcrypto.so.p/crypt-sha.c.o
> ../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-
> sha.c: In function 'px_crypt_shacrypt':
> ../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-
> sha.c:605:13: warning: pointer 'cp' may be used after 'pfree' [-Wuse-
> after-free]
>   605 |         *cp = '\0';
>       |         ~~~~^~~~~~
> ../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-
> sha.c:533:9: note: call to 'pfree' here
>   533 |         pfree(s_bytes);
>       |         ^~~~~~~~~~~~~~
> 
> And it sure seems to have a point.  I'm surprised this isn't causing
> wider
> issues...

Indeed. I think this assignment is useless anyways, since s_bytes is
already allocated with palloc0. I must have overseen this one when
rearranging code...but yes, strange that it didn't cause drama.


Thanks
	Bernd






^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-07 11:48  Bernd Helmle <mailings@oopsware.de>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 38+ messages in thread

From: Bernd Helmle @ 2025-04-07 11:48 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Alvaro Herrera <alvherre@alvh.no-ip.org>; Andres Freund <andres@anarazel.de>; +Cc: Japin Li <japinli@hotmail.com>; pgsql-hackers

Am Sonntag, dem 06.04.2025 um 15:43 -0400 schrieb Tom Lane:
> I'd be more comfortable with a check like
> 
> 	if (strchr("...valid chars...", *ep) != NULL)
> 
> It looks like "_crypt_itoa64" might be directly usable as the
> valid-chars string, too.  (BTW, why is _crypt_itoa64 not
> marked const?)

Here is a patch that tries to address all these issues (including
Andres' report). I've adjusted the error message and use ereport(), so
it might be more useful if we deal with not just single byte letters.

I've also changed _crypt_itoa64 from unsigned char to char, since this
seems what strchr() expects (at least on my machine) and we don't deal
specifically elsewhere with that.


Thanks,
	Bernd

Attachments:

  [text/x-patch] 0001-Follow-up-fixes-for-commit-749a9e20c9790006f3af47f7a.patch (1.8K, ../../6fab35422df6b6b9727fdcc243c5fa1c667dd3b5.camel@oopsware.de/2-0001-Follow-up-fixes-for-commit-749a9e20c9790006f3af47f7a.patch)
  download | inline diff:
From d9aa1524a8280bb3997998c2a3499b1f8cfdee6c Mon Sep 17 00:00:00 2001
From: Bernd Helmle <Bernd Helmle mailings@oopsware.de>
Date: Mon, 7 Apr 2025 13:45:12 +0200
Subject: [PATCH] Follow up fixes for commit
 749a9e20c9790006f3af47f7a8faf4ad8dc358d9.

This removes the platform dependant use of isalpha() and friends and replaces
the check for supported letters in the salt string to just check against
plain ASCII letters. While this makes the use of salt letters more strict,
we expect this to make the code more robust.

This fixes a careless use-pointer-after-pfree spotted by Andres Freund, too.
---
 contrib/pgcrypto/crypt-sha.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/contrib/pgcrypto/crypt-sha.c b/contrib/pgcrypto/crypt-sha.c
index 7e9440ca784..bcf5b480732 100644
--- a/contrib/pgcrypto/crypt-sha.c
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -58,7 +58,7 @@ typedef enum
 	PGCRYPTO_SHA_UNKOWN
 } PGCRYPTO_SHA_t;
 
-static unsigned char _crypt_itoa64[64 + 1] =
+static const char _crypt_itoa64[64 + 1] =
 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
 /*
@@ -321,10 +321,12 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 		if (*ep != '$')
 		{
-			if (isalpha(*ep) || isdigit(*ep) || (*ep == '.') || (*ep == '/'))
+			if (strchr(_crypt_itoa64, *ep) != NULL)
 				appendStringInfoCharMacro(decoded_salt, *ep);
 			else
-				elog(ERROR, "invalid character in salt string: \"%c\"", *ep);
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid character in salt string at: \"%s\"", ep));
 		}
 		else
 		{
@@ -602,8 +604,6 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 			elog(ERROR, "unsupported digest length");
 	}
 
-	*cp = '\0';
-
 	/*
 	 * Copy over result to specified buffer.
 	 *
-- 
2.49.0



^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-07 17:50  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 2 replies; 38+ messages in thread

From: Tom Lane @ 2025-04-07 17:50 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>; Andres Freund <andres@anarazel.de>; Japin Li <japinli@hotmail.com>; pgsql-hackers

Bernd Helmle <mailings@oopsware.de> writes:
> Here is a patch that tries to address all these issues (including
> Andres' report). I've adjusted the error message and use ereport(), so
> it might be more useful if we deal with not just single byte letters.

I'd like to get mamba back to green, so I'll take care of this
(unless Alvaro is already on it?)

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-07 18:00  Andres Freund <andres@anarazel.de>
  parent: Bernd Helmle <mailings@oopsware.de>
  0 siblings, 0 replies; 38+ messages in thread

From: Andres Freund @ 2025-04-07 18:00 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>; Japin Li <japinli@hotmail.com>; pgsql-hackers

Hi,

On 2025-04-07 09:09:39 +0200, Bernd Helmle wrote:
> Am Sonntag, dem 06.04.2025 um 23:02 -0400 schrieb Andres Freund:
> > On 2025-04-05 19:22:58 +0200, Alvaro Herrera wrote:
> > > I have pushed this now, hoping it won't explode.
> > 
> > I have a WIP patch that adds gcc specific allocator attributes for
> > palloc et
> > al. Just rebased that. It warns on the new code as follows:
> > 
> > [1489/1804 42  82%] Compiling C object
> > contrib/pgcrypto/pgcrypto.so.p/crypt-sha.c.o
> > ../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-
> > sha.c: In function 'px_crypt_shacrypt':
> > ../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-
> > sha.c:605:13: warning: pointer 'cp' may be used after 'pfree' [-Wuse-
> > after-free]
> >   605 |         *cp = '\0';
> >       |         ~~~~^~~~~~
> > ../../../../../home/andres/src/postgresql/contrib/pgcrypto/crypt-
> > sha.c:533:9: note: call to 'pfree' here
> >   533 |         pfree(s_bytes);
> >       |         ^~~~~~~~~~~~~~
> > 
> > And it sure seems to have a point.  I'm surprised this isn't causing
> > wider
> > issues...
> 
> Indeed. I think this assignment is useless anyways, since s_bytes is
> already allocated with palloc0. I must have overseen this one when
> rearranging code...but yes, strange that it didn't cause drama.

Valgrind did find it actually, I just missed it due to other failures:

https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=skink&dt=2025-04-05%2020%3A52%3A38...

==2917549== VALGRINDERROR-BEGIN
==2917549== Invalid write of size 1
==2917549==    at 0x11D4AD9C: px_crypt_shacrypt (crypt-sha.c:605)
==2917549==    by 0x11D54B50: run_crypt_sha (px-crypt.c:76)
==2917549==    by 0x11D54BCB: px_crypt (px-crypt.c:119)
==2917549==    by 0x11D4BCBB: pg_crypt (pgcrypto.c:228)
==2917549==    by 0x3BBCEA: ExecInterpExpr (execExprInterp.c:1001)
==2917549==    by 0x3B790E: ExecInterpExprStillValid (execExprInterp.c:2299)
==2917549==    by 0x48FD40: ExecEvalExprSwitchContext (executor.h:466)
==2917549==    by 0x48FD40: evaluate_expr (clauses.c:5012)
==2917549==    by 0x48FF08: evaluate_function (clauses.c:4519)
==2917549==    by 0x492054: simplify_function (clauses.c:4108)
==2917549==    by 0x4901FA: eval_const_expressions_mutator (clauses.c:2593)
==2917549==    by 0x432CDE: expression_tree_mutator_impl (nodeFuncs.c:3486)
==2917549==    by 0x48FFB9: eval_const_expressions_mutator (clauses.c:3727)
==2917549==  Address 0x82fa740 is 912 bytes inside a recently re-allocated block of size 8,192 alloc'd
==2917549==    at 0x4844818: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==2917549==    by 0x71EF75: AllocSetContextCreateInternal (aset.c:444)
==2917549==    by 0x3D17AE: CreateExprContextInternal (execUtils.c:260)
==2917549==    by 0x3D1CA8: CreateExprContext (execUtils.c:310)
==2917549==    by 0x3D1ED8: MakePerTupleExprContext (execUtils.c:462)
==2917549==    by 0x48FDBF: evaluate_expr (clauses.c:5013)
==2917549==    by 0x48FF08: evaluate_function (clauses.c:4519)
==2917549==    by 0x492054: simplify_function (clauses.c:4108)
==2917549==    by 0x4901FA: eval_const_expressions_mutator (clauses.c:2593)
==2917549==    by 0x432CDE: expression_tree_mutator_impl (nodeFuncs.c:3486)
==2917549==    by 0x48FFB9: eval_const_expressions_mutator (clauses.c:3727)
==2917549==    by 0x432EFD: expression_tree_mutator_impl (nodeFuncs.c:3572)
==2917549== 
==2917549== VALGRINDERROR-END
{
   <insert_a_suppression_name_here>
   Memcheck:Addr1
   fun:px_crypt_shacrypt
   fun:run_crypt_sha
   fun:px_crypt
   fun:pg_crypt
   fun:ExecInterpExpr
   fun:ExecInterpExprStillValid
   fun:ExecEvalExprSwitchContext
   fun:evaluate_expr
   fun:evaluate_function
   fun:simplify_function
   fun:eval_const_expressions_mutator
   fun:expression_tree_mutator_impl
   fun:eval_const_expressions_mutator
}
**2917549** Valgrind detected 1 error(s) during execution of "SELECT crypt('', '$5$Szzz0yzz');"

Greetings,

Andres





^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-07 18:15  Álvaro Herrera <alvherre@alvh.no-ip.org>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 38+ messages in thread

From: Álvaro Herrera @ 2025-04-07 18:15 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Bernd Helmle <mailings@oopsware.de>; +Cc: Andres Freund <andres@anarazel.de>; Japin Li <japinli@hotmail.com>; pgsql-hackers



On Mon, Apr 7, 2025, at 7:50 PM, Tom Lane wrote:
> I'd like to get mamba back to green, so I'll take care of this
> (unless Alvaro is already on it?)

Please feel free, thank you.

^ permalink  raw  reply  [nested|flat] 38+ messages in thread

* Re: Modern SHA2- based password hashes for pgcrypto
@ 2025-04-07 18:16  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 38+ messages in thread

From: Tom Lane @ 2025-04-07 18:16 UTC (permalink / raw)
  To: Bernd Helmle <mailings@oopsware.de>; +Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>; Andres Freund <andres@anarazel.de>; Japin Li <japinli@hotmail.com>; pgsql-hackers

I wrote:
> Bernd Helmle <mailings@oopsware.de> writes:
>> Here is a patch that tries to address all these issues (including
>> Andres' report). I've adjusted the error message and use ereport(), so
>> it might be more useful if we deal with not just single byte letters.

> I'd like to get mamba back to green, so I'll take care of this

And done.  FYI, we actually have a standard practice for error
messages that want to complain about a single character without
assuming it's a single-byte character:

                ereport(ERROR,
                        errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                        errmsg("invalid character in salt string: \"%.*s\"",
                               pg_mblen(ep), ep));

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 38+ messages in thread


end of thread, other threads:[~2025-04-07 18:16 UTC | newest]

Thread overview: 38+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-12-31 16:06 Modern SHA2- based password hashes for pgcrypto Bernd Helmle <mailings@oopsware.de>
2024-12-31 16:36 ` Bernd Helmle <mailings@oopsware.de>
2025-01-02 14:57 ` Daniel Gustafsson <daniel@yesql.se>
2025-01-02 15:17   ` Bernd Helmle <mailings@oopsware.de>
2025-01-02 15:28     ` Daniel Gustafsson <daniel@yesql.se>
2025-01-03 15:58 ` Japin Li <japinli@hotmail.com>
2025-01-03 16:55 ` Bernd Helmle <mailings@oopsware.de>
2025-01-04 00:19   ` Japin Li <japinli@hotmail.com>
2025-01-07 17:48     ` Bernd Helmle <mailings@oopsware.de>
2025-01-13 16:06       ` Alvaro Herrera <alvherre@alvh.no-ip.org>
2025-01-14 09:34         ` Bernd Helmle <mailings@oopsware.de>
2025-01-14 10:47           ` Alvaro Herrera <alvherre@alvh.no-ip.org>
2025-01-14 11:41             ` Bernd Helmle <mailings@oopsware.de>
2025-01-20 17:41         ` Bernd Helmle <mailings@oopsware.de>
2025-01-23 13:36           ` Japin Li <japinli@hotmail.com>
2025-01-24 17:14             ` Bernd Helmle <mailings@oopsware.de>
2025-01-24 18:06               ` Alvaro Herrera <alvherre@alvh.no-ip.org>
2025-01-27 04:28                 ` Japin Li <japinli@hotmail.com>
2025-01-28 10:25                 ` Bernd Helmle <mailings@oopsware.de>
2025-02-06 10:20                   ` Alvaro Herrera <alvherre@alvh.no-ip.org>
2025-02-07 08:17                     ` Japin Li <japinli@hotmail.com>
2025-02-07 09:31                       ` Alvaro Herrera <alvherre@alvh.no-ip.org>
2025-02-08 02:16                         ` Japin Li <japinli@hotmail.com>
2025-03-11 16:58                         ` Bernd Helmle <mailings@oopsware.de>
2025-04-03 18:39                           ` Alvaro Herrera <alvherre@alvh.no-ip.org>
2025-04-05 09:47                             ` Alvaro Herrera <alvherre@alvh.no-ip.org>
2025-04-05 10:45                             ` Bernd Helmle <mailings@oopsware.de>
2025-04-05 17:22                               ` Alvaro Herrera <alvherre@alvh.no-ip.org>
2025-04-06 17:39                                 ` Bernd Helmle <mailings@oopsware.de>
2025-04-06 19:43                                 ` Tom Lane <tgl@sss.pgh.pa.us>
2025-04-07 07:03                                   ` Bernd Helmle <mailings@oopsware.de>
2025-04-07 11:48                                   ` Bernd Helmle <mailings@oopsware.de>
2025-04-07 17:50                                     ` Tom Lane <tgl@sss.pgh.pa.us>
2025-04-07 18:15                                       ` Álvaro Herrera <alvherre@alvh.no-ip.org>
2025-04-07 18:16                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2025-04-07 03:02                                 ` Andres Freund <andres@anarazel.de>
2025-04-07 07:09                                   ` Bernd Helmle <mailings@oopsware.de>
2025-04-07 18:00                                     ` Andres Freund <andres@anarazel.de>

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox