Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit68e61ee

Browse files
committed
Change the on-disk format of SCRAM verifiers to conform to RFC 5803.
It doesn't make any immediate difference to PostgreSQL, but might as wellfollow the standard, since one exists. (I looked at RFC 5803 earlier, butdidn't fully understand it back then.)The new format uses Base64 instead of hex to encode StoredKey andServerKey, which makes the verifiers slightly smaller. Using the sameencoding for the salt and the keys also means that you only need oneencoder/decoder instead of two. Although we have code in the backend todo both, we are talking about teaching libpq how to create SCRAM verifiersfor PQencodePassword(), and libpq doesn't currently have any code for hexencoding.Bump catversion, because this renders any existing SCRAM verifiers inpg_authid invalid.Discussion:https://www.postgresql.org/message-id/351ba574-85ea-d9b8-9689-8c928dd0955d@iki.fi
1 parentc29a752 commit68e61ee

File tree

6 files changed

+119
-73
lines changed

6 files changed

+119
-73
lines changed

‎doc/src/sgml/catalogs.sgml

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,14 +1376,22 @@
13761376
32-character hexadecimal MD5 hash. The MD5 hash will be of the user's
13771377
password concatenated to their user name. For example, if user
13781378
<literal>joe</> has password <literal>xyzzy</>, <productname>PostgreSQL</>
1379-
will store the md5 hash of <literal>xyzzyjoe</>. If the password is
1380-
encrypted with SCRAM-SHA-256, it consists of 5 fields separated by colons.
1381-
The first field is the constant <literal>scram-sha-256</literal>, to
1382-
identify the password as a SCRAM-SHA-256 verifier. The second field is a
1383-
salt, Base64-encoded, and the third field is the number of iterations used
1384-
to generate the password. The fourth field and fifth field are the stored
1385-
key and server key, respectively, in hexadecimal format. A password that
1386-
does not follow either of those formats is assumed to be unencrypted.
1379+
will store the md5 hash of <literal>xyzzyjoe</>.
1380+
</para>
1381+
1382+
<para>
1383+
If the password is encrypted with SCRAM-SHA-256, it has the format:
1384+
<synopsis>
1385+
SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</>:<replaceable>&lt;salt&gt;</>$<replaceable>&lt;StoredKey&gt;</>:<replaceable>&lt;ServerKey&gt;</>
1386+
</synopsis>
1387+
where <replaceable>salt</>, <replaceable>StoredKey</> and
1388+
<replaceable>ServerKey</> are in Base64 encoded format. This format is
1389+
the same as that specified by RFC 5803.
1390+
</para>
1391+
1392+
<para>
1393+
A password that does not follow either of those formats is assumed to be
1394+
unencrypted.
13871395
</para>
13881396
</sect1>
13891397

‎src/backend/libpq/auth-scram.c

Lines changed: 90 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*
66
* See the following RFCs for more details:
77
* - RFC 5802: https://tools.ietf.org/html/rfc5802
8+
* - RFC 5803: https://tools.ietf.org/html/rfc5803
89
* - RFC 7677: https://tools.ietf.org/html/rfc7677
910
*
1011
* Here are some differences:
@@ -19,7 +20,7 @@
1920
* - Channel binding is not supported yet.
2021
*
2122
*
22-
* The password stored in pg_authid consists of thesalt,iteration count,
23+
* The password stored in pg_authid consists of the iteration count, salt,
2324
* StoredKey and ServerKey.
2425
*
2526
* SASLprep usage
@@ -111,8 +112,8 @@ typedef struct
111112

112113
constchar*username;/* username from startup packet */
113114

114-
char*salt;/* base64-encoded */
115115
intiterations;
116+
char*salt;/* base64-encoded */
116117
uint8StoredKey[SCRAM_KEY_LEN];
117118
uint8ServerKey[SCRAM_KEY_LEN];
118119

