Add modern SHA-2 based password hashes to pgcrypto.
authorÁlvaro Herrera <[email protected]>
Sat, 5 Apr 2025 17:16:58 +0000 (19:16 +0200)
committerÁlvaro Herrera <[email protected]>
Sat, 5 Apr 2025 17:17:13 +0000 (19:17 +0200)
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.

Author: Bernd Helmle <[email protected]>
Reviewed-by: Japin Li <[email protected]>
Discussion: https://postgr.es/m/c763235a2757e2f5f9e3e27268b9028349cef659[email protected]

contrib/pgcrypto/Makefile
contrib/pgcrypto/crypt-gensalt.c
contrib/pgcrypto/crypt-sha.c [new file with mode: 0644]
contrib/pgcrypto/expected/crypt-shacrypt.out [new file with mode: 0644]
contrib/pgcrypto/meson.build
contrib/pgcrypto/px-crypt.c
contrib/pgcrypto/px-crypt.h
contrib/pgcrypto/sql/crypt-shacrypt.sql [new file with mode: 0644]
doc/src/sgml/pgcrypto.sgml

index 11c4455dd059ea388541b6cd9874c22bc0278021..69afa3750116c5ca8a50eca019d8bffd0d08c2f4 100644 (file)
@@ -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
index 740f3612532b0719985beb0629d9b2d769f6170f..ef68ae0e49e52736dc6d19d210177b2225e65997 100644 (file)
@@ -185,3 +185,85 @@ _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)
+{
+   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;
+}
+
+/* gen_list->gen function for sha512 */
+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);
+}
+
+/* gen_list->gen function for sha256 */
+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);
+}
diff --git a/contrib/pgcrypto/crypt-sha.c b/contrib/pgcrypto/crypt-sha.c
new file mode 100644 (file)
index 0000000..862f3fb
--- /dev/null
@@ -0,0 +1,640 @@
+/*
+ * 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 "common/string.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];
+
+   /* 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 */
+   uint32      rounds = PX_SHACRYPT_ROUNDS_DEFAULT;
+   unsigned int 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.  Do some sanity checks first.
+    */
+   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 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;
+       int         srounds = strtoint(num, &endp, 10);
+
+       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;
+   }
+
+   /*
+    * 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=%u$", 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. 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
+            * 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 = %u",
+        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 (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 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) != 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) != 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
+        */
+       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 (file)
index 0000000..f3b76d5
--- /dev/null
@@ -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;
index 7a4e8e76d64c20e8a5810b7c14180f93dec3ccd3..7d5ef9b6d32313242406eab2c3ecee866685acc2 100644 (file)
@@ -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(
index 96ce9384aff43c5c623c34b16d1403a6801d5b89..d7729eec9bcaeaed88575482789ed33d9a3154cd 100644 (file)
@@ -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,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
+   },
    {NULL, NULL, 0, 0, 0, 0}
 };
 
index 54de806965530ebd6fccf32052c16ec18a23ac95..bb03cf5592ad04fd6f30b4c6797bd2c2b3c43b7d 100644 (file)
 /* 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 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 (file)
index 0000000..3ee826f
--- /dev/null
@@ -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;
index f87668dfaede155bd63f7eea305b5e8aae5c8077..b567b5c16e8945257ffc284659d171b4b07079f5 100644 (file)
@@ -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">