From 556a1b30e889163ee76c8774a88d58b5de541151 Mon Sep 17 00:00:00 2001 From: "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 11c4455dd059..69afa3750116 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 740f3612532b..b9aded897d1d 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 000000000000..ec51e865a5e9 --- /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= + * 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 000000000000..f3b76d5e9136 --- /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 7a4e8e76d64c..7d5ef9b6d323 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 96ce9384aff4..df9875b94330 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 54de80696553..9287c33efe16 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 000000000000..3ee826f61aa6 --- /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 f87668dfaede..b567b5c16e89 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 13 Original UNIX crypt + + sha256crypt + unlimited + yes + up to 32 + 80 + Adapted from publicly available reference implementation + Unix crypt using SHA-256 and SHA-512 + + + + + sha512crypt + unlimited + yes + up to 32 + 123 + Adapted from publicly available reference implementation + Unix crypt using SHA-256 and SHA-512 + + + + @@ -245,7 +268,9 @@ gen_salt(type text [, iter_count integer ]) returns text The type parameter specifies the hashing algorithm. The accepted types are: des, xdes, - md5 and bf. + md5, bf, sha256crypt and + sha512crypt. The last two, sha256crypt and + sha512crypt are modern SHA-2 based password hashes. @@ -284,6 +309,12 @@ gen_salt(type text [, iter_count integer ]) returns text 4 31 + + sha256crypt, sha512crypt + 5000 + 1000 + 999999999 + @@ -313,6 +344,14 @@ gen_salt(type text [, iter_count integer ]) returns text gen_salt. + + The default iter_count for sha256crypt and + sha512crypt of 5000 is considered too low for modern + hardware, but can be adjusted to generate stronger password hashes. + Otherwise both hashes, sha256crypt and sha512crypt are + considered safe. + + Hash Algorithm Speeds