@@ -146,10 +147,10 @@ static char *build_server_first_message(scram_state *state);
146147
staticchar*build_server_final_message(scram_state*state);
147148
staticboolverify_client_proof(scram_state*state);
148149
staticboolverify_final_nonce(scram_state*state);
149-
staticboolparse_scram_verifier(constchar*verifier,char**salt,
150-
int*iterations,uint8*stored_key,uint8*server_key);
151-
staticvoidmock_scram_verifier(constchar*username,char**salt,int*iterations,
152-
uint8*stored_key,uint8*server_key);
150+
staticboolparse_scram_verifier(constchar*verifier,int*iterations,
151+
char**salt,uint8*stored_key,uint8*server_key);
152+
staticvoidmock_scram_verifier(constchar*username,int*iterations,
153+
char**salt,uint8*stored_key,uint8*server_key);
153154
staticboolis_scram_printable(char*p);
154155
staticchar*sanitize_char(charc);
155156
staticchar*scram_MockSalt(constchar*username);
@@ -185,7 +186,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
185186

186187
if (password_type==PASSWORD_TYPE_SCRAM_SHA_256)
187188
{
188-
if (parse_scram_verifier(shadow_pass,&state->salt,&state->iterations,
189+
if (parse_scram_verifier(shadow_pass,&state->iterations,&state->salt,
189190
state->StoredKey,state->ServerKey))
190191
got_verifier= true;
191192
else
@@ -208,7 +209,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
208209

209210
verifier=scram_build_verifier(username,shadow_pass,0);
210211

211-
(void)parse_scram_verifier(verifier,&state->salt,&state->iterations,
212+
(void)parse_scram_verifier(verifier,&state->iterations,&state->salt,
212213
state->StoredKey,state->ServerKey);
213214
pfree(verifier);
214215

@@ -243,7 +244,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
243244
*/
244245
if (!got_verifier)
245246
{
246-
mock_scram_verifier(username,&state->salt,&state->iterations,
247+
mock_scram_verifier(username,&state->iterations,&state->salt,
247248
state->StoredKey,state->ServerKey);
248249
state->doomed= true;
249250
}
@@ -393,14 +394,15 @@ char *
393394
scram_build_verifier(constchar*username,constchar*password,
394395
intiterations)
395396
{
396-
uint8keybuf[SCRAM_KEY_LEN+1];
397-
charstoredkey_hex[SCRAM_KEY_LEN*2+1];
398-
charserverkey_hex[SCRAM_KEY_LEN*2+1];
399-
charsalt[SCRAM_SALT_LEN];
400-
char*encoded_salt;
401-
intencoded_len;
402397
char*prep_password=NULL;
403398
pg_saslprep_rcrc;
399+
charsaltbuf[SCRAM_SALT_LEN];
400+
uint8keybuf[SCRAM_KEY_LEN];
401+
char*encoded_salt;
402+
char*encoded_storedkey;
403+
char*encoded_serverkey;
404+
intencoded_len;
405+
char*result;
404406

405407
/*
406408
* Normalize the password with SASLprep. If that doesn't work, because
@@ -414,7 +416,8 @@ scram_build_verifier(const char *username, const char *password,
414416
if (iterations <=0)
415417
iterations=SCRAM_ITERATIONS_DEFAULT;
416418

417-
if (!pg_backend_random(salt,SCRAM_SALT_LEN))
419+
/* Generate salt, and encode it in base64 */
420+
if (!pg_backend_random(saltbuf,SCRAM_SALT_LEN))
418421
{
419422
ereport(LOG,
420423
(errcode(ERRCODE_INTERNAL_ERROR),
@@ -423,26 +426,38 @@ scram_build_verifier(const char *username, const char *password,
423426
}
424427

425428
encoded_salt=palloc(pg_b64_enc_len(SCRAM_SALT_LEN)+1);
426-
encoded_len=pg_b64_encode(salt,SCRAM_SALT_LEN,encoded_salt);
429+
encoded_len=pg_b64_encode(saltbuf,SCRAM_SALT_LEN,encoded_salt);
427430
encoded_salt[encoded_len]='\0';
428431

429-
/* Calculate StoredKey, and encode it inhex */
430-
scram_ClientOrServerKey(password,salt,SCRAM_SALT_LEN,
432+
/* Calculate StoredKey, and encode it inbase64 */
433+
scram_ClientOrServerKey(password,saltbuf,SCRAM_SALT_LEN,
431434
iterations,SCRAM_CLIENT_KEY_NAME,keybuf);
432435
scram_H(keybuf,SCRAM_KEY_LEN,keybuf);/* StoredKey */
433-
(void)hex_encode((constchar*)keybuf,SCRAM_KEY_LEN,storedkey_hex);
434-
storedkey_hex[SCRAM_KEY_LEN*2]='\0';
436+
437+
encoded_storedkey=palloc(pg_b64_enc_len(SCRAM_KEY_LEN)+1);
438+
encoded_len=pg_b64_encode((constchar*)keybuf,SCRAM_KEY_LEN,
439+
encoded_storedkey);
440+
encoded_storedkey[encoded_len]='\0';
435441

436442
/* And same for ServerKey */
437-
scram_ClientOrServerKey(password,salt,SCRAM_SALT_LEN,iterations,
443+
scram_ClientOrServerKey(password,saltbuf,SCRAM_SALT_LEN,iterations,
438444
SCRAM_SERVER_KEY_NAME,keybuf);
439-
(void)hex_encode((constchar*)keybuf,SCRAM_KEY_LEN,serverkey_hex);
440-
serverkey_hex[SCRAM_KEY_LEN*2]='\0';
445+
446+
encoded_serverkey=palloc(pg_b64_enc_len(SCRAM_KEY_LEN)+1);
447+
encoded_len=pg_b64_encode((constchar*)keybuf,SCRAM_KEY_LEN,
448+
encoded_serverkey);
449+
encoded_serverkey[encoded_len]='\0';
450+
451+
result=psprintf("SCRAM-SHA-256$%d:%s$%s:%s",iterations,encoded_salt,
452+
encoded_storedkey,encoded_serverkey);
441453

442454
if (prep_password)
443455
pfree(prep_password);
456+
pfree(encoded_salt);
457+
pfree(encoded_storedkey);
458+
pfree(encoded_serverkey);
444459

445-
returnpsprintf("scram-sha-256:%s:%d:%s:%s",encoded_salt,iterations,storedkey_hex,serverkey_hex);
460+
returnresult;
446461
}
447462

448463
/*
@@ -464,7 +479,7 @@ scram_verify_plain_password(const char *username, const char *password,
464479
char*prep_password=NULL;
465480
pg_saslprep_rcrc;
466481

467-
if (!parse_scram_verifier(verifier,&encoded_salt,&iterations,
482+
if (!parse_scram_verifier(verifier,&iterations,&encoded_salt,
468483
stored_key,server_key))
469484
{
470485
/*
@@ -509,13 +524,14 @@ scram_verify_plain_password(const char *username, const char *password,
509524
bool
510525
is_scram_verifier(constchar*verifier)
511526
{
512-
char*salt=NULL;
513527
intiterations;
528+
char*salt=NULL;
514529
uint8stored_key[SCRAM_KEY_LEN];
515530
uint8server_key[SCRAM_KEY_LEN];
516531
boolresult;
517532

518-
result=parse_scram_verifier(verifier,&salt,&iterations,stored_key,server_key);
533+
result=parse_scram_verifier(verifier,&iterations,&salt,
534+
stored_key,server_key);
519535
if (salt)
520536
pfree(salt);
521537

@@ -529,60 +545,82 @@ is_scram_verifier(const char *verifier)
529545
* Returns true if the SCRAM verifier has been parsed, and false otherwise.
530546
*/
531547
staticbool
532-
parse_scram_verifier(constchar*verifier,char**salt,int*iterations,
548+
parse_scram_verifier(constchar*verifier,int*iterations,char**salt,
533549
uint8*stored_key,uint8*server_key)
534550
{
535551
char*v;
536552
char*p;
553+
char*scheme_str;
554+
char*salt_str;
555+
char*iterations_str;
556+
char*storedkey_str;
557+
char*serverkey_str;
558+
intdecoded_len;
559+
char*decoded_salt_buf;
537560

538561
/*
539562
* The verifier is of form:
540563
*
541-
*scram-sha-256:<salt>:<iterations>:<storedkey>:<serverkey>
564+
*SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
542565
*/
543-
if (strncmp(verifier,"scram-sha-256:",strlen("scram-sha-256:"))!=0)
544-
return false;
545-
546-
v=pstrdup(verifier+strlen("scram-sha-256:"));
547-
548-
/* salt */
549-
if ((p=strtok(v,":"))==NULL)
566+
v=pstrdup(verifier);
567+
if ((scheme_str=strtok(v,"$"))==NULL)
568+
gotoinvalid_verifier;
569+
if ((iterations_str=strtok(NULL,":"))==NULL)
570+
gotoinvalid_verifier;
571+
if ((salt_str=strtok(NULL,"$"))==NULL)
572+
gotoinvalid_verifier;
573+
if ((storedkey_str=strtok(NULL,":"))==NULL)
574+
gotoinvalid_verifier;
575+
if ((serverkey_str=strtok(NULL,""))==NULL)
550576
gotoinvalid_verifier;
551-
*salt=pstrdup(p);
552577

553-
/*iterations */
554-
if ((p=strtok(NULL,":"))==NULL)
578+
/*Parse the fields */
579+
if (strcmp(scheme_str,"SCRAM-SHA-256")!=0)
555580
gotoinvalid_verifier;
581+
556582
errno=0;
557-
*iterations=strtol(p,&p,10);
583+
*iterations=strtol(iterations_str,&p,10);
558584
if (*p||errno!=0)
559585
gotoinvalid_verifier;
560586

561-
/* storedkey */
562-
if ((p=strtok(NULL,":"))==NULL)
563-
gotoinvalid_verifier;
564-
if (strlen(p)!=SCRAM_KEY_LEN*2)
587+
/*
588+
* Verify that the salt is in Base64-encoded format, by decoding it,
589+
* although we return the encoded version to the caller.
590+
*/
591+
decoded_salt_buf=palloc(pg_b64_dec_len(strlen(salt_str)));
592+
decoded_len=pg_b64_decode(salt_str,strlen(salt_str),decoded_salt_buf);
593+
if (decoded_len<0)
565594
gotoinvalid_verifier;
595+
*salt=pstrdup(salt_str);
566596

567-
hex_decode(p,SCRAM_KEY_LEN*2, (char*)stored_key);
597+
/*
598+
* Decode StoredKey and ServerKey.
599+
*/
600+
if (pg_b64_dec_len(strlen(storedkey_str)!=SCRAM_KEY_LEN))
601+
gotoinvalid_verifier;
602+
decoded_len=pg_b64_decode(storedkey_str,strlen(storedkey_str),
603+
(char*)stored_key);
604+
if (decoded_len!=SCRAM_KEY_LEN)
605+
gotoinvalid_verifier;
568606

569-
/* serverkey */
570-
if ((p=strtok(NULL,":"))==NULL)
607+
if (pg_b64_dec_len(strlen(serverkey_str)!=SCRAM_KEY_LEN))
571608
gotoinvalid_verifier;
572-
if (strlen(p)!=SCRAM_KEY_LEN*2)
609+
decoded_len=pg_b64_decode(serverkey_str,strlen(serverkey_str),
610+
(char*)server_key);
611+
if (decoded_len!=SCRAM_KEY_LEN)
573612
gotoinvalid_verifier;
574-
hex_decode(p,SCRAM_KEY_LEN*2, (char*)server_key);
575613

576-
pfree(v);
577614
return true;
578615

579616
invalid_verifier:
580617
pfree(v);
618+
*salt=NULL;
581619
return false;
582620
}
583621

584622
staticvoid
585-
mock_scram_verifier(constchar*username,char**salt,int*iterations,
623+
mock_scram_verifier(constchar*username,int*iterations,char**salt,
586624
uint8*stored_key,uint8*server_key)
587625
{
588626
char*raw_salt;

‎src/backend/libpq/crypt.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ get_password_type(const char *shadow_pass)
100100
{
101101
if (strncmp(shadow_pass,"md5",3)==0&&strlen(shadow_pass)==MD5_PASSWD_LEN)
102102
returnPASSWORD_TYPE_MD5;
103-
if (strncmp(shadow_pass,"scram-sha-256:",strlen("scram-sha-256:"))==0)
103+
if (strncmp(shadow_pass,"SCRAM-SHA-256$",strlen("SCRAM-SHA-256$"))==0)
104104
returnPASSWORD_TYPE_SCRAM_SHA_256;
105105
returnPASSWORD_TYPE_PLAINTEXT;
106106
}

‎src/include/catalog/catversion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@
5353
*/
5454

5555
/*yyyymmddN */
56-
#defineCATALOG_VERSION_NO201704171
56+
#defineCATALOG_VERSION_NO201704211
5757

5858
#endif

‎src/test/regress/expected/password.out

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ CREATE ROLE regress_passwd5 PASSWORD NULL;
2323
-- check list of created entries
2424
--
2525
-- The scram verifier will look something like:
26-
--scram-sha-256:E4HxLGtnRzsYwg==:4096:5ebc825510cb7862efd87dfa638d8337179e6913a724441dc9e888a856fbc10c:e966b1c72fad89d69aaebb156eae04edc9581286f92207c044711e79cd461bee
26+
--SCRAM-SHA-256$4096:E4HxLGtnRzsYwg==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo=
2727
--
2828
-- Since the salt is random, the exact value stored will be different on every test
2929
-- run. Use a regular expression to mask the changing parts.
30-
SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1:<salt>:\3:<storedkey>:<serverkey>') as rolpassword_masked
30+
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
3131
FROM pg_authid
3232
WHERE rolname LIKE 'regress_passwd%'
3333
ORDER BY rolname, rolpassword;
@@ -36,7 +36,7 @@ SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):
3636
regress_passwd1 | role_pwd1
3737
regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3
3838
regress_passwd3 | md50e5699b6911d87f17a08b8d76a21e8b8
39-
regress_passwd4 |scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
39+
regress_passwd4 |SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
4040
regress_passwd5 |
4141
(5 rows)
4242

@@ -59,11 +59,11 @@ ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
5959
ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5dfa155cadd5f4ad57860162f3fab9cdb'; -- encrypted with MD5
6060
SET password_encryption = 'md5';
6161
ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
62-
ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'scram-sha-256:VLK4RMaQLCvNtQ==:4096:3ded2376f7aafa93b1bdbd71bcc18b7d6ee50ed018029cc583d152ef3fc7d430:a6dd36dfc94c181956a6ae95f05e01b1864f0a22a2657d1de4ba84d2a24dc438'; -- client-supplied SCRAM verifier, use as it is
62+
ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo='; -- client-supplied SCRAM verifier, use as it is
6363
SET password_encryption = 'scram-sha-256';
6464
ALTER ROLE regress_passwd5 ENCRYPTED PASSWORD 'foo'; -- create SCRAM verifier
6565
CREATE ROLE regress_passwd6 ENCRYPTED PASSWORD 'md53725413363ab045e20521bf36b8d8d7f'; -- encrypted with MD5, use as it is
66-
SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1:<salt>:\3:<storedkey>:<serverkey>') as rolpassword_masked
66+
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
6767
FROM pg_authid
6868
WHERE rolname LIKE 'regress_passwd%'
6969
ORDER BY rolname, rolpassword;
@@ -72,8 +72,8 @@ SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):
7272
regress_passwd1 | foo
7373
regress_passwd2 | md5dfa155cadd5f4ad57860162f3fab9cdb
7474
regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
75-
regress_passwd4 |scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
76-
regress_passwd5 |scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
75+
regress_passwd4 |SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
76+
regress_passwd5 |SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
7777
regress_passwd6 | md53725413363ab045e20521bf36b8d8d7f
7878
(6 rows)
7979

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp