diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 481f93cea1b..71730cc52fe 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1132,6 +1132,26 @@ include_dir 'conf.d'
+
+ scram_iterations (integer)
+
+ scram_iterations configuration parameter
+
+
+
+
+ The number of computational iterations to be performed when encrypting
+ a password using SCRAM-SHA-256. The default is 4096.
+ A higher number of iterations provides additional protection against
+ brute-force attacks on stored passwords, but makes authentication
+ slower. Changing the value has no effect on existing passwords
+ encrypted with SCRAM-SHA-256 as the iteration count is fixed at the
+ time of encryption. In order to make use of a changed value, a new
+ password must be set.
+
+
+
+
krb_server_keyfile (string)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 4441e0d7745..9b286aa4d7f 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -191,6 +191,11 @@ static char *scram_mock_salt(const char *username,
pg_cryptohash_type hash_type,
int key_length);
+/*
+ * The number of iterations to use when generating new secrets.
+ */
+int scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
+
/*
* Get a list of SASL mechanisms that this module supports.
*
@@ -496,7 +501,7 @@ pg_be_scram_build_secret(const char *password)
result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN,
saltbuf, SCRAM_DEFAULT_SALT_LEN,
- SCRAM_DEFAULT_ITERATIONS, password,
+ scram_sha_256_iterations, password,
&errstr);
if (prep_password)
@@ -717,7 +722,7 @@ mock_scram_secret(const char *username, pg_cryptohash_type *hash_type,
encoded_salt[encoded_len] = '\0';
*salt = encoded_salt;
- *iterations = SCRAM_DEFAULT_ITERATIONS;
+ *iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
/* StoredKey and ServerKey are not used in a doomed authentication */
memset(stored_key, 0, SCRAM_MAX_KEY_LEN);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe267..a60bd48499e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -41,9 +41,11 @@
#include "commands/trigger.h"
#include "commands/user.h"
#include "commands/vacuum.h"
+#include "common/scram-common.h"
#include "jit/jit.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
+#include "libpq/scram.h"
#include "nodes/queryjumble.h"
#include "optimizer/cost.h"
#include "optimizer/geqo.h"
@@ -3468,6 +3470,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"scram_iterations", PGC_USERSET, CONN_AUTH_AUTH,
+ gettext_noop("Sets the iteration count for SCRAM secret generation."),
+ NULL,
+ GUC_REPORT
+ },
+ &scram_sha_256_iterations,
+ SCRAM_SHA_256_DEFAULT_ITERATIONS, 1, INT_MAX,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f6..fc831565d90 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -95,6 +95,7 @@
#authentication_timeout = 1min # 1s-600s
#password_encryption = scram-sha-256 # scram-sha-256 or md5
+#scram_iterations = 4096
#db_user_namespace = off
# GSSAPI using Kerberos
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index bd40d497a96..ef997ef6849 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -214,8 +214,7 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length,
/* Only this hash method is supported currently */
Assert(hash_type == PG_SHA256);
- if (iterations <= 0)
- iterations = SCRAM_DEFAULT_ITERATIONS;
+ Assert(iterations > 0);
/* Calculate StoredKey and ServerKey */
if (scram_SaltedPassword(password, hash_type, key_length,
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index 0c75df55597..5ccff96ecee 100644
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -47,7 +47,7 @@
* Default number of iterations when generating secret. Should be at least
* 4096 per RFC 7677.
*/
-#define SCRAM_DEFAULT_ITERATIONS 4096
+#define SCRAM_SHA_256_DEFAULT_ITERATIONS 4096
extern int scram_SaltedPassword(const char *password,
pg_cryptohash_type hash_type, int key_length,
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index b275e1e87e5..310bc365177 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -18,6 +18,9 @@
#include "libpq/libpq-be.h"
#include "libpq/sasl.h"
+/* Number of iterations when generating new secrets */
+extern PGDLLIMPORT int scram_sha_256_iterations;
+
/* SASL implementation callbacks */
extern PGDLLIMPORT const pg_be_sasl_mech pg_be_scram_mech;
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 277f72b280c..6b779ec7ffd 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -895,7 +895,7 @@ verify_server_signature(fe_scram_state *state, bool *match,
* error details.
*/
char *
-pg_fe_scram_build_secret(const char *password, const char **errstr)
+pg_fe_scram_build_secret(const char *password, int iterations, const char **errstr)
{
char *prep_password;
pg_saslprep_rc rc;
@@ -927,7 +927,7 @@ pg_fe_scram_build_secret(const char *password, const char **errstr)
result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, saltbuf,
SCRAM_DEFAULT_SALT_LEN,
- SCRAM_DEFAULT_ITERATIONS, password,
+ iterations, password,
errstr);
free(prep_password);
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 934e3f4f7ca..b0550e63324 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -1341,7 +1341,9 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
{
const char *errstr = NULL;
- crypt_pwd = pg_fe_scram_build_secret(passwd, &errstr);
+ crypt_pwd = pg_fe_scram_build_secret(passwd,
+ conn->scram_sha_256_iterations,
+ &errstr);
if (!crypt_pwd)
libpq_append_conn_error(conn, "could not encrypt password: %s", errstr);
}
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 1aa556ea2fa..124dd5d0313 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -26,6 +26,7 @@ extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
/* Mechanisms in fe-auth-scram.c */
extern const pg_fe_sasl_mech pg_scram_mech;
extern char *pg_fe_scram_build_secret(const char *password,
+ int iterations,
const char **errstr);
#endif /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 660775e0198..b71378d94c5 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -596,6 +596,7 @@ pqDropServerData(PGconn *conn)
conn->std_strings = false;
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
conn->in_hot_standby = PG_BOOL_UNKNOWN;
+ conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
conn->sversion = 0;
/* Drop large-object lookup data */
@@ -4182,6 +4183,7 @@ makeEmptyPGconn(void)
conn->std_strings = false; /* unless server says differently */
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
conn->in_hot_standby = PG_BOOL_UNKNOWN;
+ conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index ec62550e385..a16bbf32ef5 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1181,6 +1181,10 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
conn->in_hot_standby =
(strcmp(value, "on") == 0) ? PG_BOOL_YES : PG_BOOL_NO;
}
+ else if (strcmp(name, "scram_iterations") == 0)
+ {
+ conn->scram_sha_256_iterations = atoi(value);
+ }
}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 10187c31b9a..88b9838d766 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -525,6 +525,7 @@ struct pg_conn
/* Assorted state for SASL, SSL, GSS, etc */
const pg_fe_sasl_mech *sasl;
void *sasl_state;
+ int scram_sha_256_iterations;
/* SSL structures */
bool ssl_in_use;
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index cba5d7d6487..00857fdae5a 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -86,6 +86,21 @@ $node->safe_psql('postgres',
q{SET password_encryption='md5'; CREATE ROLE "md5,role" LOGIN PASSWORD 'pass';}
);
+# Create a role with a non-default iteration count
+$node->safe_psql(
+ 'postgres',
+ "SET password_encryption='scram-sha-256';
+ SET scram_iterations=1024;
+ CREATE ROLE scram_role_iter LOGIN PASSWORD 'pass';
+ RESET scram_iterations;"
+);
+
+my $res = $node->safe_psql('postgres',
+ "SELECT substr(rolpassword,1,19)
+ FROM pg_authid
+ WHERE rolname = 'scram_role_iter'");
+is($res, 'SCRAM-SHA-256$1024:', 'scram_iterations in server side ROLE');
+
# Create a database to test regular expression.
$node->safe_psql('postgres', "CREATE database regex_testdb;");
@@ -98,7 +113,7 @@ test_conn($node, 'user=md5_role', 'trust', 0,
log_unlike => [qr/connection authenticated:/]);
# SYSTEM_USER is null when not authenticated.
-my $res = $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+$res = $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
is($res, 't', "users with trust authentication use SYSTEM_USER = NULL");
# Test SYSTEM_USER with parallel workers when not authenticated.
@@ -283,6 +298,14 @@ test_conn(
log_like => [
qr/connection authenticated: identity="scram_role" method=scram-sha-256/
]);
+test_conn(
+ $node,
+ 'user=scram_role_iter',
+ 'scram-sha-256',
+ 0,
+ log_like => [
+ qr/connection authenticated: identity="scram_role_iter" method=scram-sha-256/
+ ]);
test_conn($node, 'user=md5_role', 'scram-sha-256', 2,
log_unlike => [qr/connection authenticated:/]);
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 7c84c9da337..84752317356 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -72,6 +72,9 @@ CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234';
CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
-- invalid length
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
+-- Changing the SCRAM iteration count
+SET scram_iterations = 1024;
+CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount';
SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:$:') as rolpassword_masked
FROM pg_authid
WHERE rolname LIKE 'regress_passwd%'
@@ -86,7 +89,8 @@ SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+
regress_passwd6 | SCRAM-SHA-256$4096:$:
regress_passwd7 | SCRAM-SHA-256$4096:$:
regress_passwd8 | SCRAM-SHA-256$4096:$:
-(8 rows)
+ regress_passwd9 | SCRAM-SHA-256$1024:$:
+(9 rows)
-- An empty password is not allowed, in any form
CREATE ROLE regress_passwd_empty PASSWORD '';
@@ -129,6 +133,7 @@ DROP ROLE regress_passwd5;
DROP ROLE regress_passwd6;
DROP ROLE regress_passwd7;
DROP ROLE regress_passwd8;
+DROP ROLE regress_passwd9;
DROP ROLE regress_passwd_empty;
DROP ROLE regress_passwd_sha_len0;
DROP ROLE regress_passwd_sha_len1;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 98f49916e5d..53e86b0b6ce 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -63,6 +63,10 @@ CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
-- invalid length
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
+-- Changing the SCRAM iteration count
+SET scram_iterations = 1024;
+CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount';
+
SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:$:') as rolpassword_masked
FROM pg_authid
WHERE rolname LIKE 'regress_passwd%'
@@ -97,6 +101,7 @@ DROP ROLE regress_passwd5;
DROP ROLE regress_passwd6;
DROP ROLE regress_passwd7;
DROP ROLE regress_passwd8;
+DROP ROLE regress_passwd9;
DROP ROLE regress_passwd_empty;
DROP ROLE regress_passwd_sha_len0;
DROP ROLE regress_passwd_sha_len1;