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

From 131b03729123967b93925d7f94bc04d5b5ab347c Mon Sep 17 00:00:00 2001
From: Bernd Helmle <Bernd Helmle maili...@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

Reply via email to