Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit b577743

Browse files
Make SCRAM iteration count configurable
Replace the hardcoded value with a GUC such that the iteration count can be raised in order to increase protection against brute-force attacks. The hardcoded value for SCRAM iteration count was defined to be 4096, which is taken from RFC 7677, so set the default for the GUC to 4096 to match. In RFC 7677 the recommendation is at least 15000 iterations but 4096 is listed as a SHOULD requirement given that it's estimated to yield a 0.5s processing time on a mobile handset of the time of RFC writing (late 2015). Raising the iteration count of SCRAM will make stored passwords more resilient to brute-force attacks at a higher computational cost during connection establishment. Lowering the count will reduce computational overhead during connections at the tradeoff of reducing strength against brute-force attacks. There are however platforms where even a modest iteration count yields a too high computational overhead, with weaker password encryption schemes chosen as a result. In these situations, SCRAM with a very low iteration count still gives benefits over weaker schemes like md5, so we allow the iteration count to be set to one at the low end. The new GUC is intentionally generically named such that it can be made to support future SCRAM standards should they emerge. At that point the value can be made into key:value pairs with an undefined key as a default which will be backwards compatible with this. Reviewed-by: Michael Paquier <michael@paquier.xyz> Reviewed-by: Jonathan S. Katz <jkatz@postgresql.org> Discussion: https://postgr.es/m/F72E7BC7-189F-4B17-BF47-9735EB72C364@yesql.se
1 parent c15631f commit b577743

File tree

16 files changed

+94
-10
lines changed

16 files changed

+94
-10
lines changed

doc/src/sgml/config.sgml

+20
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,26 @@ include_dir 'conf.d'
11321132
</listitem>
11331133
</varlistentry>
11341134

1135+
<varlistentry>
1136+
<term><varname>scram_iterations</varname> (<type>integer</type>)
1137+
<indexterm>
1138+
<primary><varname>scram_iterations</varname> configuration parameter</primary>
1139+
</indexterm>
1140+
</term>
1141+
<listitem>
1142+
<para>
1143+
The number of computational iterations to be performed when encrypting
1144+
a password using SCRAM-SHA-256. The default is <literal>4096</literal>.
1145+
A higher number of iterations provides additional protection against
1146+
brute-force attacks on stored passwords, but makes authentication
1147+
slower. Changing the value has no effect on existing passwords
1148+
encrypted with SCRAM-SHA-256 as the iteration count is fixed at the
1149+
time of encryption. In order to make use of a changed value, a new
1150+
password must be set.
1151+
</para>
1152+
</listitem>
1153+
</varlistentry>
1154+
11351155
<varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
11361156
<term><varname>krb_server_keyfile</varname> (<type>string</type>)
11371157
<indexterm>

src/backend/libpq/auth-scram.c

+7-2
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ static char *scram_mock_salt(const char *username,
191191
pg_cryptohash_type hash_type,
192192
int key_length);
193193

194+
/*
195+
* The number of iterations to use when generating new secrets.
196+
*/
197+
int scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
198+
194199
/*
195200
* Get a list of SASL mechanisms that this module supports.
196201
*
@@ -496,7 +501,7 @@ pg_be_scram_build_secret(const char *password)
496501

497502
result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN,
498503
saltbuf, SCRAM_DEFAULT_SALT_LEN,
499-
SCRAM_DEFAULT_ITERATIONS, password,
504+
scram_sha_256_iterations, password,
500505
&errstr);
501506

502507
if (prep_password)
@@ -717,7 +722,7 @@ mock_scram_secret(const char *username, pg_cryptohash_type *hash_type,
717722
encoded_salt[encoded_len] = '\0';
718723

719724
*salt = encoded_salt;
720-
*iterations = SCRAM_DEFAULT_ITERATIONS;
725+
*iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
721726

722727
/* StoredKey and ServerKey are not used in a doomed authentication */
723728
memset(stored_key, 0, SCRAM_MAX_KEY_LEN);

src/backend/utils/misc/guc_tables.c

+13
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@
4141
#include "commands/trigger.h"
4242
#include "commands/user.h"
4343
#include "commands/vacuum.h"
44+
#include "common/scram-common.h"
4445
#include "jit/jit.h"
4546
#include "libpq/auth.h"
4647
#include "libpq/libpq.h"
48+
#include "libpq/scram.h"
4749
#include "nodes/queryjumble.h"
4850
#include "optimizer/cost.h"
4951
#include "optimizer/geqo.h"
@@ -3468,6 +3470,17 @@ struct config_int ConfigureNamesInt[] =
34683470
NULL, NULL, NULL
34693471
},
34703472

3473+
{
3474+
{"scram_iterations", PGC_USERSET, CONN_AUTH_AUTH,
3475+
gettext_noop("Sets the iteration count for SCRAM secret generation."),
3476+
NULL,
3477+
GUC_REPORT
3478+
},
3479+
&scram_sha_256_iterations,
3480+
SCRAM_SHA_256_DEFAULT_ITERATIONS, 1, INT_MAX,
3481+
NULL, NULL, NULL
3482+
},
3483+
34713484
/* End-of-list marker */
34723485
{
34733486
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL

src/backend/utils/misc/postgresql.conf.sample

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595

9696
#authentication_timeout = 1min # 1s-600s
9797
#password_encryption = scram-sha-256 # scram-sha-256 or md5
98+
#scram_iterations = 4096
9899
#db_user_namespace = off
99100

100101
# GSSAPI using Kerberos

src/common/scram-common.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,7 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length,
214214
/* Only this hash method is supported currently */
215215
Assert(hash_type == PG_SHA256);
216216

217-
if (iterations <= 0)
218-
iterations = SCRAM_DEFAULT_ITERATIONS;
217+
Assert(iterations > 0);
219218

220219
/* Calculate StoredKey and ServerKey */
221220
if (scram_SaltedPassword(password, hash_type, key_length,

src/include/common/scram-common.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
* Default number of iterations when generating secret. Should be at least
4848
* 4096 per RFC 7677.
4949
*/
50-
#define SCRAM_DEFAULT_ITERATIONS 4096
50+
#define SCRAM_SHA_256_DEFAULT_ITERATIONS 4096
5151

5252
extern int scram_SaltedPassword(const char *password,
5353
pg_cryptohash_type hash_type, int key_length,

src/include/libpq/scram.h

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
#include "libpq/libpq-be.h"
1919
#include "libpq/sasl.h"
2020

21+
/* Number of iterations when generating new secrets */
22+
extern PGDLLIMPORT int scram_sha_256_iterations;
23+
2124
/* SASL implementation callbacks */
2225
extern PGDLLIMPORT const pg_be_sasl_mech pg_be_scram_mech;
2326

src/interfaces/libpq/fe-auth-scram.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ verify_server_signature(fe_scram_state *state, bool *match,
895895
* error details.
896896
*/
897897
char *
898-
pg_fe_scram_build_secret(const char *password, const char **errstr)
898+
pg_fe_scram_build_secret(const char *password, int iterations, const char **errstr)
899899
{
900900
char *prep_password;
901901
pg_saslprep_rc rc;
@@ -927,7 +927,7 @@ pg_fe_scram_build_secret(const char *password, const char **errstr)
927927

928928
result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, saltbuf,
929929
SCRAM_DEFAULT_SALT_LEN,
930-
SCRAM_DEFAULT_ITERATIONS, password,
930+
iterations, password,
931931
errstr);
932932

933933
free(prep_password);

src/interfaces/libpq/fe-auth.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -1341,7 +1341,9 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
13411341
{
13421342
const char *errstr = NULL;
13431343

1344-
crypt_pwd = pg_fe_scram_build_secret(passwd, &errstr);
1344+
crypt_pwd = pg_fe_scram_build_secret(passwd,
1345+
conn->scram_sha_256_iterations,
1346+
&errstr);
13451347
if (!crypt_pwd)
13461348
libpq_append_conn_error(conn, "could not encrypt password: %s", errstr);
13471349
}

src/interfaces/libpq/fe-auth.h

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
2626
/* Mechanisms in fe-auth-scram.c */
2727
extern const pg_fe_sasl_mech pg_scram_mech;
2828
extern char *pg_fe_scram_build_secret(const char *password,
29+
int iterations,
2930
const char **errstr);
3031

3132
#endif /* FE_AUTH_H */

src/interfaces/libpq/fe-connect.c

+2
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,7 @@ pqDropServerData(PGconn *conn)
596596
conn->std_strings = false;
597597
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
598598
conn->in_hot_standby = PG_BOOL_UNKNOWN;
599+
conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
599600
conn->sversion = 0;
600601

601602
/* Drop large-object lookup data */
@@ -4182,6 +4183,7 @@ makeEmptyPGconn(void)
41824183
conn->std_strings = false; /* unless server says differently */
41834184
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
41844185
conn->in_hot_standby = PG_BOOL_UNKNOWN;
4186+
conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
41854187
conn->verbosity = PQERRORS_DEFAULT;
41864188
conn->show_context = PQSHOW_CONTEXT_ERRORS;
41874189
conn->sock = PGINVALID_SOCKET;

src/interfaces/libpq/fe-exec.c

+4
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,10 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
11811181
conn->in_hot_standby =
11821182
(strcmp(value, "on") == 0) ? PG_BOOL_YES : PG_BOOL_NO;
11831183
}
1184+
else if (strcmp(name, "scram_iterations") == 0)
1185+
{
1186+
conn->scram_sha_256_iterations = atoi(value);
1187+
}
11841188
}
11851189

11861190

src/interfaces/libpq/libpq-int.h

+1
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ struct pg_conn
525525
/* Assorted state for SASL, SSL, GSS, etc */
526526
const pg_fe_sasl_mech *sasl;
527527
void *sasl_state;
528+
int scram_sha_256_iterations;
528529

529530
/* SSL structures */
530531
bool ssl_in_use;

src/test/authentication/t/001_password.pl

+24-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,21 @@ sub test_conn
8686
q{SET password_encryption='md5'; CREATE ROLE "md5,role" LOGIN PASSWORD 'pass';}
8787
);
8888

89+
# Create a role with a non-default iteration count
90+
$node->safe_psql(
91+
'postgres',
92+
"SET password_encryption='scram-sha-256';
93+
SET scram_iterations=1024;
94+
CREATE ROLE scram_role_iter LOGIN PASSWORD 'pass';
95+
RESET scram_iterations;"
96+
);
97+
98+
my $res = $node->safe_psql('postgres',
99+
"SELECT substr(rolpassword,1,19)
100+
FROM pg_authid
101+
WHERE rolname = 'scram_role_iter'");
102+
is($res, 'SCRAM-SHA-256$1024:', 'scram_iterations in server side ROLE');
103+
89104
# Create a database to test regular expression.
90105
$node->safe_psql('postgres', "CREATE database regex_testdb;");
91106

@@ -98,7 +113,7 @@ sub test_conn
98113
log_unlike => [qr/connection authenticated:/]);
99114

100115
# SYSTEM_USER is null when not authenticated.
101-
my $res = $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
116+
$res = $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
102117
is($res, 't', "users with trust authentication use SYSTEM_USER = NULL");
103118

104119
# Test SYSTEM_USER with parallel workers when not authenticated.
@@ -283,6 +298,14 @@ sub test_conn
283298
log_like => [
284299
qr/connection authenticated: identity="scram_role" method=scram-sha-256/
285300
]);
301+
test_conn(
302+
$node,
303+
'user=scram_role_iter',
304+
'scram-sha-256',
305+
0,
306+
log_like => [
307+
qr/connection authenticated: identity="scram_role_iter" method=scram-sha-256/
308+
]);
286309
test_conn($node, 'user=md5_role', 'scram-sha-256', 2,
287310
log_unlike => [qr/connection authenticated:/]);
288311

src/test/regress/expected/password.out

+6-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234';
7272
CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
7373
-- invalid length
7474
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
75+
-- Changing the SCRAM iteration count
76+
SET scram_iterations = 1024;
77+
CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount';
7578
SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
7679
FROM pg_authid
7780
WHERE rolname LIKE 'regress_passwd%'
@@ -86,7 +89,8 @@ SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+
8689
regress_passwd6 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
8790
regress_passwd7 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
8891
regress_passwd8 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
89-
(8 rows)
92+
regress_passwd9 | SCRAM-SHA-256$1024:<salt>$<storedkey>:<serverkey>
93+
(9 rows)
9094

9195
-- An empty password is not allowed, in any form
9296
CREATE ROLE regress_passwd_empty PASSWORD '';
@@ -129,6 +133,7 @@ DROP ROLE regress_passwd5;
129133
DROP ROLE regress_passwd6;
130134
DROP ROLE regress_passwd7;
131135
DROP ROLE regress_passwd8;
136+
DROP ROLE regress_passwd9;
132137
DROP ROLE regress_passwd_empty;
133138
DROP ROLE regress_passwd_sha_len0;
134139
DROP ROLE regress_passwd_sha_len1;

src/test/regress/sql/password.sql

+5
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
6363
-- invalid length
6464
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
6565

66+
-- Changing the SCRAM iteration count
67+
SET scram_iterations = 1024;
68+
CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount';
69+
6670
SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
6771
FROM pg_authid
6872
WHERE rolname LIKE 'regress_passwd%'
@@ -97,6 +101,7 @@ DROP ROLE regress_passwd5;
97101
DROP ROLE regress_passwd6;
98102
DROP ROLE regress_passwd7;
99103
DROP ROLE regress_passwd8;
104+
DROP ROLE regress_passwd9;
100105
DROP ROLE regress_passwd_empty;
101106
DROP ROLE regress_passwd_sha_len0;
102107
DROP ROLE regress_passwd_sha_len1;

0 commit comments

Comments
 (0)